diff --git a/.changeset/tidy-eyes-flash.md b/.changeset/tidy-eyes-flash.md new file mode 100644 index 0000000000..a91ca44741 --- /dev/null +++ b/.changeset/tidy-eyes-flash.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': minor +--- + +feat: add input.include option diff --git a/docs/.vitepress/config/en.ts b/docs/.vitepress/config/en.ts index 53e7caccbf..d3ddd6d0e4 100644 --- a/docs/.vitepress/config/en.ts +++ b/docs/.vitepress/config/en.ts @@ -69,10 +69,10 @@ export default defineConfig({ }, { items: [ - { link: '/about', text: 'Philosophy' }, + { link: '/openapi-ts/migrating', text: 'Migrating' }, { link: '/license', text: 'License' }, + { link: '/about', text: 'Philosophy' }, { link: '/contributing', text: 'Contributing' }, - { link: '/openapi-ts/migrating', text: 'Migrating' }, ], text: '@hey-api', }, diff --git a/docs/openapi-ts/configuration.md b/docs/openapi-ts/configuration.md index defe9d2aa4..24876f71c4 100644 --- a/docs/openapi-ts/configuration.md +++ b/docs/openapi-ts/configuration.md @@ -49,6 +49,28 @@ Input is the first thing you must define. It can be a local path, remote URL, or We use [`@apidevtools/json-schema-ref-parser`](https://github.com/APIDevTools/json-schema-ref-parser) to resolve file locations. Please note that accessing a HTTPS URL on localhost has a known [workaround](https://github.com/hey-api/openapi-ts/issues/276). ::: +## Filters + +::: warning +Filters work only with the [experimental parser](#parser) which is currently an opt-in feature. +::: + +If you work with large specifications and want to generate output from their subset, set `input.include` to a valid regular expression string. + +```js +export default { + client: '@hey-api/client-fetch', + experimentalParser: true, // [!code ++] + input: { + include: '^(#/components/schemas/foo|#/paths/api/v1/foo/get)$', // [!code ++] + path: 'path/to/openapi.json', + }, + output: 'src/client', +}; +``` + +The configuration above will process only the schema named `foo` and `GET` operation for the `/api/v1/foo` path. + ## Output Output is the next thing to define. It can be either a string pointing to the destination folder or a configuration object containing the destination folder path and optional settings (these are described below). @@ -180,6 +202,31 @@ Plugins are responsible for generating artifacts from your input. By default, He You can learn more on the [Output](/openapi-ts/output) page. +## Parser + +If you're using OpenAPI 3.0 or newer, we encourage you to try out the experimental parser. In the future it will become the default parser, but until it's been tested it's an opt-in feature. To try it out, set the `experimentalParser` flag in your configuration to `true`. + +::: code-group + +```js [config] +export default { + client: '@hey-api/client-fetch', + experimentalParser: true, // [!code ++] + input: 'path/to/openapi.json', + output: 'src/client', +}; +``` + +```sh [cli] +npx @hey-api/openapi-ts -i path/to/openapi.json -o src/client -c @hey-api/client-fetch -e +``` + +::: + +The experimental parser produces a cleaner output while being faster than the legacy parser. It also supports features such as [Filters](#filters) and more will be added in the future. + +The legacy parser will remain enabled for the [legacy clients](/openapi-ts/clients/legacy) regardless of the `experimentalParser` flag value. However, it's unlikely to receive any further updates. + ## Config API You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/types/config.ts) interface. diff --git a/docs/openapi-ts/migrating.md b/docs/openapi-ts/migrating.md index eb500e8541..4a8185d288 100644 --- a/docs/openapi-ts/migrating.md +++ b/docs/openapi-ts/migrating.md @@ -50,6 +50,38 @@ This config option is deprecated and will be removed in favor of [clients](./cli This config option is deprecated and will be removed. +## v0.55.0 + +This release adds the ability to filter your OpenAPI specification before it's processed. This feature will be useful if you are working with a large specification and are interested in generating output only from a small subset. + +This feature is available only in the experimental parser. In the future, this will become the default parser. To opt-in to the experimental parser, set the `experimentalParser` flag in your configuration to `true`. + +### Deprecated `include` in `@hey-api/types` + +This config option is deprecated and will be removed when the experimental parser becomes the default. + +### Deprecated `filter` in `@hey-api/services` + +This config option is deprecated and will be removed when the experimental parser becomes the default. + +### Added `input.include` option + +This config option can be used to replace the deprecated options. It accepts a regular expression string matching against references within the bundled specification. + +```js +export default { + client: '@hey-api/client-fetch', + experimentalParser: true, + input: { + include: '^(#/components/schemas/foo|#/paths/api/v1/foo/get)$', // [!code ++] + path: 'path/to/openapi.json', + }, + output: 'src/client', +}; +``` + +The configuration above will process only the schema named `foo` and `GET` operation for the `/api/v1/foo` path. + ## v0.54.0 This release makes plugins first-class citizens. In order to achieve that, the following breaking changes were introduced. diff --git a/docs/openapi-ts/output.md b/docs/openapi-ts/output.md index 90ea6a85a1..d8911fc52a 100644 --- a/docs/openapi-ts/output.md +++ b/docs/openapi-ts/output.md @@ -8,9 +8,7 @@ description: Learn about files generated with @hey-api/openapi-ts. Learn about files generated with `@hey-api/openapi-ts`. ::: tip - Your actual output depends on your Hey API configuration. It may contain a different number of files and their contents might differ. - ::: ## Overview diff --git a/packages/openapi-ts/src/generate/__tests__/class.spec.ts b/packages/openapi-ts/src/generate/__tests__/class.spec.ts index b7b41a2aa0..9788099616 100644 --- a/packages/openapi-ts/src/generate/__tests__/class.spec.ts +++ b/packages/openapi-ts/src/generate/__tests__/class.spec.ts @@ -19,7 +19,9 @@ describe('generateLegacyClientClass', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, name: 'AppClient', output: { path: '', diff --git a/packages/openapi-ts/src/generate/__tests__/core.spec.ts b/packages/openapi-ts/src/generate/__tests__/core.spec.ts index 34cf46721a..8d9373b5fa 100644 --- a/packages/openapi-ts/src/generate/__tests__/core.spec.ts +++ b/packages/openapi-ts/src/generate/__tests__/core.spec.ts @@ -33,7 +33,9 @@ describe('generateLegacyCore', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, name: 'AppClient', output: { path: '', @@ -106,7 +108,9 @@ describe('generateLegacyCore', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, name: 'AppClient', output: { path: '', @@ -162,7 +166,9 @@ describe('generateLegacyCore', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, name: 'AppClient', output: { path: '', diff --git a/packages/openapi-ts/src/generate/__tests__/index.spec.ts b/packages/openapi-ts/src/generate/__tests__/index.spec.ts index 986af15702..674608bdd0 100644 --- a/packages/openapi-ts/src/generate/__tests__/index.spec.ts +++ b/packages/openapi-ts/src/generate/__tests__/index.spec.ts @@ -20,7 +20,9 @@ describe('generateIndexFile', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, output: { path: '', }, diff --git a/packages/openapi-ts/src/generate/__tests__/output.spec.ts b/packages/openapi-ts/src/generate/__tests__/output.spec.ts index 82dda5fdc3..75269ee757 100644 --- a/packages/openapi-ts/src/generate/__tests__/output.spec.ts +++ b/packages/openapi-ts/src/generate/__tests__/output.spec.ts @@ -20,7 +20,9 @@ describe('generateLegacyOutput', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, output: { format: 'prettier', path: './dist', diff --git a/packages/openapi-ts/src/generate/output.ts b/packages/openapi-ts/src/generate/output.ts index 1b2c11f332..27144aee83 100644 --- a/packages/openapi-ts/src/generate/output.ts +++ b/packages/openapi-ts/src/generate/output.ts @@ -25,11 +25,13 @@ export const generateLegacyOutput = async ({ templates, }: { client: Client; - openApi: OpenApi; + openApi: unknown; templates: Templates; }): Promise => { const config = getConfig(); + const spec = openApi as OpenApi; + // TODO: parser - move to config.input if (client) { if ( @@ -55,7 +57,7 @@ export const generateLegacyOutput = async ({ } // deprecated files - await generateLegacyClientClass(openApi, outputPath, client, templates); + await generateLegacyClientClass(spec, outputPath, client, templates); await generateLegacyCore( path.resolve(config.output.path, 'core'), client, @@ -78,7 +80,7 @@ export const generateLegacyOutput = async ({ plugin._handlerLegacy({ client, files, - openApi, + openApi: spec, plugin: plugin as never, }); } diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index 16fad58218..7c507fa81a 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -1,5 +1,7 @@ +import { existsSync } from 'node:fs'; import path from 'node:path'; +import $RefParser from '@apidevtools/json-schema-ref-parser'; import { loadConfig } from 'c12'; import { sync } from 'cross-spawn'; @@ -23,7 +25,6 @@ import { legacyNameFromConfig, setConfig, } from './utils/config'; -import { getOpenApiSpec } from './utils/getOpenApiSpec'; import { registerHandlebarTemplates } from './utils/handlebars'; import { Performance, PerformanceReport } from './utils/performance'; import { postProcessClient } from './utils/postprocess'; @@ -125,6 +126,26 @@ const getClient = (userConfig: ClientConfig): Config['client'] => { return client; }; +const getInput = (userConfig: ClientConfig): Config['input'] => { + let input: Config['input'] = { + path: '', + }; + if (typeof userConfig.input === 'string') { + input.path = userConfig.input; + } else if (userConfig.input && userConfig.input.path) { + input = { + ...input, + ...userConfig.input, + }; + } else { + input = { + ...input, + path: userConfig.input, + }; + } + return input; +}; + const getOutput = (userConfig: ClientConfig): Config['output'] => { let output: Config['output'] = { format: false, @@ -221,6 +242,19 @@ const getPlugins = ( }; }; +const getSpec = async ({ config }: { config: Config }) => { + let spec: unknown = config.input.path; + + if (typeof config.input.path === 'string') { + const absolutePathOrUrl = existsSync(config.input.path) + ? path.resolve(config.input.path) + : config.input.path; + spec = await $RefParser.bundle(absolutePathOrUrl, absolutePathOrUrl, {}); + } + + return spec; +}; + const initConfigs = async (userConfig: UserConfig): Promise => { let configurationFile: string | undefined = undefined; if (userConfig.configFile) { @@ -250,7 +284,6 @@ const initConfigs = async (userConfig: UserConfig): Promise => { dryRun = false, exportCore = true, experimentalParser = false, - input, name, request, useOptions = true, @@ -260,9 +293,10 @@ const initConfigs = async (userConfig: UserConfig): Promise => { console.warn('userConfig:', userConfig); } + const input = getInput(userConfig); const output = getOutput(userConfig); - if (!input) { + if (!input.path) { throw new Error( '🚫 missing input - which OpenAPI specification should we use to generate your client?', ); @@ -332,14 +366,9 @@ export async function createClient( Performance.end('handlebars'); const pCreateClient = (config: Config) => async () => { - Performance.start('openapi'); - const openApi = - typeof config.input === 'string' - ? await getOpenApiSpec(config.input) - : (config.input as unknown as Awaited< - ReturnType - >); - Performance.end('openapi'); + Performance.start('spec'); + const spec = await getSpec({ config }); + Performance.end('spec'); let client: Client | undefined; let context: IRContext | undefined; @@ -363,14 +392,14 @@ export async function createClient( context = parseExperimental({ config, parserConfig, - spec: openApi, + spec, }); } // fallback to legacy parser if (!context) { const parsed = parseLegacy({ - openApi, + openApi: spec, parserConfig, }); client = postProcessClient(parsed); @@ -383,7 +412,7 @@ export async function createClient( if (context) { await generateOutput({ context }); } else if (client) { - await generateLegacyOutput({ client, openApi, templates }); + await generateLegacyOutput({ client, openApi: spec, templates }); } Performance.end('generator'); diff --git a/packages/openapi-ts/src/openApi/3.0.x/parser/index.ts b/packages/openapi-ts/src/openApi/3.0.x/parser/index.ts index c600088297..7c8604f9db 100644 --- a/packages/openapi-ts/src/openApi/3.0.x/parser/index.ts +++ b/packages/openapi-ts/src/openApi/3.0.x/parser/index.ts @@ -1,4 +1,5 @@ import type { IRContext } from '../../../ir/context'; +import { canProcessRef } from '../../shared/utils/filter'; import type { OpenApiV3_0_X, ParameterObject, @@ -16,6 +17,10 @@ import { parseSchema } from './schema'; export const parseV3_0_X = (context: IRContext) => { const operationIds = new Map(); + const regexp = context.config.input.include + ? new RegExp(context.config.input.include) + : undefined; + for (const path in context.spec.paths) { const pathItem = context.spec.paths[path as keyof PathsObject]; @@ -50,7 +55,8 @@ export const parseV3_0_X = (context: IRContext) => { path: path as keyof PathsObject, }; - if (finalPathItem.delete) { + const $refDelete = `#/paths${path}/delete`; + if (finalPathItem.delete && canProcessRef($refDelete, regexp)) { parseOperation({ ...operationArgs, method: 'delete', @@ -68,7 +74,8 @@ export const parseV3_0_X = (context: IRContext) => { }); } - if (finalPathItem.get) { + const $refGet = `#/paths${path}/get`; + if (finalPathItem.get && canProcessRef($refGet, regexp)) { parseOperation({ ...operationArgs, method: 'get', @@ -86,7 +93,8 @@ export const parseV3_0_X = (context: IRContext) => { }); } - if (finalPathItem.head) { + const $refHead = `#/paths${path}/head`; + if (finalPathItem.head && canProcessRef($refHead, regexp)) { parseOperation({ ...operationArgs, method: 'head', @@ -104,7 +112,8 @@ export const parseV3_0_X = (context: IRContext) => { }); } - if (finalPathItem.options) { + const $refOptions = `#/paths${path}/options`; + if (finalPathItem.options && canProcessRef($refOptions, regexp)) { parseOperation({ ...operationArgs, method: 'options', @@ -122,7 +131,8 @@ export const parseV3_0_X = (context: IRContext) => { }); } - if (finalPathItem.patch) { + const $refPatch = `#/paths${path}/patch`; + if (finalPathItem.patch && canProcessRef($refPatch, regexp)) { parseOperation({ ...operationArgs, method: 'patch', @@ -140,7 +150,8 @@ export const parseV3_0_X = (context: IRContext) => { }); } - if (finalPathItem.post) { + const $refPost = `#/paths${path}/post`; + if (finalPathItem.post && canProcessRef($refPost, regexp)) { parseOperation({ ...operationArgs, method: 'post', @@ -158,7 +169,8 @@ export const parseV3_0_X = (context: IRContext) => { }); } - if (finalPathItem.put) { + const $refPut = `#/paths${path}/put`; + if (finalPathItem.put && canProcessRef($refPut, regexp)) { parseOperation({ ...operationArgs, method: 'put', @@ -176,7 +188,8 @@ export const parseV3_0_X = (context: IRContext) => { }); } - if (finalPathItem.trace) { + const $refTrace = `#/paths${path}/trace`; + if (finalPathItem.trace && canProcessRef($refTrace, regexp)) { parseOperation({ ...operationArgs, method: 'trace', @@ -198,6 +211,11 @@ export const parseV3_0_X = (context: IRContext) => { // TODO: parser - handle more component types, old parser handles only parameters and schemas if (context.spec.components) { for (const name in context.spec.components.parameters) { + const $ref = `#/components/parameters/${name}`; + if (!canProcessRef($ref, regexp)) { + continue; + } + const parameterOrReference = context.spec.components.parameters[name]; const parameter = '$ref' in parameterOrReference @@ -212,6 +230,11 @@ export const parseV3_0_X = (context: IRContext) => { } for (const name in context.spec.components.schemas) { + const $ref = `#/components/schemas/${name}`; + if (!canProcessRef($ref, regexp)) { + continue; + } + const schema = context.spec.components.schemas[name]; parseSchema({ diff --git a/packages/openapi-ts/src/openApi/3.0.x/parser/operation.ts b/packages/openapi-ts/src/openApi/3.0.x/parser/operation.ts index 67d0c8efab..94df2da3cf 100644 --- a/packages/openapi-ts/src/openApi/3.0.x/parser/operation.ts +++ b/packages/openapi-ts/src/openApi/3.0.x/parser/operation.ts @@ -184,20 +184,10 @@ export const parseOperation = ({ operationIds: Map; path: keyof IRPathsObject; }) => { - const operationKey = `${method.toUpperCase()} ${path}`; - - // TODO: parser - move services to plugin, cleaner syntax - if ( - !context.parserConfig.filterFn.operation({ - config: context.config, - operationKey, - }) - ) { - return; - } - // TODO: parser - support throw on duplicate if (operation.operationId) { + const operationKey = `${method.toUpperCase()} ${path}`; + if (operationIds.has(operation.operationId)) { console.warn( `❗️ Duplicate operationId: ${operation.operationId} in ${operationKey}. Please ensure your operation IDs are unique. This behavior is not supported and will likely lead to unexpected results.`, diff --git a/packages/openapi-ts/src/openApi/3.1.x/parser/index.ts b/packages/openapi-ts/src/openApi/3.1.x/parser/index.ts index 3735b7e533..d41fd38a9b 100644 --- a/packages/openapi-ts/src/openApi/3.1.x/parser/index.ts +++ b/packages/openapi-ts/src/openApi/3.1.x/parser/index.ts @@ -1,4 +1,5 @@ import type { IRContext } from '../../../ir/context'; +import { canProcessRef } from '../../shared/utils/filter'; import type { OpenApiV3_1_X, ParameterObject, @@ -16,6 +17,10 @@ import { parseSchema } from './schema'; export const parseV3_1_X = (context: IRContext) => { const operationIds = new Map(); + const regexp = context.config.input.include + ? new RegExp(context.config.input.include) + : undefined; + for (const path in context.spec.paths) { const pathItem = context.spec.paths[path as keyof PathsObject]; @@ -43,7 +48,8 @@ export const parseV3_1_X = (context: IRContext) => { path: path as keyof PathsObject, }; - if (finalPathItem.delete) { + const $refDelete = `#/paths${path}/delete`; + if (finalPathItem.delete && canProcessRef($refDelete, regexp)) { parseOperation({ ...operationArgs, method: 'delete', @@ -61,7 +67,8 @@ export const parseV3_1_X = (context: IRContext) => { }); } - if (finalPathItem.get) { + const $refGet = `#/paths${path}/get`; + if (finalPathItem.get && canProcessRef($refGet, regexp)) { parseOperation({ ...operationArgs, method: 'get', @@ -79,7 +86,8 @@ export const parseV3_1_X = (context: IRContext) => { }); } - if (finalPathItem.head) { + const $refHead = `#/paths${path}/head`; + if (finalPathItem.head && canProcessRef($refHead, regexp)) { parseOperation({ ...operationArgs, method: 'head', @@ -97,7 +105,8 @@ export const parseV3_1_X = (context: IRContext) => { }); } - if (finalPathItem.options) { + const $refOptions = `#/paths${path}/options`; + if (finalPathItem.options && canProcessRef($refOptions, regexp)) { parseOperation({ ...operationArgs, method: 'options', @@ -115,7 +124,8 @@ export const parseV3_1_X = (context: IRContext) => { }); } - if (finalPathItem.patch) { + const $refPatch = `#/paths${path}/patch`; + if (finalPathItem.patch && canProcessRef($refPatch, regexp)) { parseOperation({ ...operationArgs, method: 'patch', @@ -133,7 +143,8 @@ export const parseV3_1_X = (context: IRContext) => { }); } - if (finalPathItem.post) { + const $refPost = `#/paths${path}/post`; + if (finalPathItem.post && canProcessRef($refPost, regexp)) { parseOperation({ ...operationArgs, method: 'post', @@ -151,7 +162,8 @@ export const parseV3_1_X = (context: IRContext) => { }); } - if (finalPathItem.put) { + const $refPut = `#/paths${path}/put`; + if (finalPathItem.put && canProcessRef($refPut, regexp)) { parseOperation({ ...operationArgs, method: 'put', @@ -169,7 +181,8 @@ export const parseV3_1_X = (context: IRContext) => { }); } - if (finalPathItem.trace) { + const $refTrace = `#/paths${path}/trace`; + if (finalPathItem.trace && canProcessRef($refTrace, regexp)) { parseOperation({ ...operationArgs, method: 'trace', @@ -191,6 +204,11 @@ export const parseV3_1_X = (context: IRContext) => { // TODO: parser - handle more component types, old parser handles only parameters and schemas if (context.spec.components) { for (const name in context.spec.components.parameters) { + const $ref = `#/components/parameters/${name}`; + if (!canProcessRef($ref, regexp)) { + continue; + } + const parameterOrReference = context.spec.components.parameters[name]; const parameter = '$ref' in parameterOrReference @@ -205,6 +223,11 @@ export const parseV3_1_X = (context: IRContext) => { } for (const name in context.spec.components.schemas) { + const $ref = `#/components/schemas/${name}`; + if (!canProcessRef($ref, regexp)) { + continue; + } + const schema = context.spec.components.schemas[name]; parseSchema({ diff --git a/packages/openapi-ts/src/openApi/3.1.x/parser/operation.ts b/packages/openapi-ts/src/openApi/3.1.x/parser/operation.ts index 7ed717242a..18951b2ab3 100644 --- a/packages/openapi-ts/src/openApi/3.1.x/parser/operation.ts +++ b/packages/openapi-ts/src/openApi/3.1.x/parser/operation.ts @@ -172,20 +172,10 @@ export const parseOperation = ({ operationIds: Map; path: keyof IRPathsObject; }) => { - const operationKey = `${method.toUpperCase()} ${path}`; - - // TODO: parser - move services to plugin, cleaner syntax - if ( - !context.parserConfig.filterFn.operation({ - config: context.config, - operationKey, - }) - ) { - return; - } - // TODO: parser - support throw on duplicate if (operation.operationId) { + const operationKey = `${method.toUpperCase()} ${path}`; + if (operationIds.has(operation.operationId)) { console.warn( `❗️ Duplicate operationId: ${operation.operationId} in ${operationKey}. Please ensure your operation IDs are unique. This behavior is not supported and will likely lead to unexpected results.`, diff --git a/packages/openapi-ts/src/openApi/__tests__/index.spec.ts b/packages/openapi-ts/src/openApi/__tests__/index.spec.ts index 2dfb31836b..a7575e8155 100644 --- a/packages/openapi-ts/src/openApi/__tests__/index.spec.ts +++ b/packages/openapi-ts/src/openApi/__tests__/index.spec.ts @@ -97,7 +97,6 @@ describe('parse', () => { it('throws on unknown version', () => { expect(() => - // @ts-expect-error parseLegacy({ openApi: { foo: 'bar' }, parserConfig }), ).toThrow( `Unsupported OpenAPI specification: ${JSON.stringify({ foo: 'bar' }, null, 2)}`, diff --git a/packages/openapi-ts/src/openApi/index.ts b/packages/openapi-ts/src/openApi/index.ts index 6039f41a17..f50418b108 100644 --- a/packages/openapi-ts/src/openApi/index.ts +++ b/packages/openapi-ts/src/openApi/index.ts @@ -39,21 +39,23 @@ export function parseLegacy({ openApi, parserConfig, }: { - openApi: OpenApi; + openApi: unknown; parserConfig: ParserConfig; }): Client { + const spec = openApi as OpenApi; + setParserConfig(parserConfig); - if ('openapi' in openApi) { - return parseV3(openApi); + if ('openapi' in spec) { + return parseV3(spec); } - if ('swagger' in openApi) { - return parseV2(openApi); + if ('swagger' in spec) { + return parseV2(spec); } throw new Error( - `Unsupported OpenAPI specification: ${JSON.stringify(openApi, null, 2)}`, + `Unsupported OpenAPI specification: ${JSON.stringify(spec, null, 2)}`, ); } diff --git a/packages/openapi-ts/src/openApi/shared/utils/filter.ts b/packages/openapi-ts/src/openApi/shared/utils/filter.ts new file mode 100644 index 0000000000..ba870f19a7 --- /dev/null +++ b/packages/openapi-ts/src/openApi/shared/utils/filter.ts @@ -0,0 +1,8 @@ +export const canProcessRef = ($ref: string, regexp?: RegExp): boolean => { + if (!regexp) { + return true; + } + + regexp.lastIndex = 0; + return regexp.test($ref); +}; diff --git a/packages/openapi-ts/src/plugins/@hey-api/schemas/__tests__/schemas.spec.ts b/packages/openapi-ts/src/plugins/@hey-api/schemas/__tests__/schemas.spec.ts index 04b5c65099..edc9739c19 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/schemas/__tests__/schemas.spec.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/schemas/__tests__/schemas.spec.ts @@ -21,7 +21,9 @@ describe('generateLegacySchemas', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, name: 'AppClient', output: { path: '', @@ -90,7 +92,9 @@ describe('generateLegacySchemas', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, name: 'AppClient', output: { path: '', diff --git a/packages/openapi-ts/src/plugins/@hey-api/services/__tests__/services.spec.ts b/packages/openapi-ts/src/plugins/@hey-api/services/__tests__/services.spec.ts index 1cfbbd7b08..1944712ce2 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/services/__tests__/services.spec.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/services/__tests__/services.spec.ts @@ -23,7 +23,9 @@ describe('generateLegacyServices', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, output: { path: '', }, @@ -161,7 +163,9 @@ describe('methodNameBuilder', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, output: { path: '', }, @@ -223,7 +227,9 @@ describe('methodNameBuilder', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, output: { path: '', }, @@ -288,7 +294,9 @@ describe('methodNameBuilder', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, output: { path: '', }, diff --git a/packages/openapi-ts/src/plugins/@hey-api/services/types.d.ts b/packages/openapi-ts/src/plugins/@hey-api/services/types.d.ts index c5dc1330af..8478881ef2 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/services/types.d.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/services/types.d.ts @@ -20,6 +20,10 @@ export interface Config extends PluginName<'@hey-api/services'> { * results will be included in the output. The input pattern this * string will be tested against is `{method} {path}`. For example, * you can match `POST /api/v1/foo` with `^POST /api/v1/foo$`. + * + * This option does not work with the experimental parser. + * + * @deprecated */ filter?: string; /** diff --git a/packages/openapi-ts/src/plugins/@hey-api/types/__tests__/types.spec.ts b/packages/openapi-ts/src/plugins/@hey-api/types/__tests__/types.spec.ts index 2bca1f14df..9c2b1c1791 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/types/__tests__/types.spec.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/types/__tests__/types.spec.ts @@ -20,7 +20,9 @@ describe('generateLegacyTypes', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, name: 'AppClient', output: { path: '', diff --git a/packages/openapi-ts/src/plugins/@hey-api/types/types.d.ts b/packages/openapi-ts/src/plugins/@hey-api/types/types.d.ts index 17e124a046..a3edd4c0a8 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/types/types.d.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/types/types.d.ts @@ -7,7 +7,11 @@ export interface Config extends PluginName<'@hey-api/types'> { */ enums?: 'javascript' | 'typescript' | 'typescript+namespace' | false; /** - * Include only types matching regular expression + * Include only types matching regular expression. + * + * This option does not work with the experimental parser. + * + * @deprecated */ include?: string; /** diff --git a/packages/openapi-ts/src/types/config.ts b/packages/openapi-ts/src/types/config.ts index cd533ea184..f84071f63c 100644 --- a/packages/openapi-ts/src/types/config.ts +++ b/packages/openapi-ts/src/types/config.ts @@ -1,5 +1,9 @@ import type { ClientPlugins, UserPlugins } from '../plugins/'; -import type { ArrayOfObjectsToObjectMap, ExtractArrayOfObjects } from './utils'; +import type { + ArrayOfObjectsToObjectMap, + ExtractArrayOfObjects, + ExtractWithDiscriminator, +} from './utils'; export const CLIENTS = [ '@hey-api/client-axios', @@ -67,9 +71,32 @@ export interface ClientConfig { */ exportCore?: boolean; /** - * The relative location of the OpenAPI spec + * Path to the OpenAPI specification. This can be either local or remote path. + * Both JSON and YAML file formats are supported. You can also pass the parsed + * object directly if you're fetching the file yourself. + * + * Alternatively, you can define a configuration object with more options. */ - input: string | Record; + input: + | string + | Record + | { + /** + * Process only parts matching the regular expression. You can select both + * operations and components by reference within the bundled input. + * + * @example + * operation: '^#/paths/api/v1/foo/get$' + * schema: '^#/components/schemas/Foo$' + */ + include?: string; + /** + * Path to the OpenAPI specification. This can be either local or remote path. + * Both JSON and YAML file formats are supported. You can also pass the parsed + * object directly if you're fetching the file yourself. + */ + path: string | Record; + }; /** * Custom client class name. Please note this option is deprecated and * will be removed in favor of clients. @@ -123,11 +150,12 @@ export interface UserConfig extends ClientConfig {} export type Config = Omit< Required, - 'base' | 'client' | 'name' | 'output' | 'plugins' | 'request' + 'base' | 'client' | 'input' | 'name' | 'output' | 'plugins' | 'request' > & Pick & { client: Extract['client'], object>; - output: Extract['output'], object>; + input: ExtractWithDiscriminator; + output: Extract; pluginOrder: ReadonlyArray; plugins: ArrayOfObjectsToObjectMap< ExtractArrayOfObjects, { name: string }>, diff --git a/packages/openapi-ts/src/types/utils.ts b/packages/openapi-ts/src/types/utils.ts index bb79381ca0..fd6d68d6d9 100644 --- a/packages/openapi-ts/src/types/utils.ts +++ b/packages/openapi-ts/src/types/utils.ts @@ -1,6 +1,8 @@ import type { TypeScriptFile } from '../generate/files'; -type ExtractFromArray = T extends Discriminator ? T : never; +export type ExtractWithDiscriminator = T extends Discriminator + ? T + : never; /** * Accepts an array of elements union and attempts to extract only objects. @@ -9,9 +11,9 @@ type ExtractFromArray = T extends Discriminator ? T : never; */ export type ExtractArrayOfObjects = T extends Array - ? Array> + ? Array> : T extends ReadonlyArray - ? ReadonlyArray> + ? ReadonlyArray> : never; export type Files = Record; diff --git a/packages/openapi-ts/src/utils/__tests__/handlebars.spec.ts b/packages/openapi-ts/src/utils/__tests__/handlebars.spec.ts index ecc0e1bd31..e0803012b4 100644 --- a/packages/openapi-ts/src/utils/__tests__/handlebars.spec.ts +++ b/packages/openapi-ts/src/utils/__tests__/handlebars.spec.ts @@ -18,7 +18,9 @@ describe('registerHandlebarHelpers', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, output: { format: 'prettier', path: '', @@ -66,7 +68,9 @@ describe('registerHandlebarTemplates', () => { dryRun: false, experimentalParser: false, exportCore: true, - input: '', + input: { + path: '', + }, output: { format: 'prettier', path: '', diff --git a/packages/openapi-ts/src/utils/__tests__/parse.spec.ts b/packages/openapi-ts/src/utils/__tests__/parse.spec.ts index 2a9ff8c5fa..af0ea529d5 100644 --- a/packages/openapi-ts/src/utils/__tests__/parse.spec.ts +++ b/packages/openapi-ts/src/utils/__tests__/parse.spec.ts @@ -13,7 +13,9 @@ describe('operationNameFn', () => { dryRun: true, experimentalParser: false, exportCore: false, - input: '', + input: { + path: '', + }, output: { path: '', }, diff --git a/packages/openapi-ts/src/utils/getOpenApiSpec.ts b/packages/openapi-ts/src/utils/getOpenApiSpec.ts deleted file mode 100644 index 099ad67bc9..0000000000 --- a/packages/openapi-ts/src/utils/getOpenApiSpec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { existsSync } from 'node:fs'; -import path from 'node:path'; - -import $RefParser from '@apidevtools/json-schema-ref-parser'; - -import type { OpenApi } from '../openApi'; - -/** - * Load and parse te open api spec. If the file extension is ".yml" or ".yaml" - * we will try to parse the file as a YAML spec, otherwise we will fall back - * on parsing the file as JSON. - * @param location: Path or url - */ -export const getOpenApiSpec = async (location: string) => { - const absolutePathOrUrl = existsSync(location) - ? path.resolve(location) - : location; - const schema = (await $RefParser.bundle( - absolutePathOrUrl, - absolutePathOrUrl, - {}, - )) as OpenApi; - return schema; -}; diff --git a/packages/openapi-ts/test/3.0.x.spec.ts b/packages/openapi-ts/test/3.0.x.spec.ts index 6746000519..d3517284ee 100644 --- a/packages/openapi-ts/test/3.0.x.spec.ts +++ b/packages/openapi-ts/test/3.0.x.spec.ts @@ -48,6 +48,13 @@ describe(`OpenAPI ${VERSION}`, () => { }), description: 'allows arbitrary properties on objects', }, + { + config: createConfig({ + input: 'enum-escape.json', + output: 'enum-escape', + }), + description: 'escapes enum values', + }, ]; it.each(scenarios)('$description', async ({ config }) => { diff --git a/packages/openapi-ts/test/3.1.x.spec.ts b/packages/openapi-ts/test/3.1.x.spec.ts index b078c0f33a..037d7ee8a7 100644 --- a/packages/openapi-ts/test/3.1.x.spec.ts +++ b/packages/openapi-ts/test/3.1.x.spec.ts @@ -55,6 +55,13 @@ describe(`OpenAPI ${VERSION}`, () => { }), description: 'does not generate duplicate null', }, + { + config: createConfig({ + input: 'enum-escape.json', + output: 'enum-escape', + }), + description: 'escapes enum values', + }, { config: createConfig({ input: 'object-properties-all-of.json', diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-escape/index.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-escape/index.ts new file mode 100644 index 0000000000..56bade120a --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-escape/index.ts @@ -0,0 +1,2 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-escape/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-escape/types.gen.ts new file mode 100644 index 0000000000..090beaedd3 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-escape/types.gen.ts @@ -0,0 +1,7 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Foo = { + foo?: "foo'bar" | 'foo"bar'; +}; + +export type Bar = "foo'bar" | 'foo"bar'; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@hey-api/services/default/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@hey-api/services/default/services.gen.ts index b21353c5df..c79ed1e8cc 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@hey-api/services/default/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@hey-api/services/default/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); } @@ -177,7 +177,7 @@ export class RequestBodyService { 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/requestBody/' + url: '/api/v{api-version}/requestBody' }); } @@ -192,7 +192,7 @@ export class FormDataService { 'Content-Type': null, ...options?.headers }, - url: '/api/v{api-version}/formData/' + url: '/api/v{api-version}/formData' }); } diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/angular-query-experimental/axios/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/angular-query-experimental/axios/services.gen.ts index 5eda265823..89730659aa 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/angular-query-experimental/axios/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/angular-query-experimental/axios/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); } @@ -177,7 +177,7 @@ export class RequestBodyService { 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/requestBody/' + url: '/api/v{api-version}/requestBody' }); } @@ -192,7 +192,7 @@ export class FormDataService { 'Content-Type': null, ...options?.headers }, - url: '/api/v{api-version}/formData/' + url: '/api/v{api-version}/formData' }); } diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/react-query/axios/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/react-query/axios/services.gen.ts index 5eda265823..89730659aa 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/react-query/axios/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/react-query/axios/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); } @@ -177,7 +177,7 @@ export class RequestBodyService { 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/requestBody/' + url: '/api/v{api-version}/requestBody' }); } @@ -192,7 +192,7 @@ export class FormDataService { 'Content-Type': null, ...options?.headers }, - url: '/api/v{api-version}/formData/' + url: '/api/v{api-version}/formData' }); } diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/solid-query/axios/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/solid-query/axios/services.gen.ts index 5eda265823..89730659aa 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/solid-query/axios/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/solid-query/axios/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); } @@ -177,7 +177,7 @@ export class RequestBodyService { 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/requestBody/' + url: '/api/v{api-version}/requestBody' }); } @@ -192,7 +192,7 @@ export class FormDataService { 'Content-Type': null, ...options?.headers }, - url: '/api/v{api-version}/formData/' + url: '/api/v{api-version}/formData' }); } diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/svelte-query/axios/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/svelte-query/axios/services.gen.ts index 5eda265823..89730659aa 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/svelte-query/axios/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/svelte-query/axios/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); } @@ -177,7 +177,7 @@ export class RequestBodyService { 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/requestBody/' + url: '/api/v{api-version}/requestBody' }); } @@ -192,7 +192,7 @@ export class FormDataService { 'Content-Type': null, ...options?.headers }, - url: '/api/v{api-version}/formData/' + url: '/api/v{api-version}/formData' }); } diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/vue-query/axios/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/vue-query/axios/services.gen.ts index 5eda265823..89730659aa 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/vue-query/axios/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/plugins/@tanstack/vue-query/axios/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); } @@ -177,7 +177,7 @@ export class RequestBodyService { 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/requestBody/' + url: '/api/v{api-version}/requestBody' }); } @@ -192,7 +192,7 @@ export class FormDataService { 'Content-Type': null, ...options?.headers }, - url: '/api/v{api-version}/formData/' + url: '/api/v{api-version}/formData' }); } diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/angular-query-experimental/axios/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/angular-query-experimental/axios/services.gen.ts index 5eda265823..89730659aa 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/angular-query-experimental/axios/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/angular-query-experimental/axios/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); } @@ -177,7 +177,7 @@ export class RequestBodyService { 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/requestBody/' + url: '/api/v{api-version}/requestBody' }); } @@ -192,7 +192,7 @@ export class FormDataService { 'Content-Type': null, ...options?.headers }, - url: '/api/v{api-version}/formData/' + url: '/api/v{api-version}/formData' }); } diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/react-query/axios/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/react-query/axios/services.gen.ts index 5eda265823..89730659aa 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/react-query/axios/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/react-query/axios/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); } @@ -177,7 +177,7 @@ export class RequestBodyService { 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/requestBody/' + url: '/api/v{api-version}/requestBody' }); } @@ -192,7 +192,7 @@ export class FormDataService { 'Content-Type': null, ...options?.headers }, - url: '/api/v{api-version}/formData/' + url: '/api/v{api-version}/formData' }); } diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/solid-query/axios/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/solid-query/axios/services.gen.ts index 5eda265823..89730659aa 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/solid-query/axios/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/solid-query/axios/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); } @@ -177,7 +177,7 @@ export class RequestBodyService { 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/requestBody/' + url: '/api/v{api-version}/requestBody' }); } @@ -192,7 +192,7 @@ export class FormDataService { 'Content-Type': null, ...options?.headers }, - url: '/api/v{api-version}/formData/' + url: '/api/v{api-version}/formData' }); } diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/svelte-query/axios/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/svelte-query/axios/services.gen.ts index 5eda265823..89730659aa 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/svelte-query/axios/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/svelte-query/axios/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options?: Options) { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); } @@ -177,7 +177,7 @@ export class RequestBodyService { 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/requestBody/' + url: '/api/v{api-version}/requestBody' }); } @@ -192,7 +192,7 @@ export class FormDataService { 'Content-Type': null, ...options?.headers }, - url: '/api/v{api-version}/formData/' + url: '/api/v{api-version}/formData' }); } diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/vue-query/axios/services.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/vue-query/axios/services.gen.ts index 5eda265823..89730659aa 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/vue-query/axios/services.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/plugins/@tanstack/vue-query/axios/services.gen.ts @@ -96,7 +96,7 @@ export const deleteFoo = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = (options: Options export const callWithDescriptions = (options?: Options) => { return (options?.client ?? client).post({ ...options, - url: '/api/v{api-version}/descriptions/' + url: '/api/v{api-version}/descriptions' }); }; @@ -139,7 +139,7 @@ export const getCallWithOptionalParam = (o 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -150,7 +150,7 @@ export const postCallWithOptionalParam = ( 'Content-Type': 'application/json', ...options?.headers }, - url: '/api/v{api-version}/parameters/' + url: '/api/v{api-version}/parameters' }); }; @@ -161,7 +161,7 @@ export const postApiVbyApiVersionRequestBody = { }, // debug: true, experimentalParser: true, - input: './test/spec/3.0.x/full.json', - // input: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', + input: { + include: + '^(#/components/schemas/import|#/paths/api/v{api-version}/simple/options)$', + path: './test/spec/3.1.x/full.json', + // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', + }, // name: 'foo', output: { // format: 'prettier', @@ -25,7 +29,7 @@ const main = async () => { // }, { // asClass: true, - // filter: '^GET /api/v{api-version}/simple:operation$', + // include... name: '@hey-api/services', // serviceNameBuilder: '^Parameters', }, @@ -37,13 +41,11 @@ const main = async () => { // enums: 'typescript', // enums: 'typescript+namespace', // enums: 'javascript', - // include: - // '^(_400|CompositionWithOneOfAndProperties)', name: '@hey-api/types', // style: 'PascalCase', // tree: false, }, - '@tanstack/react-query', + // '@tanstack/react-query', // 'zod', ], // useOptions: false, diff --git a/packages/openapi-ts/test/spec/3.0.x/enum-escape.json b/packages/openapi-ts/test/spec/3.0.x/enum-escape.json new file mode 100644 index 0000000000..f709947977 --- /dev/null +++ b/packages/openapi-ts/test/spec/3.0.x/enum-escape.json @@ -0,0 +1,24 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "OpenAPI 3.0.2 enum escape example", + "version": "1" + }, + "components": { + "schemas": { + "Foo": { + "type": "object", + "properties": { + "foo": { + "enum": ["foo'bar", "foo\"bar"], + "type": "string" + } + } + }, + "Bar": { + "enum": ["foo'bar", "foo\"bar"], + "type": "string" + } + } + } +} diff --git a/packages/openapi-ts/test/spec/3.0.x/full.json b/packages/openapi-ts/test/spec/3.0.x/full.json index d390f47343..8a2c95f977 100644 --- a/packages/openapi-ts/test/spec/3.0.x/full.json +++ b/packages/openapi-ts/test/spec/3.0.x/full.json @@ -203,7 +203,7 @@ } ] }, - "/api/v{api-version}/descriptions/": { + "/api/v{api-version}/descriptions": { "post": { "tags": ["Descriptions"], "operationId": "CallWithDescriptions", @@ -478,7 +478,7 @@ } } }, - "/api/v{api-version}/parameters/": { + "/api/v{api-version}/parameters": { "get": { "tags": ["Parameters"], "operationId": "GetCallWithOptionalParam", @@ -554,7 +554,7 @@ } } }, - "/api/v{api-version}/requestBody/": { + "/api/v{api-version}/requestBody": { "post": { "tags": ["RequestBody"], "parameters": [ @@ -567,7 +567,7 @@ } } }, - "/api/v{api-version}/formData/": { + "/api/v{api-version}/formData": { "post": { "tags": ["FormData"], "parameters": [ diff --git a/packages/openapi-ts/test/spec/3.1.x/enum-escape.json b/packages/openapi-ts/test/spec/3.1.x/enum-escape.json new file mode 100644 index 0000000000..6de288481a --- /dev/null +++ b/packages/openapi-ts/test/spec/3.1.x/enum-escape.json @@ -0,0 +1,24 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "OpenAPI 3.1.1 enum escape example", + "version": "1" + }, + "components": { + "schemas": { + "Foo": { + "type": "object", + "properties": { + "foo": { + "enum": ["foo'bar", "foo\"bar"], + "type": "string" + } + } + }, + "Bar": { + "enum": ["foo'bar", "foo\"bar"], + "type": "string" + } + } + } +} diff --git a/packages/openapi-ts/test/spec/3.1.x/full.json b/packages/openapi-ts/test/spec/3.1.x/full.json index e8b8c46a74..c92219b78e 100644 --- a/packages/openapi-ts/test/spec/3.1.x/full.json +++ b/packages/openapi-ts/test/spec/3.1.x/full.json @@ -211,7 +211,7 @@ } ] }, - "/api/v{api-version}/descriptions/": { + "/api/v{api-version}/descriptions": { "post": { "tags": ["Descriptions"], "operationId": "CallWithDescriptions", @@ -476,7 +476,7 @@ } } }, - "/api/v{api-version}/parameters/": { + "/api/v{api-version}/parameters": { "get": { "tags": ["Parameters"], "operationId": "GetCallWithOptionalParam", @@ -551,7 +551,7 @@ } } }, - "/api/v{api-version}/requestBody/": { + "/api/v{api-version}/requestBody": { "post": { "tags": ["RequestBody"], "parameters": [ @@ -564,7 +564,7 @@ } } }, - "/api/v{api-version}/formData/": { + "/api/v{api-version}/formData": { "post": { "tags": ["FormData"], "parameters": [