diff --git a/.changeset/dry-suits-destroy.md b/.changeset/dry-suits-destroy.md new file mode 100644 index 000000000..ad3f5f901 --- /dev/null +++ b/.changeset/dry-suits-destroy.md @@ -0,0 +1,25 @@ +--- +'@hey-api/openapi-ts': minor +--- + +feat(valibot): generate a single schema for requests + +### Single Valibot schema per request + +Previously, we generated a separate schema for each endpoint parameter and request body. In v0.76.0, a single request schema is generated for the whole endpoint. It may contain a request body, parameters, and headers. + +```ts +const vData = v.object({ + body: v.optional(v.object({ + foo: v.optional(v.string()), + bar: v.optional(v.union([v.number(), v.null()])), + })), + headers: v.optional(v.never()), + path: v.object({ + baz: v.string(), + }), + query: v.optional(v.never()), +}); +``` + +If you need to access individual fields, you can do so using the [`.entries`](https://valibot.dev/api/object/) API. For example, we can get the request body schema with `vData.entries.body`. diff --git a/.changeset/six-birds-peel.md b/.changeset/six-birds-peel.md new file mode 100644 index 000000000..74ae8bf7b --- /dev/null +++ b/.changeset/six-birds-peel.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +fix(valibot): add `metadata` option to generate additional metadata for documentation, code generation, AI structured outputs, form validation, and other purposes diff --git a/docs/openapi-ts/migrating.md b/docs/openapi-ts/migrating.md index 95bf5b4cf..58d0a5b83 100644 --- a/docs/openapi-ts/migrating.md +++ b/docs/openapi-ts/migrating.md @@ -27,6 +27,30 @@ This config option is deprecated and will be removed in favor of [clients](./cli This config option is deprecated and will be removed. +## v0.76.0 + +### Single Valibot schema per request + +Previously, we generated a separate schema for each endpoint parameter and request body. In v0.76.0, a single request schema is generated for the whole endpoint. It may contain a request body, parameters, and headers. + +```ts +const vData = v.object({ + body: v.optional( + v.object({ + foo: v.optional(v.string()), + bar: v.optional(v.union([v.number(), v.null()])), + }), + ), + headers: v.optional(v.never()), + path: v.object({ + baz: v.string(), + }), + query: v.optional(v.never()), +}); +``` + +If you need to access individual fields, you can do so using the [`.entries`](https://valibot.dev/api/object/) API. For example, we can get the request body schema with `vData.entries.body`. + ## v0.75.0 ### Updated TanStack Query options diff --git a/docs/openapi-ts/plugins/valibot.md b/docs/openapi-ts/plugins/valibot.md index c16770f3f..963137f57 100644 --- a/docs/openapi-ts/plugins/valibot.md +++ b/docs/openapi-ts/plugins/valibot.md @@ -26,20 +26,18 @@ Launch demo ## Features - seamless integration with `@hey-api/openapi-ts` ecosystem -- Valibot schemas for request payloads, parameters, and responses +- Valibot schemas for requests, responses, and reusable definitions ## Installation In your [configuration](/openapi-ts/get-started), add `valibot` to your plugins and you'll be ready to generate Valibot artifacts. :tada: ```js -import { defaultPlugins } from '@hey-api/openapi-ts'; - export default { input: 'https://get.heyapi.dev/hey-api/backend', output: 'src/client', plugins: [ - ...defaultPlugins, + // ...other plugins 'valibot', // [!code ++] ], }; @@ -50,13 +48,11 @@ export default { To automatically validate response data in your SDKs, set `sdk.validator` to `true`. ```js -import { defaultPlugins } from '@hey-api/openapi-ts'; - export default { input: 'https://get.heyapi.dev/hey-api/backend', output: 'src/client', plugins: [ - ...defaultPlugins, + // ...other plugins 'valibot', { name: '@hey-api/sdk', // [!code ++] @@ -70,6 +66,32 @@ export default { The Valibot plugin will generate the following artifacts, depending on the input specification. +## Requests + +A single request schema is generated for each endpoint. It may contain a request body, parameters, and headers. + +```ts +const vData = v.object({ + body: v.optional( + v.object({ + foo: v.optional(v.string()), + bar: v.optional(v.union([v.number(), v.null()])), + }), + ), + headers: v.optional(v.never()), + path: v.object({ + baz: v.string(), + }), + query: v.optional(v.never()), +}); +``` + +::: tip +If you need to access individual fields, you can do so using the [`.entries`](https://valibot.dev/api/object/) API. For example, we can get the request body schema with `vData.entries.body`. +::: + +You can customize the naming and casing pattern for requests using the `requests.name` and `requests.case` options. + ## Responses A single Valibot schema is generated for all endpoint's responses. If the endpoint describes multiple responses, the generated schema is a union of all possible response shapes. @@ -85,37 +107,38 @@ const vResponse = v.union([ ]); ``` -## Request Bodies - -If an endpoint describes a request body, we will generate a Valibot schema representing its shape. +You can customize the naming and casing pattern for responses using the `responses.name` and `responses.case` options. -```ts -const vData = v.object({ - foo: v.optional(v.string()), - bar: v.optional(v.union([v.number(), v.null()])), -}); -``` +## Definitions -## Parameters - -A separate Valibot schema is generated for every request parameter. +A Valibot schema is generated for every reusable definition from your input. ```ts -const vParameterFoo = v.pipe(v.number(), v.integer()); +const vFoo = v.pipe(v.number(), v.integer()); -const vParameterBar = v.string(); +const vBar = v.object({ + bar: v.optional(v.array(v.pipe(v.number(), v.integer()))), +}); ``` -## Schemas +You can customize the naming and casing pattern for definitions using the `definitions.name` and `definitions.case` options. -A separate Valibot schema is generated for every reusable schema. +## Metadata -```ts -const vFoo = v.pipe(v.number(), v.integer()); +It's often useful to associate a schema with some additional [metadata](https://valibot.dev/api/metadata/) for documentation, code generation, AI structured outputs, form validation, and other purposes. If this is your use case, you can set `metadata` to `true` to generate additional metadata about schemas. -const vBar = v.object({ - bar: v.optional(v.union([v.array(v.unknown()), v.null()])), -}); +```js +export default { + input: 'https://get.heyapi.dev/hey-api/backend', + output: 'src/client', + plugins: [ + // ...other plugins + { + metadata: true, // [!code ++] + name: 'valibot', + }, + ], +}; ``` diff --git a/packages/openapi-ts-tests/test/3.1.x.test.ts b/packages/openapi-ts-tests/test/3.1.x.test.ts index 15e34ab15..228a1b67e 100644 --- a/packages/openapi-ts-tests/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/test/3.1.x.test.ts @@ -734,7 +734,10 @@ describe(`OpenAPI ${version}`, () => { input: 'validators.yaml', output: 'validators-metadata', plugins: [ - 'valibot', + { + metadata: true, + name: 'valibot', + }, { metadata: true, name: 'zod', diff --git a/packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts b/packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts index 17026f2e5..0e3e4580f 100644 --- a/packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts +++ b/packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts @@ -12,6 +12,13 @@ export const vBar = v.object({ foo: v.pipe(v.number(), v.integer()) }); +export const vPostFooData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * OK */ diff --git a/packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts b/packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts index 26fa28b78..c4dfa3255 100644 --- a/packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts +++ b/packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts @@ -398,212 +398,272 @@ export const vFailureFailure = v.object({ reference_code: v.optional(v.string()) }); -/** - * Testing multiline comments in string: First line - * Second line - * - * Fourth line - */ -export const vCallWithDescriptionsParameterParameterWithBreaks = v.string(); - -/** - * Testing backticks in string: `backticks` and ```multiple backticks``` should work - */ -export const vCallWithDescriptionsParameterParameterWithBackticks = v.string(); - -/** - * Testing slashes in string: \backwards\\\ and /forwards/// should work - */ -export const vCallWithDescriptionsParameterParameterWithSlashes = v.string(); - -/** - * Testing expression placeholders in string: ${expression} should work - */ -export const vCallWithDescriptionsParameterParameterWithExpressionPlaceholders = v.string(); - -/** - * Testing quotes in string: 'single quote''' and "double quotes""" should work - */ -export const vCallWithDescriptionsParameterParameterWithQuotes = v.string(); - -/** - * Testing reserved characters in string: * inline * and ** inline ** should work - */ -export const vCallWithDescriptionsParameterParameterWithReservedCharacters = v.string(); - -/** - * This is the parameter that goes into the header - */ -export const vCallWithParametersParameterParameterHeader = v.string(); - -/** - * This is the parameter that goes into the path - */ -export const vCallWithParametersParameterParameterPath = v.string(); - -/** - * api-version should be required in standalone clients - */ -export const vCallWithParametersParameterApiVersion = v.string(); - -/** - * This is the parameter that goes into the query params - */ -export const vCallWithParametersParameterParameterQuery = v.string(); - -/** - * This is the parameter that goes into the request header - */ -export const vCallWithWeirdParameterNamesParameterParameterHeader = v.string(); - -/** - * This is the parameter that goes into the path - */ -export const vCallWithWeirdParameterNamesParameterParameterPath1 = v.string(); - -/** - * This is the parameter that goes into the path - */ -export const vCallWithWeirdParameterNamesParameterParameterPath2 = v.string(); +export const vServiceWithEmptyTagData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the path - */ -export const vCallWithWeirdParameterNamesParameterParameterPath3 = v.string(); +export const vPatchApiVbyApiVersionNoTagData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * api-version should be required in standalone clients - */ -export const vCallWithWeirdParameterNamesParameterApiVersion = v.string(); +export const vFooWowData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter with a reserved keyword - */ -export const vCallWithWeirdParameterNamesParameterDefault = v.string(); +export const vDeleteCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the request query params - */ -export const vCallWithWeirdParameterNamesParameterParameterQuery = v.string(); +export const vGetCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a simple string with default value - */ -export const vCallWithDefaultParametersParameterParameterString = v.optional(v.string(), 'Hello World!'); +export const vHeadCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a simple number with default value - */ -export const vCallWithDefaultParametersParameterParameterNumber = v.optional(v.number(), 123); +export const vOptionsCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a simple boolean with default value - */ -export const vCallWithDefaultParametersParameterParameterBoolean = v.optional(v.boolean(), true); +export const vPatchCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a simple enum with default value - */ -export const vCallWithDefaultParametersParameterParameterEnum = v.picklist([ - 'Success', - 'Warning', - 'Error' -]); +export const vPostCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a model with one string property - */ -export const vCallWithDefaultParametersParameterParameterModel = v.optional(v.object({ - prop: v.optional(v.string()) -}), { - prop: 'Hello World!' +export const vPutCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) }); -/** - * This is a simple string that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterString = v.optional(v.string(), 'Hello World!'); +export const vCallWithDescriptionsData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameterWithBreaks: v.optional(v.string()), + parameterWithBackticks: v.optional(v.string()), + parameterWithSlashes: v.optional(v.string()), + parameterWithExpressionPlaceholders: v.optional(v.string()), + parameterWithQuotes: v.optional(v.string()), + parameterWithReservedCharacters: v.optional(v.string()) + })) +}); -/** - * This is a simple number that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterNumber = v.optional(v.number(), 123); +export const vCallWithParametersData = v.object({ + body: v.optional(v.never()), + headers: v.object({ + parameterHeader: v.string() + }), + path: v.object({ + parameterPath: v.string(), + 'api-version': v.string() + }), + query: v.object({ + parameterQuery: v.string() + }) +}); -/** - * This is a simple boolean that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterBoolean = v.optional(v.boolean(), true); +export const vCallWithWeirdParameterNamesData = v.object({ + body: v.optional(v.never()), + headers: v.object({ + 'parameter.header': v.string() + }), + path: v.object({ + 'parameter.path.1': v.optional(v.string()), + 'parameter-path-2': v.optional(v.string()), + 'PARAMETER-PATH-3': v.optional(v.string()), + 'api-version': v.string() + }), + query: v.object({ + default: v.optional(v.string()), + 'parameter-query': v.string() + }) +}); -/** - * This is a simple enum that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterEnum = v.picklist([ - 'Success', - 'Warning', - 'Error' -]); +export const vCallWithDefaultParametersData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameterString: v.optional(v.string(), 'Hello World!'), + parameterNumber: v.optional(v.number(), 123), + parameterBoolean: v.optional(v.boolean(), true), + parameterEnum: v.picklist([ + 'Success', + 'Warning', + 'Error' + ]), + parameterModel: v.optional(v.object({ + prop: v.optional(v.string()) + }), { + prop: 'Hello World!' + }) + }) +}); -/** - * This is a optional string with default - */ -export const vCallToTestOrderOfParamsParameterParameterOptionalStringWithDefault = v.optional(v.string(), 'Hello World!'); +export const vCallWithDefaultOptionalParametersData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameterString: v.optional(v.string(), 'Hello World!'), + parameterNumber: v.optional(v.number(), 123), + parameterBoolean: v.optional(v.boolean(), true), + parameterEnum: v.optional(v.picklist([ + 'Success', + 'Warning', + 'Error' + ])) + })) +}); -/** - * This is a optional string with empty default - */ -export const vCallToTestOrderOfParamsParameterParameterOptionalStringWithEmptyDefault = v.optional(v.string(), ''); +export const vCallToTestOrderOfParamsData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameterOptionalStringWithDefault: v.optional(v.string(), 'Hello World!'), + parameterOptionalStringWithEmptyDefault: v.optional(v.string(), ''), + parameterOptionalStringWithNoDefault: v.optional(v.string()), + parameterStringWithDefault: v.optional(v.string(), 'Hello World!'), + parameterStringWithEmptyDefault: v.optional(v.string(), ''), + parameterStringWithNoDefault: v.string(), + parameterStringNullableWithNoDefault: v.optional(v.union([ + v.string(), + v.null() + ])), + parameterStringNullableWithDefault: v.optional(v.union([ + v.string(), + v.null() + ]), null) + }) +}); -/** - * This is a optional string with no default - */ -export const vCallToTestOrderOfParamsParameterParameterOptionalStringWithNoDefault = v.string(); +export const vDuplicateNameData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string with default - */ -export const vCallToTestOrderOfParamsParameterParameterStringWithDefault = v.optional(v.string(), 'Hello World!'); +export const vDuplicateName2Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string with empty default - */ -export const vCallToTestOrderOfParamsParameterParameterStringWithEmptyDefault = v.optional(v.string(), ''); +export const vDuplicateName3Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string with no default - */ -export const vCallToTestOrderOfParamsParameterParameterStringWithNoDefault = v.string(); +export const vDuplicateName4Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string that can be null with no default - */ -export const vCallToTestOrderOfParamsParameterParameterStringNullableWithNoDefault = v.union([ - v.string(), - v.null() -]); +export const vCallWithNoContentResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string that can be null with default - */ -export const vCallToTestOrderOfParamsParameterParameterStringNullableWithDefault = v.optional(v.union([ - v.string(), - v.null() -]), null); +export const vCallWithResponseAndNoContentResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); export const vCallWithResponseAndNoContentResponseResponse = v.union([ v.number(), v.unknown() ]); +export const vDummyAData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + +export const vDummyBData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + +export const vCallWithResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * Message for default response */ export const vCallWithResponseResponse = vModelWithString; +export const vCallWithDuplicateResponsesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * Message for 201 response */ export const vCallWithDuplicateResponsesResponse = vModelWithString; +export const vCallWithResponsesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vCallWithResponsesResponse = v.union([ v.object({ '@namespace.string': v.optional(v.pipe(v.string(), v.readonly())), @@ -614,69 +674,38 @@ export const vCallWithResponsesResponse = v.union([ vModelThatExtendsExtends ]); -/** - * This is an array parameter that is sent as csv format (comma-separated values) - */ -export const vCollectionFormatParameterParameterArrayCsv = v.array(v.string()); - -/** - * This is an array parameter that is sent as ssv format (space-separated values) - */ -export const vCollectionFormatParameterParameterArraySsv = v.array(v.string()); - -/** - * This is an array parameter that is sent as tsv format (tab-separated values) - */ -export const vCollectionFormatParameterParameterArrayTsv = v.array(v.string()); - -/** - * This is an array parameter that is sent as pipes format (pipe-separated values) - */ -export const vCollectionFormatParameterParameterArrayPipes = v.array(v.string()); - -/** - * This is an array parameter that is sent as multi format (multiple parameter instances) - */ -export const vCollectionFormatParameterParameterArrayMulti = v.array(v.string()); - -/** - * This is a number parameter - */ -export const vTypesParameterId = v.pipe(v.number(), v.integer()); - -/** - * This is a number parameter - */ -export const vTypesParameterParameterNumber = v.optional(v.number(), 123); - -/** - * This is a string parameter - */ -export const vTypesParameterParameterString = v.optional(v.string(), 'default'); - -/** - * This is a boolean parameter - */ -export const vTypesParameterParameterBoolean = v.optional(v.boolean(), true); - -/** - * This is an array parameter - */ -export const vTypesParameterParameterArray = v.array(v.string()); - -/** - * This is a dictionary parameter - */ -export const vTypesParameterParameterDictionary = v.object({}); +export const vCollectionFormatData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameterArrayCSV: v.array(v.string()), + parameterArraySSV: v.array(v.string()), + parameterArrayTSV: v.array(v.string()), + parameterArrayPipes: v.array(v.string()), + parameterArrayMulti: v.array(v.string()) + }) +}); -/** - * This is an enum parameter - */ -export const vTypesParameterParameterEnum = v.picklist([ - 'Success', - 'Warning', - 'Error' -]); +export const vTypesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.object({ + id: v.optional(v.pipe(v.number(), v.integer())) + })), + query: v.object({ + parameterNumber: v.optional(v.number(), 123), + parameterString: v.optional(v.string(), 'default'), + parameterBoolean: v.optional(v.boolean(), true), + parameterArray: v.array(v.string()), + parameterDictionary: v.object({}), + parameterEnum: v.picklist([ + 'Success', + 'Warning', + 'Error' + ]) + }) +}); export const vTypesResponse = v.union([ v.number(), @@ -685,22 +714,22 @@ export const vTypesResponse = v.union([ v.object({}) ]); -/** - * Parameter containing object - */ -export const vComplexTypesParameterParameterObject = v.object({ - first: v.optional(v.object({ - second: v.optional(v.object({ - third: v.optional(v.string()) - })) - })) -}); - -/** - * This is a model with one string property - */ -export const vComplexTypesParameterParameterReference = v.object({ - prop: v.optional(v.string()) +export const vComplexTypesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameterObject: v.object({ + first: v.optional(v.object({ + second: v.optional(v.object({ + third: v.optional(v.string()) + })) + })) + }), + parameterReference: v.object({ + prop: v.optional(v.string()) + }) + }) }); /** @@ -708,25 +737,42 @@ export const vComplexTypesParameterParameterReference = v.object({ */ export const vComplexTypesResponse = v.array(vModelWithString); -/** - * Status code to return - */ -export const vTestErrorCodeParameterStatus = v.string(); +export const vCallWithResultFromHeaderData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * Dummy input param - */ -export const vNonAsciiæøåÆøÅöôêÊ字符串ParameterNonAsciiParamæøåÆøÅöôêÊ = v.pipe(v.number(), v.integer()); +export const vTestErrorCodeData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + status: v.string() + }) +}); + +export const vNonAsciiæøåÆøÅöôêÊ字符串Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + 'nonAsciiParamæøåÆØÅöôêÊ': v.pipe(v.number(), v.integer()) + }) +}); /** * Successful response */ export const vNonAsciiæøåÆøÅöôêÊ字符串Response = vNonAsciiStringæøåÆøÅöôêÊ字符串; -/** - * Body should not be unknown - */ -export const vPostApiVbyApiVersionBodyData = vParameterActivityParams; +export const vPostApiVbyApiVersionBodyData = v.object({ + body: vParameterActivityParams, + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); /** * OK diff --git a/packages/openapi-ts-tests/test/__snapshots__/3.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts b/packages/openapi-ts-tests/test/__snapshots__/3.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts index 17026f2e5..0e3e4580f 100644 --- a/packages/openapi-ts-tests/test/__snapshots__/3.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts +++ b/packages/openapi-ts-tests/test/__snapshots__/3.0.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts @@ -12,6 +12,13 @@ export const vBar = v.object({ foo: v.pipe(v.number(), v.integer()) }); +export const vPostFooData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * OK */ diff --git a/packages/openapi-ts-tests/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts b/packages/openapi-ts-tests/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts index 0d1c81e71..d369d9e96 100644 --- a/packages/openapi-ts-tests/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts +++ b/packages/openapi-ts-tests/test/__snapshots__/3.0.x/plugins/valibot/default/valibot.gen.ts @@ -1070,385 +1070,418 @@ export const vSimpleRequestBody = vModelWithString; export const vSimpleFormData = vModelWithString; -export const vImportData = v.union([ - vModelWithReadOnlyAndWriteOnly, - vModelWithArrayReadOnlyAndWriteOnly -]); +export const vExportData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + +export const vPatchApiVbyApiVersionNoTagData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + +export const vImportData = v.object({ + body: v.union([ + vModelWithReadOnlyAndWriteOnly, + vModelWithArrayReadOnlyAndWriteOnly + ]), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); export const vImportResponse = v.union([ vModelFromZendesk, vModelWithReadOnlyAndWriteOnly ]); +export const vFooWowData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + +export const vApiVVersionODataControllerCountData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * Success */ export const vApiVVersionODataControllerCountResponse = vModelFromZendesk; -/** - * foo in method - */ -export const vGetApiVbyApiVersionSimpleOperationParameterFooParam = v.string(); +export const vGetApiVbyApiVersionSimpleOperationData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.object({ + foo_param: v.string() + }), + query: v.optional(v.never()) +}); /** * Response is a simple number */ export const vGetApiVbyApiVersionSimpleOperationResponse = v.number(); -/** - * foo in method - */ -export const vDeleteFooParameterFooParam = v.string(); - -/** - * bar in method - */ -export const vDeleteFooParameterBarParam = v.string(); - -/** - * Parameter with illegal characters - */ -export const vDeleteFooParameterXFooBar = vModelWithString; - -/** - * Testing multiline comments in string: First line - * Second line - * - * Fourth line - */ -export const vCallWithDescriptionsParameterParameterWithBreaks = v.string(); - -/** - * Testing backticks in string: `backticks` and ```multiple backticks``` should work - */ -export const vCallWithDescriptionsParameterParameterWithBackticks = v.string(); - -/** - * Testing slashes in string: \backwards\\\ and /forwards/// should work - */ -export const vCallWithDescriptionsParameterParameterWithSlashes = v.string(); - -/** - * Testing expression placeholders in string: ${expression} should work - */ -export const vCallWithDescriptionsParameterParameterWithExpressionPlaceholders = v.string(); - -/** - * Testing quotes in string: 'single quote''' and "double quotes""" should work - */ -export const vCallWithDescriptionsParameterParameterWithQuotes = v.string(); - -/** - * Testing reserved characters in string: * inline * and ** inline ** should work - */ -export const vCallWithDescriptionsParameterParameterWithReservedCharacters = v.string(); - -/** - * This parameter is deprecated - * @deprecated - */ -export const vDeprecatedCallParameterParameter = v.union([ - vDeprecatedModel, - v.null() -]); - -/** - * This is the parameter that goes into the body - */ -export const vCallWithParametersData = v.union([ - v.object({}), - v.null() -]); - -/** - * This is the parameter that goes into the cookie - */ -export const vCallWithParametersParameterParameterCookie = v.union([ - v.string(), - v.null() -]); - -/** - * This is the parameter that goes into the header - */ -export const vCallWithParametersParameterParameterHeader = v.union([ - v.string(), - v.null() -]); - -/** - * This is the parameter that goes into the path - */ -export const vCallWithParametersParameterParameterPath = v.union([ - v.string(), - v.null() -]); - -/** - * api-version should be required in standalone clients - */ -export const vCallWithParametersParameterApiVersion = v.union([ - v.string(), - v.null() -]); - -export const vCallWithParametersParameterFooRefEnum = vModelWithNestedArrayEnumsDataFoo; - -export const vCallWithParametersParameterFooAllOfEnum = vModelWithNestedArrayEnumsDataFoo; +export const vDeleteCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the query params - */ -export const vCallWithParametersParameterCursor = v.union([ - v.string(), - v.null() -]); +export const vGetCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the body - */ -export const vCallWithWeirdParameterNamesData = v.union([ - vModelWithString, - v.null() -]); +export const vHeadCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the cookie - */ -export const vCallWithWeirdParameterNamesParameterParameterCookie = v.union([ - v.string(), - v.null() -]); +export const vOptionsCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the request header - */ -export const vCallWithWeirdParameterNamesParameterParameterHeader = v.union([ - v.string(), - v.null() -]); +export const vPatchCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the path - */ -export const vCallWithWeirdParameterNamesParameterParameterPath1 = v.string(); +export const vPostCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the path - */ -export const vCallWithWeirdParameterNamesParameterParameterPath2 = v.string(); +export const vPutCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the path - */ -export const vCallWithWeirdParameterNamesParameterParameterPath3 = v.string(); +export const vDeleteFooData3 = v.object({ + body: v.optional(v.never()), + headers: v.object({ + 'x-Foo-Bar': vModelWithString + }), + path: v.object({ + foo_param: v.string(), + BarParam: v.string() + }), + query: v.optional(v.never()) +}); -/** - * api-version should be required in standalone clients - */ -export const vCallWithWeirdParameterNamesParameterApiVersion = v.union([ - v.string(), - v.null() -]); +export const vCallWithDescriptionsData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameterWithBreaks: v.optional(v.string()), + parameterWithBackticks: v.optional(v.string()), + parameterWithSlashes: v.optional(v.string()), + parameterWithExpressionPlaceholders: v.optional(v.string()), + parameterWithQuotes: v.optional(v.string()), + parameterWithReservedCharacters: v.optional(v.string()) + })) +}); -/** - * This is the parameter with a reserved keyword - */ -export const vCallWithWeirdParameterNamesParameterDefault = v.string(); +export const vDeprecatedCallData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.object({ + parameter: v.union([ + vDeprecatedModel, + v.null() + ]) + })), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the request query params - */ -export const vCallWithWeirdParameterNamesParameterParameterQuery = v.union([ - v.string(), - v.null() -]); +export const vCallWithParametersData = v.object({ + body: v.union([ + v.object({}), + v.null() + ]), + headers: v.object({ + parameterHeader: v.union([ + v.string(), + v.null() + ]) + }), + path: v.object({ + parameterPath: v.union([ + v.string(), + v.null() + ]), + 'api-version': v.union([ + v.string(), + v.null() + ]) + }), + query: v.object({ + foo_ref_enum: v.optional(vModelWithNestedArrayEnumsDataFoo), + foo_all_of_enum: vModelWithNestedArrayEnumsDataFoo, + cursor: v.union([ + v.string(), + v.null() + ]) + }) +}); -/** - * This is a required parameter - */ -export const vGetCallWithOptionalParamData = vModelWithOneOfEnum; +export const vCallWithWeirdParameterNamesData = v.object({ + body: v.union([ + vModelWithString, + v.null() + ]), + headers: v.object({ + 'parameter.header': v.union([ + v.string(), + v.null() + ]) + }), + path: v.object({ + 'parameter.path.1': v.optional(v.string()), + 'parameter-path-2': v.optional(v.string()), + 'PARAMETER-PATH-3': v.optional(v.string()), + 'api-version': v.union([ + v.string(), + v.null() + ]) + }), + query: v.object({ + default: v.optional(v.string()), + 'parameter-query': v.union([ + v.string(), + v.null() + ]) + }) +}); -/** - * This is an optional parameter - */ -export const vGetCallWithOptionalParamParameterPage = v.number(); +export const vGetCallWithOptionalParamData = v.object({ + body: vModelWithOneOfEnum, + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + page: v.optional(v.number()) + })) +}); -/** - * This is an optional parameter - */ export const vPostCallWithOptionalParamData = v.object({ - offset: v.optional(v.union([ - v.number(), - v.null() - ])) + body: v.optional(v.object({ + offset: v.optional(v.union([ + v.number(), + v.null() + ])) + })), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameter: vPageable + }) }); -/** - * This is a required parameter - */ -export const vPostCallWithOptionalParamParameterParameter = vPageable; - export const vPostCallWithOptionalParamResponse = v.union([ v.number(), v.void() ]); -/** - * A reusable request body - */ -export const vPostApiVbyApiVersionRequestBodyData = vSimpleRequestBody; - -/** - * This is a reusable parameter - */ -export const vPostApiVbyApiVersionRequestBodyParameterParameter = v.string(); - -/** - * A reusable request body - */ -export const vPostApiVbyApiVersionFormDataData = vSimpleFormData; - -/** - * This is a reusable parameter - */ -export const vPostApiVbyApiVersionFormDataParameterParameter = v.string(); - -/** - * This is a simple string with default value - */ -export const vCallWithDefaultParametersParameterParameterString = v.optional(v.union([ - v.optional(v.string(), 'Hello World!'), - v.null() -]), 'Hello World!'); - -/** - * This is a simple number with default value - */ -export const vCallWithDefaultParametersParameterParameterNumber = v.optional(v.union([ - v.optional(v.number(), 123), - v.null() -]), 123); - -/** - * This is a simple boolean with default value - */ -export const vCallWithDefaultParametersParameterParameterBoolean = v.optional(v.union([ - v.optional(v.boolean(), true), - v.null() -]), true); - -/** - * This is a simple enum with default value - */ -export const vCallWithDefaultParametersParameterParameterEnum = v.picklist([ - 'Success', - 'Warning', - 'Error' -]); - -/** - * This is a simple model with default value - */ -export const vCallWithDefaultParametersParameterParameterModel = v.union([ - vModelWithString, - v.null() -]); - -/** - * This is a simple string that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterString = v.optional(v.string(), 'Hello World!'); - -/** - * This is a simple number that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterNumber = v.optional(v.number(), 123); - -/** - * This is a simple boolean that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterBoolean = v.optional(v.boolean(), true); - -/** - * This is a simple enum that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterEnum = v.picklist([ - 'Success', - 'Warning', - 'Error' -]); +export const vPostApiVbyApiVersionRequestBodyData = v.object({ + body: v.optional(vSimpleRequestBody), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameter: v.optional(v.string()) + })) +}); -/** - * This is a simple model that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterModel = vModelWithString; +export const vPostApiVbyApiVersionFormDataData = v.object({ + body: v.optional(vSimpleFormData), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameter: v.optional(v.string()) + })) +}); -/** - * This is a optional string with default - */ -export const vCallToTestOrderOfParamsParameterParameterOptionalStringWithDefault = v.optional(v.string(), 'Hello World!'); +export const vCallWithDefaultParametersData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameterString: v.optional(v.union([ + v.optional(v.string(), 'Hello World!'), + v.null() + ]), 'Hello World!'), + parameterNumber: v.optional(v.union([ + v.optional(v.number(), 123), + v.null() + ]), 123), + parameterBoolean: v.optional(v.union([ + v.optional(v.boolean(), true), + v.null() + ]), true), + parameterEnum: v.optional(v.picklist([ + 'Success', + 'Warning', + 'Error' + ])), + parameterModel: v.optional(v.union([ + vModelWithString, + v.null() + ])) + })) +}); -/** - * This is a optional string with empty default - */ -export const vCallToTestOrderOfParamsParameterParameterOptionalStringWithEmptyDefault = v.optional(v.string(), ''); +export const vCallWithDefaultOptionalParametersData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameterString: v.optional(v.string(), 'Hello World!'), + parameterNumber: v.optional(v.number(), 123), + parameterBoolean: v.optional(v.boolean(), true), + parameterEnum: v.optional(v.picklist([ + 'Success', + 'Warning', + 'Error' + ])), + parameterModel: v.optional(vModelWithString) + })) +}); -/** - * This is a optional string with no default - */ -export const vCallToTestOrderOfParamsParameterParameterOptionalStringWithNoDefault = v.string(); +export const vCallToTestOrderOfParamsData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameterOptionalStringWithDefault: v.optional(v.string(), 'Hello World!'), + parameterOptionalStringWithEmptyDefault: v.optional(v.string(), ''), + parameterOptionalStringWithNoDefault: v.optional(v.string()), + parameterStringWithDefault: v.optional(v.string(), 'Hello World!'), + parameterStringWithEmptyDefault: v.optional(v.string(), ''), + parameterStringWithNoDefault: v.string(), + parameterStringNullableWithNoDefault: v.optional(v.union([ + v.string(), + v.null() + ])), + parameterStringNullableWithDefault: v.optional(v.union([ + v.string(), + v.null() + ]), null) + }) +}); -/** - * This is a string with default - */ -export const vCallToTestOrderOfParamsParameterParameterStringWithDefault = v.optional(v.string(), 'Hello World!'); +export const vDuplicateNameData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string with empty default - */ -export const vCallToTestOrderOfParamsParameterParameterStringWithEmptyDefault = v.optional(v.string(), ''); +export const vDuplicateName2Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string with no default - */ -export const vCallToTestOrderOfParamsParameterParameterStringWithNoDefault = v.string(); +export const vDuplicateName3Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string that can be null with no default - */ -export const vCallToTestOrderOfParamsParameterParameterStringNullableWithNoDefault = v.union([ - v.string(), - v.null() -]); +export const vDuplicateName4Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string that can be null with default - */ -export const vCallToTestOrderOfParamsParameterParameterStringNullableWithDefault = v.optional(v.union([ - v.string(), - v.null() -]), null); +export const vCallWithNoContentResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); /** * Success */ export const vCallWithNoContentResponseResponse = v.void(); +export const vCallWithResponseAndNoContentResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vCallWithResponseAndNoContentResponseResponse = v.union([ v.number(), v.void() ]); +export const vDummyAData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vDummyAResponse = v400; +export const vDummyBData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * Success */ export const vDummyBResponse = v.void(); +export const vCallWithResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vCallWithResponseResponse = vImport; +export const vCallWithDuplicateResponsesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vCallWithDuplicateResponsesResponse = v.union([ v.intersect([ vModelWithBoolean, @@ -1457,6 +1490,13 @@ export const vCallWithDuplicateResponsesResponse = v.union([ vModelWithString ]); +export const vCallWithResponsesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vCallWithResponsesResponse = v.union([ v.object({ '@namespace.string': v.optional(v.pipe(v.string(), v.readonly())), @@ -1467,104 +1507,69 @@ export const vCallWithResponsesResponse = v.union([ vModelThatExtendsExtends ]); -/** - * This is an array parameter that is sent as csv format (comma-separated values) - */ -export const vCollectionFormatParameterParameterArrayCsv = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is an array parameter that is sent as ssv format (space-separated values) - */ -export const vCollectionFormatParameterParameterArraySsv = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is an array parameter that is sent as tsv format (tab-separated values) - */ -export const vCollectionFormatParameterParameterArrayTsv = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is an array parameter that is sent as pipes format (pipe-separated values) - */ -export const vCollectionFormatParameterParameterArrayPipes = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is an array parameter that is sent as multi format (multiple parameter instances) - */ -export const vCollectionFormatParameterParameterArrayMulti = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is a number parameter - */ -export const vTypesParameterId = v.pipe(v.number(), v.integer()); - -/** - * This is a number parameter - */ -export const vTypesParameterParameterNumber = v.optional(v.number(), 123); - -/** - * This is a string parameter - */ -export const vTypesParameterParameterString = v.optional(v.union([ - v.optional(v.string(), 'default'), - v.null() -]), 'default'); - -/** - * This is a boolean parameter - */ -export const vTypesParameterParameterBoolean = v.optional(v.union([ - v.optional(v.boolean(), true), - v.null() -]), true); - -/** - * This is an object parameter - */ -export const vTypesParameterParameterObject = v.optional(v.union([ - v.object({}), - v.null() -]), null); - -/** - * This is an array parameter - */ -export const vTypesParameterParameterArray = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is a dictionary parameter - */ -export const vTypesParameterParameterDictionary = v.union([ - v.object({}), - v.null() -]); +export const vCollectionFormatData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameterArrayCSV: v.union([ + v.array(v.string()), + v.null() + ]), + parameterArraySSV: v.union([ + v.array(v.string()), + v.null() + ]), + parameterArrayTSV: v.union([ + v.array(v.string()), + v.null() + ]), + parameterArrayPipes: v.union([ + v.array(v.string()), + v.null() + ]), + parameterArrayMulti: v.union([ + v.array(v.string()), + v.null() + ]) + }) +}); -/** - * This is an enum parameter - */ -export const vTypesParameterParameterEnum = v.picklist([ - 'Success', - 'Warning', - 'Error' -]); +export const vTypesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.object({ + id: v.optional(v.pipe(v.number(), v.integer())) + })), + query: v.object({ + parameterNumber: v.optional(v.number(), 123), + parameterString: v.optional(v.union([ + v.optional(v.string(), 'default'), + v.null() + ]), 'default'), + parameterBoolean: v.optional(v.union([ + v.optional(v.boolean(), true), + v.null() + ]), true), + parameterObject: v.optional(v.union([ + v.object({}), + v.null() + ]), null), + parameterArray: v.union([ + v.array(v.string()), + v.null() + ]), + parameterDictionary: v.union([ + v.object({}), + v.null() + ]), + parameterEnum: v.picklist([ + 'Success', + 'Warning', + 'Error' + ]) + }) +}); export const vTypesResponse = v.union([ v.number(), @@ -1573,51 +1578,63 @@ export const vTypesResponse = v.union([ v.object({}) ]); -export const vUploadFileData = v.string(); - -/** - * api-version should be required in standalone clients - */ -export const vUploadFileParameterApiVersion = v.union([ - v.string(), - v.null() -]); +export const vUploadFileData = v.object({ + body: v.string(), + headers: v.optional(v.never()), + path: v.object({ + 'api-version': v.union([ + v.string(), + v.null() + ]) + }), + query: v.optional(v.never()) +}); export const vUploadFileResponse = v.boolean(); -export const vFileResponseParameterId = v.string(); - -/** - * api-version should be required in standalone clients - */ -export const vFileResponseParameterApiVersion = v.string(); +export const vFileResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.object({ + id: v.string(), + 'api-version': v.string() + }), + query: v.optional(v.never()) +}); /** * Success */ export const vFileResponseResponse = v.string(); -/** - * Parameter containing object - */ -export const vComplexTypesParameterParameterObject = v.object({ - first: v.optional(v.object({ - second: v.optional(v.object({ - third: v.optional(v.string()) - })) - })) +export const vComplexTypesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameterObject: v.object({ + first: v.optional(v.object({ + second: v.optional(v.object({ + third: v.optional(v.string()) + })) + })) + }), + parameterReference: vModelWithString + }) }); -/** - * Parameter containing reference - */ -export const vComplexTypesParameterParameterReference = vModelWithString; - /** * Successful response */ export const vComplexTypesResponse = v.array(vModelWithString); +export const vMultipartResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * OK */ @@ -1630,76 +1647,102 @@ export const vMultipartResponseResponse = v.object({ }); export const vMultipartRequestData = v.object({ - content: v.optional(v.string()), - data: v.optional(v.union([ - vModelWithString, - v.null() - ])) + body: v.optional(v.object({ + content: v.optional(v.string()), + data: v.optional(v.union([ + vModelWithString, + v.null() + ])) + })), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) }); export const vComplexParamsData = v.object({ - key: v.pipe(v.union([ - v.pipe(v.pipe(v.string(), v.maxLength(64), v.regex(/^[a-zA-Z0-9_]*$/)), v.readonly()), - v.null() - ]), v.readonly()), - name: v.union([ - v.pipe(v.string(), v.maxLength(255)), - v.null() - ]), - enabled: v.optional(v.boolean(), true), - type: v.picklist([ - 'Monkey', - 'Horse', - 'Bird' - ]), - listOfModels: v.optional(v.union([ - v.array(vModelWithString), - v.null() - ])), - listOfStrings: v.optional(v.union([ - v.array(v.string()), - v.null() - ])), - parameters: v.union([ - vModelWithString, - vModelWithEnum, - vModelWithArray, - vModelWithDictionary - ]), - user: v.optional(v.pipe(v.object({ - id: v.optional(v.pipe(v.pipe(v.number(), v.integer()), v.readonly())), - name: v.optional(v.pipe(v.union([ - v.pipe(v.string(), v.readonly()), + body: v.optional(v.object({ + key: v.pipe(v.union([ + v.pipe(v.pipe(v.string(), v.maxLength(64), v.regex(/^[a-zA-Z0-9_]*$/)), v.readonly()), v.null() - ]), v.readonly())) - }), v.readonly())) + ]), v.readonly()), + name: v.union([ + v.pipe(v.string(), v.maxLength(255)), + v.null() + ]), + enabled: v.optional(v.boolean(), true), + type: v.picklist([ + 'Monkey', + 'Horse', + 'Bird' + ]), + listOfModels: v.optional(v.union([ + v.array(vModelWithString), + v.null() + ])), + listOfStrings: v.optional(v.union([ + v.array(v.string()), + v.null() + ])), + parameters: v.union([ + vModelWithString, + vModelWithEnum, + vModelWithArray, + vModelWithDictionary + ]), + user: v.optional(v.pipe(v.object({ + id: v.optional(v.pipe(v.pipe(v.number(), v.integer()), v.readonly())), + name: v.optional(v.pipe(v.union([ + v.pipe(v.string(), v.readonly()), + v.null() + ]), v.readonly())) + }), v.readonly())) + })), + headers: v.optional(v.never()), + path: v.object({ + id: v.pipe(v.number(), v.integer()), + 'api-version': v.string() + }), + query: v.optional(v.never()) }); -export const vComplexParamsParameterId = v.pipe(v.number(), v.integer()); - -/** - * api-version should be required in standalone clients - */ -export const vComplexParamsParameterApiVersion = v.string(); - /** * Success */ export const vComplexParamsResponse = vModelWithString; -/** - * Status code to return - */ -export const vTestErrorCodeParameterStatus = v.pipe(v.number(), v.integer()); +export const vCallWithResultFromHeaderData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * Dummy input param - */ -export const vNonAsciiæøåÆøÅöôêÊ字符串ParameterNonAsciiParamæøåÆøÅöôêÊ = v.pipe(v.number(), v.integer()); +export const vTestErrorCodeData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + status: v.pipe(v.number(), v.integer()) + }) +}); + +export const vNonAsciiæøåÆøÅöôêÊ字符串Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + 'nonAsciiParamæøåÆØÅöôêÊ': v.pipe(v.number(), v.integer()) + }) +}); /** * Successful response */ export const vNonAsciiæøåÆøÅöôêÊ字符串Response = v.array(vNonAsciiStringæøåÆøÅöôêÊ字符串); -export const vPutWithFormUrlEncodedData = vArrayWithStrings; \ No newline at end of file +export const vPutWithFormUrlEncodedData = v.object({ + body: vArrayWithStrings, + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); \ No newline at end of file diff --git a/packages/openapi-ts-tests/test/__snapshots__/3.1.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts b/packages/openapi-ts-tests/test/__snapshots__/3.1.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts index 17026f2e5..0e3e4580f 100644 --- a/packages/openapi-ts-tests/test/__snapshots__/3.1.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts +++ b/packages/openapi-ts-tests/test/__snapshots__/3.1.x/plugins/@hey-api/transformers/type-format-valibot/valibot.gen.ts @@ -12,6 +12,13 @@ export const vBar = v.object({ foo: v.pipe(v.number(), v.integer()) }); +export const vPostFooData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * OK */ diff --git a/packages/openapi-ts-tests/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts b/packages/openapi-ts-tests/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts index 083f665bc..7dadf458d 100644 --- a/packages/openapi-ts-tests/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts +++ b/packages/openapi-ts-tests/test/__snapshots__/3.1.x/plugins/valibot/default/valibot.gen.ts @@ -1074,388 +1074,421 @@ export const vSimpleRequestBody = vModelWithString; */ export const vSimpleFormData = vModelWithString; -export const vImportData = v.union([ - vModelWithReadOnlyAndWriteOnly, - vModelWithArrayReadOnlyAndWriteOnly -]); +export const vExportData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + +export const vPatchApiVbyApiVersionNoTagData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + +export const vImportData = v.object({ + body: v.union([ + vModelWithReadOnlyAndWriteOnly, + vModelWithArrayReadOnlyAndWriteOnly + ]), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); export const vImportResponse = v.union([ vModelFromZendesk, vModelWithReadOnlyAndWriteOnly ]); +export const vFooWowData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + +export const vApiVVersionODataControllerCountData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * Success */ export const vApiVVersionODataControllerCountResponse = vModelFromZendesk; -/** - * foo in method - */ -export const vGetApiVbyApiVersionSimpleOperationParameterFooParam = v.union([ - v.string(), - v.pipe(v.string(), v.uuid()) -]); +export const vGetApiVbyApiVersionSimpleOperationData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.object({ + foo_param: v.union([ + v.string(), + v.pipe(v.string(), v.uuid()) + ]) + }), + query: v.optional(v.never()) +}); /** * Response is a simple number */ export const vGetApiVbyApiVersionSimpleOperationResponse = v.number(); -/** - * foo in method - */ -export const vDeleteFooParameterFooParam = v.string(); - -/** - * bar in method - */ -export const vDeleteFooParameterBarParam = v.string(); - -/** - * Parameter with illegal characters - */ -export const vDeleteFooParameterXFooBar = vModelWithString; - -/** - * Testing multiline comments in string: First line - * Second line - * - * Fourth line - */ -export const vCallWithDescriptionsParameterParameterWithBreaks = v.string(); - -/** - * Testing backticks in string: `backticks` and ```multiple backticks``` should work - */ -export const vCallWithDescriptionsParameterParameterWithBackticks = v.string(); - -/** - * Testing slashes in string: \backwards\\\ and /forwards/// should work - */ -export const vCallWithDescriptionsParameterParameterWithSlashes = v.string(); - -/** - * Testing expression placeholders in string: ${expression} should work - */ -export const vCallWithDescriptionsParameterParameterWithExpressionPlaceholders = v.string(); - -/** - * Testing quotes in string: 'single quote''' and "double quotes""" should work - */ -export const vCallWithDescriptionsParameterParameterWithQuotes = v.string(); - -/** - * Testing reserved characters in string: * inline * and ** inline ** should work - */ -export const vCallWithDescriptionsParameterParameterWithReservedCharacters = v.string(); - -/** - * This parameter is deprecated - * @deprecated - */ -export const vDeprecatedCallParameterParameter = v.union([ - vDeprecatedModel, - v.null() -]); - -/** - * This is the parameter that goes into the body - */ -export const vCallWithParametersData = v.union([ - v.object({}), - v.null() -]); - -/** - * This is the parameter that goes into the cookie - */ -export const vCallWithParametersParameterParameterCookie = v.union([ - v.string(), - v.null() -]); - -/** - * This is the parameter that goes into the header - */ -export const vCallWithParametersParameterParameterHeader = v.union([ - v.string(), - v.null() -]); - -/** - * This is the parameter that goes into the path - */ -export const vCallWithParametersParameterParameterPath = v.union([ - v.string(), - v.null() -]); - -/** - * api-version should be required in standalone clients - */ -export const vCallWithParametersParameterApiVersion = v.union([ - v.string(), - v.null() -]); - -export const vCallWithParametersParameterFooRefEnum = vModelWithNestedArrayEnumsDataFoo; - -export const vCallWithParametersParameterFooAllOfEnum = vModelWithNestedArrayEnumsDataFoo; +export const vDeleteCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the query params - */ -export const vCallWithParametersParameterCursor = v.union([ - v.string(), - v.null() -]); +export const vGetCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the body - */ -export const vCallWithWeirdParameterNamesData = v.union([ - vModelWithString, - v.null() -]); +export const vHeadCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the cookie - */ -export const vCallWithWeirdParameterNamesParameterParameterCookie = v.union([ - v.string(), - v.null() -]); +export const vOptionsCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the request header - */ -export const vCallWithWeirdParameterNamesParameterParameterHeader = v.union([ - v.string(), - v.null() -]); +export const vPatchCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the path - */ -export const vCallWithWeirdParameterNamesParameterParameterPath1 = v.string(); +export const vPostCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the path - */ -export const vCallWithWeirdParameterNamesParameterParameterPath2 = v.string(); +export const vPutCallWithoutParametersAndResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the path - */ -export const vCallWithWeirdParameterNamesParameterParameterPath3 = v.string(); +export const vDeleteFooData3 = v.object({ + body: v.optional(v.never()), + headers: v.object({ + 'x-Foo-Bar': vModelWithString + }), + path: v.object({ + foo_param: v.string(), + BarParam: v.string() + }), + query: v.optional(v.never()) +}); -/** - * api-version should be required in standalone clients - */ -export const vCallWithWeirdParameterNamesParameterApiVersion = v.union([ - v.string(), - v.null() -]); +export const vCallWithDescriptionsData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameterWithBreaks: v.optional(v.string()), + parameterWithBackticks: v.optional(v.string()), + parameterWithSlashes: v.optional(v.string()), + parameterWithExpressionPlaceholders: v.optional(v.string()), + parameterWithQuotes: v.optional(v.string()), + parameterWithReservedCharacters: v.optional(v.string()) + })) +}); -/** - * This is the parameter with a reserved keyword - */ -export const vCallWithWeirdParameterNamesParameterDefault = v.string(); +export const vDeprecatedCallData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.object({ + parameter: v.union([ + vDeprecatedModel, + v.null() + ]) + })), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is the parameter that goes into the request query params - */ -export const vCallWithWeirdParameterNamesParameterParameterQuery = v.union([ - v.string(), - v.null() -]); +export const vCallWithParametersData = v.object({ + body: v.union([ + v.object({}), + v.null() + ]), + headers: v.object({ + parameterHeader: v.union([ + v.string(), + v.null() + ]) + }), + path: v.object({ + parameterPath: v.union([ + v.string(), + v.null() + ]), + 'api-version': v.union([ + v.string(), + v.null() + ]) + }), + query: v.object({ + foo_ref_enum: v.optional(vModelWithNestedArrayEnumsDataFoo), + foo_all_of_enum: vModelWithNestedArrayEnumsDataFoo, + cursor: v.union([ + v.string(), + v.null() + ]) + }) +}); -/** - * This is a required parameter - */ -export const vGetCallWithOptionalParamData = vModelWithOneOfEnum; +export const vCallWithWeirdParameterNamesData = v.object({ + body: v.union([ + vModelWithString, + v.null() + ]), + headers: v.object({ + 'parameter.header': v.union([ + v.string(), + v.null() + ]) + }), + path: v.object({ + 'parameter.path.1': v.optional(v.string()), + 'parameter-path-2': v.optional(v.string()), + 'PARAMETER-PATH-3': v.optional(v.string()), + 'api-version': v.union([ + v.string(), + v.null() + ]) + }), + query: v.object({ + default: v.optional(v.string()), + 'parameter-query': v.union([ + v.string(), + v.null() + ]) + }) +}); -/** - * This is an optional parameter - */ -export const vGetCallWithOptionalParamParameterPage = v.number(); +export const vGetCallWithOptionalParamData = v.object({ + body: vModelWithOneOfEnum, + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + page: v.optional(v.number()) + })) +}); -/** - * This is an optional parameter - */ export const vPostCallWithOptionalParamData = v.object({ - offset: v.optional(v.union([ - v.number(), - v.null() - ])) + body: v.optional(v.object({ + offset: v.optional(v.union([ + v.number(), + v.null() + ])) + })), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameter: vPageable + }) }); -/** - * This is a required parameter - */ -export const vPostCallWithOptionalParamParameterParameter = vPageable; - export const vPostCallWithOptionalParamResponse = v.union([ v.number(), v.void() ]); -/** - * A reusable request body - */ -export const vPostApiVbyApiVersionRequestBodyData = vSimpleRequestBody; - -/** - * This is a reusable parameter - */ -export const vPostApiVbyApiVersionRequestBodyParameterParameter = v.string(); - -/** - * A reusable request body - */ -export const vPostApiVbyApiVersionFormDataData = vSimpleFormData; - -/** - * This is a reusable parameter - */ -export const vPostApiVbyApiVersionFormDataParameterParameter = v.string(); - -/** - * This is a simple string with default value - */ -export const vCallWithDefaultParametersParameterParameterString = v.optional(v.union([ - v.optional(v.string(), 'Hello World!'), - v.null() -]), 'Hello World!'); - -/** - * This is a simple number with default value - */ -export const vCallWithDefaultParametersParameterParameterNumber = v.optional(v.union([ - v.optional(v.number(), 123), - v.null() -]), 123); - -/** - * This is a simple boolean with default value - */ -export const vCallWithDefaultParametersParameterParameterBoolean = v.optional(v.union([ - v.optional(v.boolean(), true), - v.null() -]), true); - -/** - * This is a simple enum with default value - */ -export const vCallWithDefaultParametersParameterParameterEnum = v.picklist([ - 'Success', - 'Warning', - 'Error' -]); - -/** - * This is a simple model with default value - */ -export const vCallWithDefaultParametersParameterParameterModel = v.union([ - vModelWithString, - v.null() -]); - -/** - * This is a simple string that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterString = v.optional(v.string(), 'Hello World!'); - -/** - * This is a simple number that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterNumber = v.optional(v.number(), 123); - -/** - * This is a simple boolean that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterBoolean = v.optional(v.boolean(), true); - -/** - * This is a simple enum that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterEnum = v.picklist([ - 'Success', - 'Warning', - 'Error' -]); +export const vPostApiVbyApiVersionRequestBodyData = v.object({ + body: v.optional(vSimpleRequestBody), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameter: v.optional(v.string()) + })) +}); -/** - * This is a simple model that is optional with default value - */ -export const vCallWithDefaultOptionalParametersParameterParameterModel = vModelWithString; +export const vPostApiVbyApiVersionFormDataData = v.object({ + body: v.optional(vSimpleFormData), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameter: v.optional(v.string()) + })) +}); -/** - * This is a optional string with default - */ -export const vCallToTestOrderOfParamsParameterParameterOptionalStringWithDefault = v.optional(v.string(), 'Hello World!'); +export const vCallWithDefaultParametersData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameterString: v.optional(v.union([ + v.optional(v.string(), 'Hello World!'), + v.null() + ]), 'Hello World!'), + parameterNumber: v.optional(v.union([ + v.optional(v.number(), 123), + v.null() + ]), 123), + parameterBoolean: v.optional(v.union([ + v.optional(v.boolean(), true), + v.null() + ]), true), + parameterEnum: v.optional(v.picklist([ + 'Success', + 'Warning', + 'Error' + ])), + parameterModel: v.optional(v.union([ + vModelWithString, + v.null() + ])) + })) +}); -/** - * This is a optional string with empty default - */ -export const vCallToTestOrderOfParamsParameterParameterOptionalStringWithEmptyDefault = v.optional(v.string(), ''); +export const vCallWithDefaultOptionalParametersData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + parameterString: v.optional(v.string(), 'Hello World!'), + parameterNumber: v.optional(v.number(), 123), + parameterBoolean: v.optional(v.boolean(), true), + parameterEnum: v.optional(v.picklist([ + 'Success', + 'Warning', + 'Error' + ])), + parameterModel: v.optional(vModelWithString) + })) +}); -/** - * This is a optional string with no default - */ -export const vCallToTestOrderOfParamsParameterParameterOptionalStringWithNoDefault = v.string(); +export const vCallToTestOrderOfParamsData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameterOptionalStringWithDefault: v.optional(v.string(), 'Hello World!'), + parameterOptionalStringWithEmptyDefault: v.optional(v.string(), ''), + parameterOptionalStringWithNoDefault: v.optional(v.string()), + parameterStringWithDefault: v.optional(v.string(), 'Hello World!'), + parameterStringWithEmptyDefault: v.optional(v.string(), ''), + parameterStringWithNoDefault: v.string(), + parameterStringNullableWithNoDefault: v.optional(v.union([ + v.string(), + v.null() + ])), + parameterStringNullableWithDefault: v.optional(v.union([ + v.string(), + v.null() + ]), null) + }) +}); -/** - * This is a string with default - */ -export const vCallToTestOrderOfParamsParameterParameterStringWithDefault = v.optional(v.string(), 'Hello World!'); +export const vDuplicateNameData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string with empty default - */ -export const vCallToTestOrderOfParamsParameterParameterStringWithEmptyDefault = v.optional(v.string(), ''); +export const vDuplicateName2Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string with no default - */ -export const vCallToTestOrderOfParamsParameterParameterStringWithNoDefault = v.string(); +export const vDuplicateName3Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string that can be null with no default - */ -export const vCallToTestOrderOfParamsParameterParameterStringNullableWithNoDefault = v.union([ - v.string(), - v.null() -]); +export const vDuplicateName4Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * This is a string that can be null with default - */ -export const vCallToTestOrderOfParamsParameterParameterStringNullableWithDefault = v.optional(v.union([ - v.string(), - v.null() -]), null); +export const vCallWithNoContentResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); /** * Success */ export const vCallWithNoContentResponseResponse = v.void(); +export const vCallWithResponseAndNoContentResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vCallWithResponseAndNoContentResponseResponse = v.union([ v.number(), v.void() ]); +export const vDummyAData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vDummyAResponse = v400; +export const vDummyBData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * Success */ export const vDummyBResponse = v.void(); +export const vCallWithResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vCallWithResponseResponse = vImport; +export const vCallWithDuplicateResponsesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vCallWithDuplicateResponsesResponse = v.union([ v.intersect([ vModelWithBoolean, @@ -1464,6 +1497,13 @@ export const vCallWithDuplicateResponsesResponse = v.union([ vModelWithString ]); +export const vCallWithResponsesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + export const vCallWithResponsesResponse = v.union([ v.object({ '@namespace.string': v.optional(v.pipe(v.string(), v.readonly())), @@ -1474,105 +1514,70 @@ export const vCallWithResponsesResponse = v.union([ vModelThatExtendsExtends ]); -/** - * This is an array parameter that is sent as csv format (comma-separated values) - */ -export const vCollectionFormatParameterParameterArrayCsv = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is an array parameter that is sent as ssv format (space-separated values) - */ -export const vCollectionFormatParameterParameterArraySsv = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is an array parameter that is sent as tsv format (tab-separated values) - */ -export const vCollectionFormatParameterParameterArrayTsv = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is an array parameter that is sent as pipes format (pipe-separated values) - */ -export const vCollectionFormatParameterParameterArrayPipes = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is an array parameter that is sent as multi format (multiple parameter instances) - */ -export const vCollectionFormatParameterParameterArrayMulti = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is a number parameter - */ -export const vTypesParameterId = v.pipe(v.number(), v.integer()); - -/** - * This is a number parameter - */ -export const vTypesParameterParameterNumber = v.optional(v.number(), 123); - -/** - * This is a string parameter - */ -export const vTypesParameterParameterString = v.optional(v.union([ - v.optional(v.string(), 'default'), - v.null() -]), 'default'); - -/** - * This is a boolean parameter - */ -export const vTypesParameterParameterBoolean = v.optional(v.union([ - v.optional(v.boolean(), true), - v.null() -]), true); - -/** - * This is an object parameter - */ -export const vTypesParameterParameterObject = v.optional(v.union([ - v.object({}), - v.null() -]), null); - -/** - * This is an array parameter - */ -export const vTypesParameterParameterArray = v.union([ - v.array(v.string()), - v.null() -]); - -/** - * This is a dictionary parameter - */ -export const vTypesParameterParameterDictionary = v.union([ - v.object({}), - v.null() -]); +export const vCollectionFormatData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameterArrayCSV: v.union([ + v.array(v.string()), + v.null() + ]), + parameterArraySSV: v.union([ + v.array(v.string()), + v.null() + ]), + parameterArrayTSV: v.union([ + v.array(v.string()), + v.null() + ]), + parameterArrayPipes: v.union([ + v.array(v.string()), + v.null() + ]), + parameterArrayMulti: v.union([ + v.array(v.string()), + v.null() + ]) + }) +}); -/** - * This is an enum parameter - */ -export const vTypesParameterParameterEnum = v.union([ - v.literal('Success'), - v.literal('Warning'), - v.literal('Error'), - v.null() -]); +export const vTypesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.object({ + id: v.optional(v.pipe(v.number(), v.integer())) + })), + query: v.object({ + parameterNumber: v.optional(v.number(), 123), + parameterString: v.optional(v.union([ + v.optional(v.string(), 'default'), + v.null() + ]), 'default'), + parameterBoolean: v.optional(v.union([ + v.optional(v.boolean(), true), + v.null() + ]), true), + parameterObject: v.optional(v.union([ + v.object({}), + v.null() + ]), null), + parameterArray: v.union([ + v.array(v.string()), + v.null() + ]), + parameterDictionary: v.union([ + v.object({}), + v.null() + ]), + parameterEnum: v.union([ + v.literal('Success'), + v.literal('Warning'), + v.literal('Error'), + v.null() + ]) + }) +}); export const vTypesResponse = v.union([ v.number(), @@ -1581,51 +1586,63 @@ export const vTypesResponse = v.union([ v.object({}) ]); -export const vUploadFileData = v.string(); - -/** - * api-version should be required in standalone clients - */ -export const vUploadFileParameterApiVersion = v.union([ - v.string(), - v.null() -]); +export const vUploadFileData = v.object({ + body: v.string(), + headers: v.optional(v.never()), + path: v.object({ + 'api-version': v.union([ + v.string(), + v.null() + ]) + }), + query: v.optional(v.never()) +}); export const vUploadFileResponse = v.boolean(); -export const vFileResponseParameterId = v.string(); - -/** - * api-version should be required in standalone clients - */ -export const vFileResponseParameterApiVersion = v.string(); +export const vFileResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.object({ + id: v.string(), + 'api-version': v.string() + }), + query: v.optional(v.never()) +}); /** * Success */ export const vFileResponseResponse = v.string(); -/** - * Parameter containing object - */ -export const vComplexTypesParameterParameterObject = v.object({ - first: v.optional(v.object({ - second: v.optional(v.object({ - third: v.optional(v.string()) - })) - })) +export const vComplexTypesData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + parameterObject: v.object({ + first: v.optional(v.object({ + second: v.optional(v.object({ + third: v.optional(v.string()) + })) + })) + }), + parameterReference: vModelWithString + }) }); -/** - * Parameter containing reference - */ -export const vComplexTypesParameterParameterReference = vModelWithString; - /** * Successful response */ export const vComplexTypesResponse = v.array(vModelWithString); +export const vMultipartResponseData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); + /** * OK */ @@ -1638,76 +1655,102 @@ export const vMultipartResponseResponse = v.object({ }); export const vMultipartRequestData = v.object({ - content: v.optional(v.string()), - data: v.optional(v.union([ - vModelWithString, - v.null() - ])) + body: v.optional(v.object({ + content: v.optional(v.string()), + data: v.optional(v.union([ + vModelWithString, + v.null() + ])) + })), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) }); export const vComplexParamsData = v.object({ - key: v.pipe(v.union([ - v.pipe(v.pipe(v.string(), v.maxLength(64), v.regex(/^[a-zA-Z0-9_]*$/)), v.readonly()), - v.null() - ]), v.readonly()), - name: v.union([ - v.pipe(v.string(), v.maxLength(255)), - v.null() - ]), - enabled: v.optional(v.boolean(), true), - type: v.picklist([ - 'Monkey', - 'Horse', - 'Bird' - ]), - listOfModels: v.optional(v.union([ - v.array(vModelWithString), - v.null() - ])), - listOfStrings: v.optional(v.union([ - v.array(v.string()), - v.null() - ])), - parameters: v.union([ - vModelWithString, - vModelWithEnum, - vModelWithArray, - vModelWithDictionary - ]), - user: v.optional(v.pipe(v.object({ - id: v.optional(v.pipe(v.pipe(v.number(), v.integer()), v.readonly())), - name: v.optional(v.pipe(v.union([ - v.pipe(v.string(), v.readonly()), + body: v.optional(v.object({ + key: v.pipe(v.union([ + v.pipe(v.pipe(v.string(), v.maxLength(64), v.regex(/^[a-zA-Z0-9_]*$/)), v.readonly()), + v.null() + ]), v.readonly()), + name: v.union([ + v.pipe(v.string(), v.maxLength(255)), + v.null() + ]), + enabled: v.optional(v.boolean(), true), + type: v.picklist([ + 'Monkey', + 'Horse', + 'Bird' + ]), + listOfModels: v.optional(v.union([ + v.array(vModelWithString), + v.null() + ])), + listOfStrings: v.optional(v.union([ + v.array(v.string()), v.null() - ]), v.readonly())) - }), v.readonly())) + ])), + parameters: v.union([ + vModelWithString, + vModelWithEnum, + vModelWithArray, + vModelWithDictionary + ]), + user: v.optional(v.pipe(v.object({ + id: v.optional(v.pipe(v.pipe(v.number(), v.integer()), v.readonly())), + name: v.optional(v.pipe(v.union([ + v.pipe(v.string(), v.readonly()), + v.null() + ]), v.readonly())) + }), v.readonly())) + })), + headers: v.optional(v.never()), + path: v.object({ + id: v.pipe(v.number(), v.integer()), + 'api-version': v.string() + }), + query: v.optional(v.never()) }); -export const vComplexParamsParameterId = v.pipe(v.number(), v.integer()); - -/** - * api-version should be required in standalone clients - */ -export const vComplexParamsParameterApiVersion = v.string(); - /** * Success */ export const vComplexParamsResponse = vModelWithString; -/** - * Status code to return - */ -export const vTestErrorCodeParameterStatus = v.pipe(v.number(), v.integer()); +export const vCallWithResultFromHeaderData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); -/** - * Dummy input param - */ -export const vNonAsciiæøåÆøÅöôêÊ字符串ParameterNonAsciiParamæøåÆøÅöôêÊ = v.pipe(v.number(), v.integer()); +export const vTestErrorCodeData = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + status: v.pipe(v.number(), v.integer()) + }) +}); + +export const vNonAsciiæøåÆøÅöôêÊ字符串Data = v.object({ + body: v.optional(v.never()), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.object({ + 'nonAsciiParamæøåÆØÅöôêÊ': v.pipe(v.number(), v.integer()) + }) +}); /** * Successful response */ export const vNonAsciiæøåÆøÅöôêÊ字符串Response = v.array(vNonAsciiStringæøåÆøÅöôêÊ字符串); -export const vPutWithFormUrlEncodedData = vArrayWithStrings; \ No newline at end of file +export const vPutWithFormUrlEncodedData = v.object({ + body: vArrayWithStrings, + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); \ No newline at end of file diff --git a/packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators-metadata/valibot.gen.ts b/packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators-metadata/valibot.gen.ts index d8c3985ca..8fc6ff3ba 100644 --- a/packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators-metadata/valibot.gen.ts +++ b/packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators-metadata/valibot.gen.ts @@ -5,23 +5,31 @@ import * as v from 'valibot'; /** * This is Bar schema. */ -export const vBar: v.GenericSchema = v.object({ +export const vBar: v.GenericSchema = v.pipe(v.object({ foo: v.optional(v.lazy(() => { return vFoo; })) -}); +}), v.metadata({ + description: 'This is Bar schema.' +})); /** * This is Foo schema. */ export const vFoo: v.GenericSchema = v.optional(v.union([ v.object({ - foo: v.optional(v.pipe(v.string(), v.regex(/^\d{3}-\d{2}-\d{4}$/))), + foo: v.optional(v.pipe(v.pipe(v.string(), v.regex(/^\d{3}-\d{2}-\d{4}$/)), v.metadata({ + description: 'This is foo property.' + }))), bar: v.optional(vBar), - baz: v.optional(v.array(v.lazy(() => { + baz: v.optional(v.pipe(v.array(v.lazy(() => { return vFoo; + })), v.metadata({ + description: 'This is baz property.' }))), - qux: v.optional(v.pipe(v.number(), v.integer(), v.gtValue(0)), 0) + qux: v.optional(v.pipe(v.pipe(v.number(), v.integer(), v.gtValue(0)), v.metadata({ + description: 'This is qux property.' + })), 0) }), v.null() ]), null); @@ -35,29 +43,36 @@ export const vQux = v.record(v.string(), v.object({ /** * This is Foo parameter. */ -export const vFoo2 = v.string(); +export const vFoo2 = v.pipe(v.string(), v.metadata({ + description: 'This is Foo parameter.' +})); export const vFoo3 = v.object({ foo: v.optional(vBar) }); export const vPatchFooData = v.object({ - foo: v.optional(v.string()) -}); - -/** - * This is Foo parameter. - */ -export const vPatchFooParameterFoo = v.string(); - -export const vPatchFooParameterBar = vBar; - -export const vPatchFooParameterBaz = v.object({ - baz: v.optional(v.string()) + body: v.object({ + foo: v.optional(v.string()) + }), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + foo: v.optional(v.pipe(v.string(), v.metadata({ + description: 'This is Foo parameter.' + }))), + bar: v.optional(vBar), + baz: v.optional(v.object({ + baz: v.optional(v.string()) + })), + qux: v.optional(v.pipe(v.string(), v.isoDate())), + quux: v.optional(v.pipe(v.string(), v.isoTimestamp())) + })) }); -export const vPatchFooParameterQux = v.pipe(v.string(), v.isoDate()); - -export const vPatchFooParameterQuux = v.pipe(v.string(), v.isoTimestamp()); - -export const vPostFooData = vFoo3; \ No newline at end of file +export const vPostFooData = v.object({ + body: vFoo3, + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); \ No newline at end of file diff --git a/packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators/valibot.gen.ts b/packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators/valibot.gen.ts index d8c3985ca..dfab3ebca 100644 --- a/packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators/valibot.gen.ts +++ b/packages/openapi-ts-tests/test/__snapshots__/3.1.x/validators/valibot.gen.ts @@ -42,22 +42,25 @@ export const vFoo3 = v.object({ }); export const vPatchFooData = v.object({ - foo: v.optional(v.string()) -}); - -/** - * This is Foo parameter. - */ -export const vPatchFooParameterFoo = v.string(); - -export const vPatchFooParameterBar = vBar; - -export const vPatchFooParameterBaz = v.object({ - baz: v.optional(v.string()) + body: v.object({ + foo: v.optional(v.string()) + }), + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.object({ + foo: v.optional(v.string()), + bar: v.optional(vBar), + baz: v.optional(v.object({ + baz: v.optional(v.string()) + })), + qux: v.optional(v.pipe(v.string(), v.isoDate())), + quux: v.optional(v.pipe(v.string(), v.isoTimestamp())) + })) }); -export const vPatchFooParameterQux = v.pipe(v.string(), v.isoDate()); - -export const vPatchFooParameterQuux = v.pipe(v.string(), v.isoTimestamp()); - -export const vPostFooData = vFoo3; \ No newline at end of file +export const vPostFooData = v.object({ + body: vFoo3, + headers: v.optional(v.never()), + path: v.optional(v.never()), + query: v.optional(v.never()) +}); \ No newline at end of file diff --git a/packages/openapi-ts-tests/test/openapi-ts.config.ts b/packages/openapi-ts-tests/test/openapi-ts.config.ts index ab7c6563e..c6cdbcf4f 100644 --- a/packages/openapi-ts-tests/test/openapi-ts.config.ts +++ b/packages/openapi-ts-tests/test/openapi-ts.config.ts @@ -60,7 +60,7 @@ export default defineConfig(() => { // 'invalid', // 'servers-entry.yaml', // ), - path: path.resolve(__dirname, 'spec', '3.1.x', 'content-types.yaml'), + path: path.resolve(__dirname, 'spec', '3.1.x', 'full.yaml'), // path: path.resolve(__dirname, 'spec', 'v3-transforms.json'), // path: 'http://localhost:4000/', // path: 'https://get.heyapi.dev/', @@ -127,7 +127,7 @@ export default defineConfig(() => { // throwOnError: true, // transformer: '@hey-api/transformers', transformer: true, - validator: 'zod', + validator: 'valibot', }, { // bigInt: true, @@ -171,9 +171,20 @@ export default defineConfig(() => { // }, }, { + // case: 'SCREAMING_SNAKE_CASE', // comments: false, + definitions: 'z{{name}}Definition', // exportFromIndex: true, + metadata: true, name: 'valibot', + requests: { + // case: 'SCREAMING_SNAKE_CASE', + name: 'z{{name}}TestData', + }, + responses: { + // case: 'snake_case', + name: 'z{{name}}TestResponse', + }, }, { // case: 'snake_case', diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/validator.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/validator.ts index 29dbc1793..8eba57e08 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/validator.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/validator.ts @@ -1,9 +1,7 @@ import { compiler } from '../../../compiler'; import type { IR } from '../../../ir/types'; -import { operationIrRef } from '../../shared/utils/ref'; import type { Plugin } from '../../types'; import { valibotId } from '../../valibot/constants'; -import { nameTransformer } from '../../valibot/plugin'; import { zodId } from '../../zod/plugin'; import { sdkId } from './constants'; import type { Config } from './types'; @@ -23,15 +21,12 @@ const valibotResponseValidator = ({ }) => { const file = plugin.context.file({ id: sdkId })!; + const responses = plugin.getPlugin('valibot')?.config.responses; const identifierSchema = plugin.context.file({ id: valibotId })!.identifier({ - $ref: operationIrRef({ - case: 'camelCase', - config: plugin.context.config, - id: operation.id, - type: 'response', - }), + // TODO: refactor for better cross-plugin compatibility + $ref: `#/valibot-response/${operation.id}`, // TODO: refactor to not have to define nameTransformer - nameTransformer, + nameTransformer: typeof responses === 'object' ? responses.name : undefined, namespace: 'value', }); diff --git a/packages/openapi-ts/src/plugins/valibot/config.ts b/packages/openapi-ts/src/plugins/valibot/config.ts index 68b19c163..4edaa2baa 100644 --- a/packages/openapi-ts/src/plugins/valibot/config.ts +++ b/packages/openapi-ts/src/plugins/valibot/config.ts @@ -1,17 +1,59 @@ import { definePluginConfig } from '../shared/utils/config'; import type { Plugin } from '../types'; import { handler } from './plugin'; -import type { Config } from './types'; +import type { Config, ResolvedConfig } from './types'; -export const defaultConfig: Plugin.Config = { +export const defaultConfig: Plugin.Config = { config: { + case: 'camelCase', comments: true, exportFromIndex: false, + metadata: false, }, handler, handlerLegacy: () => {}, name: 'valibot', output: 'valibot', + resolveConfig: (plugin, context) => { + plugin.config.definitions = context.valueToObject({ + defaultValue: { + case: plugin.config.case ?? 'camelCase', + enabled: true, + name: 'v{{name}}', + }, + mappers: { + boolean: (enabled) => ({ enabled }), + string: (name) => ({ enabled: true, name }), + }, + value: plugin.config.definitions, + }); + + plugin.config.requests = context.valueToObject({ + defaultValue: { + case: plugin.config.case ?? 'camelCase', + enabled: true, + name: 'v{{name}}Data', + }, + mappers: { + boolean: (enabled) => ({ enabled }), + string: (name) => ({ enabled: true, name }), + }, + value: plugin.config.requests, + }); + + plugin.config.responses = context.valueToObject({ + defaultValue: { + case: plugin.config.case ?? 'camelCase', + enabled: true, + name: 'v{{name}}Response', + }, + mappers: { + boolean: (enabled) => ({ enabled }), + string: (name) => ({ enabled: true, name }), + }, + value: plugin.config.responses, + }); + }, tags: ['validator'], }; diff --git a/packages/openapi-ts/src/plugins/valibot/plugin.ts b/packages/openapi-ts/src/plugins/valibot/plugin.ts index 4db71aaad..f4c9510e5 100644 --- a/packages/openapi-ts/src/plugins/valibot/plugin.ts +++ b/packages/openapi-ts/src/plugins/valibot/plugin.ts @@ -1,15 +1,17 @@ import ts from 'typescript'; import { compiler } from '../../compiler'; +import type { Identifier } from '../../generate/files'; import { operationResponsesMap } from '../../ir/operation'; +import { hasParameterGroupObjectRequired } from '../../ir/parameter'; import { deduplicateSchema } from '../../ir/schema'; import type { IR } from '../../ir/types'; +import type { StringCase } from '../../types/config'; import { numberRegExp } from '../../utils/regexp'; -import { operationIrRef } from '../shared/utils/ref'; import { createSchemaComment } from '../shared/utils/schema'; import type { Plugin } from '../types'; import { identifiers, valibotId } from './constants'; -import type { Config } from './types'; +import type { ResolvedConfig } from './types'; interface SchemaWithType['type']> extends Omit { @@ -19,10 +21,10 @@ interface SchemaWithType['type']> interface State { circularReferenceTracker: Set; hasCircularReference: boolean; + nameCase: StringCase; + nameTransformer: string | ((name: string) => string); } -export const nameTransformer = (name: string) => `v-${name}`; - const pipesToExpression = (pipes: Array) => { if (pipes.length === 1) { return pipes[0]!; @@ -43,7 +45,7 @@ const arrayTypeToValibotSchema = ({ schema, state, }: { - plugin: Plugin.Instance; + plugin: Plugin.Instance; schema: SchemaWithType<'array'>; state: State; }): ts.CallExpression => { @@ -369,7 +371,7 @@ const objectTypeToValibotSchema = ({ schema, state, }: { - plugin: Plugin.Instance; + plugin: Plugin.Instance; schema: SchemaWithType<'object'>; state: State; }): { @@ -601,7 +603,7 @@ const tupleTypeToValibotSchema = ({ schema, state, }: { - plugin: Plugin.Instance; + plugin: Plugin.Instance; schema: SchemaWithType<'tuple'>; state: State; }) => { @@ -703,7 +705,7 @@ const schemaTypeToValibotSchema = ({ schema, state, }: { - plugin: Plugin.Instance; + plugin: Plugin.Instance; schema: IR.SchemaObject; state: State; }): { @@ -797,65 +799,154 @@ const operationToValibotSchema = ({ state, }: { operation: IR.OperationObject; - plugin: Plugin.Instance; + plugin: Plugin.Instance; state: State; }) => { - if (operation.body) { + const file = plugin.context.file({ id: valibotId })!; + + if (plugin.config.requests.enabled) { + const requiredProperties: Array = []; + if (operation.body?.required) { + requiredProperties.push('body'); + } + + const headersPropertyProperties: Record = {}; + const headersPropertyRequired: Array = []; + const pathPropertyProperties: Record = {}; + const pathPropertyRequired: Array = []; + const queryPropertyProperties: Record = {}; + const queryPropertyRequired: Array = []; + + if (operation.parameters) { + // TODO: add support for cookies + + if (operation.parameters.header) { + if (hasParameterGroupObjectRequired(operation.parameters.path)) { + requiredProperties.push('headers'); + } + + for (const key in operation.parameters.header) { + const parameter = operation.parameters.header[key]!; + headersPropertyProperties[parameter.name] = parameter.schema; + if (parameter.required) { + headersPropertyRequired.push(parameter.name); + } + } + } + + if (operation.parameters.path) { + if (hasParameterGroupObjectRequired(operation.parameters.path)) { + requiredProperties.push('path'); + } + + for (const key in operation.parameters.path) { + const parameter = operation.parameters.path[key]!; + pathPropertyProperties[parameter.name] = parameter.schema; + if (parameter.required) { + pathPropertyRequired.push(parameter.name); + } + } + } + + if (operation.parameters.query) { + if (hasParameterGroupObjectRequired(operation.parameters.query)) { + requiredProperties.push('query'); + } + + for (const key in operation.parameters.query) { + const parameter = operation.parameters.query[key]!; + queryPropertyProperties[parameter.name] = parameter.schema; + if (parameter.required) { + queryPropertyRequired.push(parameter.name); + } + } + } + } + + const identifierData = file.identifier({ + // TODO: refactor for better cross-plugin compatibility + $ref: `#/valibot-data/${operation.id}`, + case: plugin.config.requests.case, + create: true, + nameTransformer: plugin.config.requests.name, + namespace: 'value', + }); schemaToValibotSchema({ - $ref: operationIrRef({ - case: 'camelCase', - config: plugin.context.config, - id: operation.id, - type: 'data', - }), + // TODO: refactor for better cross-plugin compatibility + $ref: `#/valibot-data/${operation.id}`, + identifier: identifierData, plugin, - schema: operation.body.schema, + schema: { + properties: { + body: operation.body + ? operation.body.schema + : { + type: 'never', + }, + headers: Object.keys(headersPropertyProperties).length + ? { + properties: headersPropertyProperties, + required: headersPropertyRequired, + type: 'object', + } + : { + type: 'never', + }, + path: Object.keys(pathPropertyProperties).length + ? { + properties: pathPropertyProperties, + required: pathPropertyRequired, + type: 'object', + } + : { + type: 'never', + }, + query: Object.keys(queryPropertyProperties).length + ? { + properties: queryPropertyProperties, + required: queryPropertyRequired, + type: 'object', + } + : { + type: 'never', + }, + }, + required: requiredProperties, + type: 'object', + }, state, }); } - if (operation.parameters) { - for (const type in operation.parameters) { - const group = operation.parameters[type as keyof IR.ParametersObject]!; - for (const key in group) { - const parameter = group[key]!; + if (plugin.config.responses.enabled) { + if (operation.responses) { + const { response } = operationResponsesMap(operation); + + if (response) { + const identifierResponse = file.identifier({ + // TODO: refactor for better cross-plugin compatibility + $ref: `#/valibot-response/${operation.id}`, + case: plugin.config.responses.case, + create: true, + nameTransformer: plugin.config.responses.name, + namespace: 'value', + }); schemaToValibotSchema({ - $ref: operationIrRef({ - case: 'camelCase', - config: plugin.context.config, - id: operation.id, - parameterId: parameter.name, - type: 'parameter', - }), + // TODO: refactor for better cross-plugin compatibility + $ref: `#/valibot-response/${operation.id}`, + identifier: identifierResponse, plugin, - schema: parameter.schema, + schema: response, state, }); } } } - - if (operation.responses) { - const { response } = operationResponsesMap(operation); - - if (response) { - schemaToValibotSchema({ - $ref: operationIrRef({ - case: 'camelCase', - config: plugin.context.config, - id: operation.id, - type: 'response', - }), - plugin, - schema: response, - state, - }); - } - } }; const schemaToValibotSchema = ({ $ref, + identifier: _identifier, optional, plugin, schema, @@ -865,31 +956,35 @@ const schemaToValibotSchema = ({ * When $ref is supplied, a node will be emitted to the file. */ $ref?: string; + identifier?: Identifier; /** * Accept `optional` to handle optional object properties. We can't handle * this inside the object function because `.optional()` must come before * `.default()` which is handled in this function. */ optional?: boolean; - plugin: Plugin.Instance; + plugin: Plugin.Instance; schema: IR.SchemaObject; state: State; }): Array => { const file = plugin.context.file({ id: valibotId })!; let anyType: string | undefined; - let identifier: ReturnType | undefined; + let identifier: ReturnType | undefined = _identifier; let pipes: Array = []; if ($ref) { state.circularReferenceTracker.add($ref); - identifier = file.identifier({ - $ref, - create: true, - nameTransformer, - namespace: 'value', - }); + if (!identifier) { + identifier = file.identifier({ + $ref, + case: state.nameCase, + create: true, + nameTransformer: state.nameTransformer, + namespace: 'value', + }); + } } if (schema.$ref) { @@ -900,7 +995,8 @@ const schemaToValibotSchema = ({ // this could be (maybe?) fixed by reshuffling the generation order let identifierRef = file.identifier({ $ref: schema.$ref, - nameTransformer, + case: state.nameCase, + nameTransformer: state.nameTransformer, namespace: 'value', }); @@ -916,7 +1012,8 @@ const schemaToValibotSchema = ({ identifierRef = file.identifier({ $ref: schema.$ref, - nameTransformer, + case: state.nameCase, + nameTransformer: state.nameTransformer, namespace: 'value', }); } @@ -947,13 +1044,29 @@ const schemaToValibotSchema = ({ } } } else if (schema.type) { - const valibotSchema = schemaTypeToValibotSchema({ - plugin, - schema, - state, - }); + const valibotSchema = schemaTypeToValibotSchema({ plugin, schema, state }); anyType = valibotSchema.anyType; pipes.push(valibotSchema.expression); + + if (plugin.config.metadata && schema.description) { + const expression = compiler.callExpression({ + functionName: compiler.propertyAccessExpression({ + expression: identifiers.v, + name: identifiers.actions.metadata, + }), + parameters: [ + compiler.objectExpression({ + obj: [ + { + key: 'description', + value: compiler.stringLiteral({ text: schema.description }), + }, + ], + }), + ], + }); + pipes.push(expression); + } } else if (schema.items) { schema = deduplicateSchema({ schema }); @@ -1087,10 +1200,10 @@ const schemaToValibotSchema = ({ return pipes; }; -export const handler: Plugin.Handler = ({ plugin }) => { +export const handler: Plugin.Handler = ({ plugin }) => { const file = plugin.createFile({ id: valibotId, - identifierCase: 'camelCase', + identifierCase: plugin.config.case, path: plugin.output, }); @@ -1104,6 +1217,8 @@ export const handler: Plugin.Handler = ({ plugin }) => { const state: State = { circularReferenceTracker: new Set(), hasCircularReference: false, + nameCase: plugin.config.definitions.case, + nameTransformer: plugin.config.definitions.name, }; if (event.type === 'operation') { diff --git a/packages/openapi-ts/src/plugins/valibot/types.d.ts b/packages/openapi-ts/src/plugins/valibot/types.d.ts index c91b4b869..ca44ff3f5 100644 --- a/packages/openapi-ts/src/plugins/valibot/types.d.ts +++ b/packages/openapi-ts/src/plugins/valibot/types.d.ts @@ -1,13 +1,53 @@ -// import type { IR } from '../../ir/types'; +import type { StringCase } from '../../types/config'; import type { Plugin } from '../types'; export interface Config extends Plugin.Name<'valibot'> { + /** + * The casing convention to use for generated names. + * + * @default 'camelCase' + */ + case?: StringCase; /** * Add comments from input to the generated Valibot schemas? * * @default true */ comments?: boolean; + /** + * Configuration for reusable schema definitions. Controls generation of + * shared Valibot schemas that can be referenced across requests and + * responses. + * + * Can be: + * - `boolean`: Shorthand for `{ enabled: boolean }` + * - `string`: Shorthand for `{ enabled: true; name: string }` + * - `object`: Full configuration object + */ + definitions?: + | boolean + | string + | { + /** + * The casing convention to use for generated names. + * + * @default 'camelCase' + */ + case?: StringCase; + /** + * Whether to generate Valibot schemas for reusable definitions. + * + * @default true + */ + enabled?: boolean; + /** + * Custom naming pattern for generated schema names. The name variable is + * obtained from the schema name. + * + * @default 'v{{name}}' + */ + name?: string | ((name: string) => string); + }; /** * Should the exports from the generated files be re-exported in the index * barrel file? @@ -16,14 +56,199 @@ export interface Config extends Plugin.Name<'valibot'> { */ exportFromIndex?: boolean; /** - * Customise the Valibot schema name. By default, `v{{name}}` is used, - * where `name` is a definition name or an operation name. + * Enable Valibot metadata support? It's often useful to associate a schema + * with some additional metadata for documentation, code generation, AI + * structured outputs, form validation, and other purposes. + * + * @default false */ - // nameBuilder?: (model: IR.OperationObject | IR.SchemaObject) => string; + metadata?: boolean; /** * Name of the generated file. * * @default 'valibot' */ output?: string; + /** + * Configuration for request-specific Valibot schemas. + * Controls generation of Valibot schemas for request bodies, query + * parameters, path parameters, and headers. + * + * Can be: + * - `boolean`: Shorthand for `{ enabled: boolean }` + * - `string`: Shorthand for `{ enabled: true; name: string }` + * - `object`: Full configuration object + */ + requests?: + | boolean + | string + | { + /** + * The casing convention to use for generated names. + * + * @default 'camelCase' + */ + case?: StringCase; + /** + * Whether to generate Valibot schemas for request definitions. + * + * @default true + */ + enabled?: boolean; + /** + * Custom naming pattern for generated schema names. The name variable is + * obtained from the operation name. + * + * @default 'v{{name}}Data' + */ + name?: string | ((name: string) => string); + }; + /** + * Configuration for response-specific Valibot schemas. + * Controls generation of Valibot schemas for response bodies, error + * responses, and status codes. + * + * Can be: + * - `boolean`: Shorthand for `{ enabled: boolean }` + * - `string`: Shorthand for `{ enabled: true; name: string }` + * - `object`: Full configuration object + */ + responses?: + | boolean + | string + | { + /** + * The casing convention to use for generated names. + * + * @default 'camelCase' + */ + case?: StringCase; + /** + * Whether to generate Valibot schemas for response definitions. + * + * @default true + */ + enabled?: boolean; + /** + * Custom naming pattern for generated schema names. The name variable is + * obtained from the operation name. + * + * @default 'v{{name}}Response' + */ + name?: string | ((name: string) => string); + }; +} + +export interface ResolvedConfig extends Plugin.Name<'valibot'> { + /** + * The casing convention to use for generated names. + * + * @default 'camelCase' + */ + case: StringCase; + /** + * Add comments from input to the generated Valibot schemas? + * + * @default true + */ + comments: boolean; + /** + * Configuration for reusable schema definitions. Controls generation of + * shared Valibot schemas that can be referenced across requests and + * responses. + */ + definitions: { + /** + * The casing convention to use for generated names. + * + * @default 'camelCase' + */ + case: StringCase; + /** + * Whether to generate Valibot schemas for reusable definitions. + * + * @default true + */ + enabled: boolean; + /** + * Custom naming pattern for generated schema names. The name variable is + * obtained from the schema name. + * + * @default 'v{{name}}' + */ + name: string | ((name: string) => string); + }; + /** + * Should the exports from the generated files be re-exported in the index + * barrel file? + * + * @default false + */ + exportFromIndex: boolean; + /** + * Enable Valibot metadata support? It's often useful to associate a schema + * with some additional metadata for documentation, code generation, AI + * structured outputs, form validation, and other purposes. + * + * @default false + */ + metadata: boolean; + /** + * Name of the generated file. + * + * @default 'valibot' + */ + output: string; + /** + * Configuration for request-specific Valibot schemas. + * Controls generation of Valibot schemas for request bodies, query + * parameters, path parameters, and headers. + */ + requests: { + /** + * The casing convention to use for generated names. + * + * @default 'camelCase' + */ + case: StringCase; + /** + * Whether to generate Valibot schemas for request definitions. + * + * @default true + */ + enabled: boolean; + /** + * Custom naming pattern for generated schema names. The name variable is + * obtained from the operation name. + * + * @default 'v{{name}}Data' + */ + name: string | ((name: string) => string); + }; + /** + * Configuration for response-specific Valibot schemas. + * Controls generation of Valibot schemas for response bodies, error + * responses, and status codes. + */ + responses: { + /** + * The casing convention to use for generated names. + * + * @default 'camelCase' + */ + case: StringCase; + /** + * Whether to generate Valibot schemas for response definitions. + * + * @default true + */ + enabled: boolean; + /** + * Custom naming pattern for generated schema names. The name variable is + * obtained from the operation name. + * + * @default 'v{{name}}Response' + */ + name: string | ((name: string) => string); + }; }