diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 000000000..88d0fde35 --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,37 @@ +name: unittest + +on: [pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + + - name: Setup Node environment + uses: actions/setup-node@v4.0.1 + with: + node-version: 20 + + - name: Cache Modules + uses: actions/cache@v4 + with: + path: "**/node_modules" + key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }} + + - name: Install dependencies + run: npm install + + - name: Build library + run: npm run release + + - name: Run unit tests + run: npm run test + + # - name: Run e2e tests + # run: npm run test:e2e + + # - name: Submit to Codecov + # run: npm run codecov + diff --git a/.gitignore b/.gitignore index 2b7422568..484212c33 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ test/e2e/generated samples/generated samples/swagger-codegen-cli-v2.jar samples/swagger-codegen-cli-v3.jar +.env diff --git a/README.md b/README.md index 83f0ae233..b0d3ad873 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ $ openapi --help --useOptions Use options instead of arguments --useUnionTypes Use union types instead of enums --exportCore Write core files to disk (default: true) - --exportServices Write services to disk (default: true) - --exportModels Write models to disk (default: true) + --exportServices Write services to disk [true, false, regexp] (default: true) + --exportModels Write models to disk [true, false, regexp] (default: true) --exportSchemas Write schemas to disk (default: false) --indent Indentation options [4, 2, tab] (default: "4") --postfixServices Service name postfix (default: "Service") diff --git a/bin/index.js b/bin/index.js index 32f2fecbc..ce1c2ea81 100755 --- a/bin/index.js +++ b/bin/index.js @@ -16,6 +16,7 @@ const params = program .option('--name ', 'Custom client class name') .option('--useOptions', 'Use options instead of arguments') .option('--useUnionTypes', 'Use union types instead of enums') + .option('--autoformat', 'Process generated files with autoformatter', false) .option('--exportCore ', 'Write core files to disk', true) .option('--exportServices ', 'Write services to disk', true) .option('--exportModels ', 'Write models to disk', true) @@ -29,6 +30,14 @@ const params = program const OpenAPI = require(path.resolve(__dirname, '../dist/index.js')); +const parseBooleanOrString = value => { + try { + return JSON.parse(value) === true; + } catch (error) { + return value; + } +}; + if (OpenAPI) { OpenAPI.generate({ input: params.input, @@ -37,9 +46,10 @@ if (OpenAPI) { clientName: params.name, useOptions: params.useOptions, useUnionTypes: params.useUnionTypes, + autoformat: JSON.parse(params.autoformat) === true, exportCore: JSON.parse(params.exportCore) === true, - exportServices: JSON.parse(params.exportServices) === true, - exportModels: JSON.parse(params.exportModels) === true, + exportServices: parseBooleanOrString(params.exportServices), + exportModels: parseBooleanOrString(params.exportModels), exportSchemas: JSON.parse(params.exportSchemas) === true, indent: params.indent, postfixServices: params.postfixServices, diff --git a/bin/index.spec.js b/bin/index.spec.js index 6030c07c8..66d5bcd9f 100755 --- a/bin/index.spec.js +++ b/bin/index.spec.js @@ -43,6 +43,35 @@ describe('bin', () => { expect(result.stderr.toString()).toBe(''); }); + it('it should support regexp params', async () => { + const result = crossSpawn.sync('node', [ + './bin/index.js', + '--input', + './test/spec/v3.json', + '--output', + './test/generated/bin', + '--exportServices', + '^(Simple|Types)', + '--exportModels', + '^(Simple|Types)', + ]); + expect(result.stdout.toString()).toBe(''); + expect(result.stderr.toString()).toBe(''); + }); + + it('should autoformat with Prettier', async () => { + const result = crossSpawn.sync('node', [ + './bin/index.js', + '--input', + './test/spec/v3.json', + '--output', + './test/generated/bin', + '--autoformat', + ]); + expect(result.stdout.toString()).toBe(''); + expect(result.stderr.toString()).toBe(''); + }); + it('it should throw error without params', async () => { const result = crossSpawn.sync('node', ['./bin/index.js']); expect(result.stdout.toString()).toBe(''); diff --git a/package-lock.json b/package-lock.json index 143366cfc..e7767addd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "openapi-typescript-codegen", - "version": "0.27.0", + "name": "@nicolas-chaulet/openapi-typescript-codegen", + "version": "0.27.6", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "openapi-typescript-codegen", - "version": "0.27.0", + "name": "@nicolas-chaulet/openapi-typescript-codegen", + "version": "0.27.6", "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^10.1.0", diff --git a/package.json b/package.json index 97ea89bca..ce1bd9623 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "openapi-typescript-codegen", - "version": "0.27.0", + "name": "@nicolas-chaulet/openapi-typescript-codegen", + "version": "0.27.6", "description": "Library that generates Typescript clients based on the OpenAPI specification.", "author": "Ferdi Koomen", - "homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen", + "homepage": "https://github.com/CanoaPBC/openapi-typescript-codegen", "repository": { "type": "git", - "url": "git+https://github.com/ferdikoomen/openapi-typescript-codegen.git" + "url": "git+https://github.com/CanoaPBC/openapi-typescript-codegen.git" }, "bugs": { "url": "https://github.com/ferdikoomen/openapi-typescript-codegen/issues" diff --git a/src/client/interfaces/Model.d.ts b/src/client/interfaces/Model.d.ts index 5f0318942..90ddb26f4 100644 --- a/src/client/interfaces/Model.d.ts +++ b/src/client/interfaces/Model.d.ts @@ -3,7 +3,17 @@ import type { Schema } from './Schema'; export interface Model extends Schema { name: string; - export: 'reference' | 'generic' | 'enum' | 'array' | 'dictionary' | 'interface' | 'one-of' | 'any-of' | 'all-of'; + export: + | 'reference' + | 'generic' + | 'enum' + | 'array' + | 'dictionary' + | 'interface' + | 'one-of' + | 'any-of' + | 'all-of' + | 'const'; type: string; base: string; template: string | null; diff --git a/src/client/interfaces/ModelComposition.d.ts b/src/client/interfaces/ModelComposition.d.ts index f17fc5e64..b872651d2 100644 --- a/src/client/interfaces/ModelComposition.d.ts +++ b/src/client/interfaces/ModelComposition.d.ts @@ -1,8 +1,8 @@ import type { Model } from './Model'; export interface ModelComposition { - type: 'one-of' | 'any-of' | 'all-of'; - imports: string[]; enums: Model[]; + export: 'one-of' | 'any-of' | 'all-of'; + imports: string[]; properties: Model[]; } diff --git a/src/index.ts b/src/index.ts index e63919085..f6b9b27b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,9 +19,10 @@ export type Options = { clientName?: string; useOptions?: boolean; useUnionTypes?: boolean; + autoformat?: boolean; exportCore?: boolean; - exportServices?: boolean; - exportModels?: boolean; + exportServices?: boolean | string; + exportModels?: boolean | string; exportSchemas?: boolean; indent?: Indent; postfixServices?: string; @@ -57,6 +58,7 @@ export const generate = async ({ clientName, useOptions = false, useUnionTypes = false, + autoformat = false, exportCore = true, exportServices = true, exportModels = true, @@ -75,35 +77,24 @@ export const generate = async ({ useOptions, }); + let parser: typeof parseV2 | typeof parseV3; + switch (openApiVersion) { case OpenApiVersion.V2: { - const client = parseV2(openApi); - const clientFinal = postProcessClient(client); - if (!write) break; - await writeClient( - clientFinal, - templates, - output, - httpClient, - useOptions, - useUnionTypes, - exportCore, - exportServices, - exportModels, - exportSchemas, - indent, - postfixServices, - postfixModels, - clientName, - request - ); + parser = parseV2; break; } case OpenApiVersion.V3: { - const client = parseV3(openApi); - const clientFinal = postProcessClient(client); - if (!write) break; + parser = parseV3; + break; + } + } + + if (parser) { + const client = parser(openApi); + const clientFinal = postProcessClient(client); + if (write) { await writeClient( clientFinal, templates, @@ -111,6 +102,7 @@ export const generate = async ({ httpClient, useOptions, useUnionTypes, + autoformat, exportCore, exportServices, exportModels, @@ -121,7 +113,6 @@ export const generate = async ({ clientName, request ); - break; } } }; diff --git a/src/openApi/v2/parser/escapeName.ts b/src/openApi/v2/parser/escapeName.ts index 9d6816c10..1fb196c63 100644 --- a/src/openApi/v2/parser/escapeName.ts +++ b/src/openApi/v2/parser/escapeName.ts @@ -1,6 +1,8 @@ +import validTypescriptIdentifierRegex from '../../../utils/validTypescriptIdentifierRegex'; + export const escapeName = (value: string): string => { if (value || value === '') { - const validName = /^[a-zA-Z_$][\w$]+$/g.test(value); + const validName = validTypescriptIdentifierRegex.test(value); if (!validName) { return `'${value}'`; } diff --git a/src/openApi/v2/parser/getEnum.ts b/src/openApi/v2/parser/getEnum.ts index 64c7ca8b5..eb671f679 100644 --- a/src/openApi/v2/parser/getEnum.ts +++ b/src/openApi/v2/parser/getEnum.ts @@ -1,4 +1,5 @@ import type { Enum } from '../../../client/interfaces/Enum'; +import sanitizeEnumName from '../../../utils/sanitizeEnumName'; export const getEnum = (values?: (string | number)[]): Enum[] => { if (Array.isArray(values)) { @@ -19,11 +20,7 @@ export const getEnum = (values?: (string | number)[]): Enum[] => { }; } return { - name: String(value) - .replace(/\W+/g, '_') - .replace(/^(\d+)/g, '_$1') - .replace(/([a-z])([A-Z]+)/g, '$1_$2') - .toUpperCase(), + name: sanitizeEnumName(String(value)), value: `'${value.replace(/'/g, "\\'")}'`, type: 'string', description: null, diff --git a/src/openApi/v2/parser/getModel.ts b/src/openApi/v2/parser/getModel.ts index 22f3528aa..53495b08f 100644 --- a/src/openApi/v2/parser/getModel.ts +++ b/src/openApi/v2/parser/getModel.ts @@ -112,7 +112,7 @@ export const getModel = ( if (definition.allOf?.length) { const composition = getModelComposition(openApi, definition, definition.allOf, 'all-of', getModel); - model.export = composition.type; + model.export = composition.export; model.imports.push(...composition.imports); model.properties.push(...composition.properties); model.enums.push(...composition.enums); diff --git a/src/openApi/v2/parser/getModelComposition.ts b/src/openApi/v2/parser/getModelComposition.ts index 6b5e4c305..41a3eed94 100644 --- a/src/openApi/v2/parser/getModelComposition.ts +++ b/src/openApi/v2/parser/getModelComposition.ts @@ -17,9 +17,9 @@ export const getModelComposition = ( getModel: GetModelFn ): ModelComposition => { const composition: ModelComposition = { - type, - imports: [], enums: [], + export: type, + imports: [], properties: [], }; diff --git a/src/openApi/v2/parser/getOperationResponse.ts b/src/openApi/v2/parser/getOperationResponse.ts index 8f6c3ca56..0c5e2bfa4 100644 --- a/src/openApi/v2/parser/getOperationResponse.ts +++ b/src/openApi/v2/parser/getOperationResponse.ts @@ -18,8 +18,8 @@ export const getOperationResponse = ( code: responseCode, description: response.description || null, export: 'generic', - type: 'any', - base: 'any', + type: responseCode !== 204 ? 'any' : 'void', + base: responseCode !== 204 ? 'any' : 'void', template: null, link: null, isDefinition: false, diff --git a/src/openApi/v2/parser/getOperationResults.ts b/src/openApi/v2/parser/getOperationResults.ts index 9d8111fe8..997c2059c 100644 --- a/src/openApi/v2/parser/getOperationResults.ts +++ b/src/openApi/v2/parser/getOperationResults.ts @@ -12,36 +12,14 @@ const areEqual = (a: Model, b: Model): boolean => { export const getOperationResults = (operationResponses: OperationResponse[]): OperationResponse[] => { const operationResults: OperationResponse[] = []; - // Filter out success response codes, but skip "204 No Content" + // Filter out success response codes operationResponses.forEach(operationResponse => { const { code } = operationResponse; - if (code && code !== 204 && code >= 200 && code < 300) { + if (code && code >= 200 && code < 300) { operationResults.push(operationResponse); } }); - if (!operationResults.length) { - operationResults.push({ - in: 'response', - name: '', - code: 200, - description: '', - export: 'generic', - type: 'void', - base: 'void', - template: null, - link: null, - isDefinition: false, - isReadOnly: false, - isRequired: false, - isNullable: false, - imports: [], - enum: [], - enums: [], - properties: [], - }); - } - return operationResults.filter((operationResult, index, arr) => { return ( arr.findIndex(item => { diff --git a/src/openApi/v2/parser/getType.ts b/src/openApi/v2/parser/getType.ts index 6caa1e015..594fa6106 100644 --- a/src/openApi/v2/parser/getType.ts +++ b/src/openApi/v2/parser/getType.ts @@ -1,10 +1,9 @@ import type { Type } from '../../../client/interfaces/Type'; +import sanitizeTypeName from '../../../utils/sanitizeTypeName'; import { getMappedType } from './getMappedType'; import { stripNamespace } from './stripNamespace'; -const encode = (value: string): string => { - return value.replace(/^[^a-zA-Z_$]+/g, '').replace(/[^\w$]+/g, '_'); -}; +const encode = (value: string): string => sanitizeTypeName(value); /** * Parse any string value into a type object. diff --git a/src/openApi/v3/interfaces/OpenApiRequestBody.d.ts b/src/openApi/v3/interfaces/OpenApiRequestBody.d.ts index 1a687b8b9..fc323dba1 100644 --- a/src/openApi/v3/interfaces/OpenApiRequestBody.d.ts +++ b/src/openApi/v3/interfaces/OpenApiRequestBody.d.ts @@ -6,8 +6,9 @@ import type { OpenApiReference } from './OpenApiReference'; * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#requestBodyObject */ export interface OpenApiRequestBody extends OpenApiReference { - description?: string; + 'x-body-name'?: string; content: Dictionary; - required?: boolean; + description?: string; nullable?: boolean; + required?: boolean; } diff --git a/src/openApi/v3/interfaces/OpenApiSchema.d.ts b/src/openApi/v3/interfaces/OpenApiSchema.d.ts index a51456f3b..ff1b63b59 100644 --- a/src/openApi/v3/interfaces/OpenApiSchema.d.ts +++ b/src/openApi/v3/interfaces/OpenApiSchema.d.ts @@ -26,6 +26,7 @@ export interface OpenApiSchema extends OpenApiReference, WithEnumExtension { required?: string[]; enum?: (string | number)[]; type?: string | string[]; + const?: string | number | boolean | null; allOf?: OpenApiSchema[]; oneOf?: OpenApiSchema[]; anyOf?: OpenApiSchema[]; diff --git a/src/openApi/v3/parser/escapeName.ts b/src/openApi/v3/parser/escapeName.ts index 9d6816c10..1fb196c63 100644 --- a/src/openApi/v3/parser/escapeName.ts +++ b/src/openApi/v3/parser/escapeName.ts @@ -1,6 +1,8 @@ +import validTypescriptIdentifierRegex from '../../../utils/validTypescriptIdentifierRegex'; + export const escapeName = (value: string): string => { if (value || value === '') { - const validName = /^[a-zA-Z_$][\w$]+$/g.test(value); + const validName = validTypescriptIdentifierRegex.test(value); if (!validName) { return `'${value}'`; } diff --git a/src/openApi/v3/parser/getEnum.ts b/src/openApi/v3/parser/getEnum.ts index 64c7ca8b5..eb671f679 100644 --- a/src/openApi/v3/parser/getEnum.ts +++ b/src/openApi/v3/parser/getEnum.ts @@ -1,4 +1,5 @@ import type { Enum } from '../../../client/interfaces/Enum'; +import sanitizeEnumName from '../../../utils/sanitizeEnumName'; export const getEnum = (values?: (string | number)[]): Enum[] => { if (Array.isArray(values)) { @@ -19,11 +20,7 @@ export const getEnum = (values?: (string | number)[]): Enum[] => { }; } return { - name: String(value) - .replace(/\W+/g, '_') - .replace(/^(\d+)/g, '_$1') - .replace(/([a-z])([A-Z]+)/g, '$1_$2') - .toUpperCase(), + name: sanitizeEnumName(String(value)), value: `'${value.replace(/'/g, "\\'")}'`, type: 'string', description: null, diff --git a/src/openApi/v3/parser/getModel.spec.ts b/src/openApi/v3/parser/getModel.spec.ts new file mode 100644 index 000000000..efb720a40 --- /dev/null +++ b/src/openApi/v3/parser/getModel.spec.ts @@ -0,0 +1,92 @@ +import { reservedWords } from '../../../utils/reservedWords'; +import { getModel } from './getModel'; +import { getType } from './getType'; + +const openApi = { + openapi: '3.0', + info: { + title: 'dummy', + version: '1.0', + }, + paths: {}, + servers: [ + { + url: 'https://localhost:8080/api', + }, + ], + components: { + schemas: { + Enum1: { + enum: ['Bird', 'Dog'], + type: 'string', + }, + ConstValue: { + type: 'string', + const: 'ConstValue', + }, + CompositionWithAnyOfAndNull: { + description: + "This is a model with one property with a 'any of' relationship where the options are not $ref", + type: 'object', + properties: { + propA: { + anyOf: [ + { + items: { + anyOf: [ + { + $ref: '#/components/schemas/Enum1', + }, + { + $ref: '#/components/schemas/ConstValue', + }, + ], + }, + type: 'array', + }, + { + type: 'null', + }, + ], + }, + }, + }, + CompositionWithAny: { + description: + "This is a model with one property with a 'any of' relationship where the options are not $ref", + type: 'object', + properties: { + propA: { + anyOf: [ + { + $ref: '#/components/schemas/Enum1', + }, + { + $ref: '#/components/schemas/ConstValue', + }, + { + type: 'null', + }, + ], + }, + }, + }, + }, + }, +}; + +describe('getModel', () => { + it('Parses any of', () => { + const definition = openApi.components.schemas.CompositionWithAnyOfAndNull; + const definitionType = getType('CompositionWithAnyOfAndNull'); + const model = getModel(openApi, definition, true, definitionType.base.replace(reservedWords, '_$1')); + expect(model.properties[0].properties.length).toBe(2); + }); + + it('Parses any of 2', () => { + const definition = openApi.components.schemas.CompositionWithAny; + const definitionType = getType('CompositionWithAny'); + const model = getModel(openApi, definition, true, definitionType.base.replace(reservedWords, '_$1')); + expect(model.properties[0].properties.length).toBe(3); + }); +}); diff --git a/src/openApi/v3/parser/getModel.ts b/src/openApi/v3/parser/getModel.ts index 9e9c60a98..8c79d8397 100644 --- a/src/openApi/v3/parser/getModel.ts +++ b/src/openApi/v3/parser/getModel.ts @@ -4,16 +4,17 @@ import type { OpenApi } from '../interfaces/OpenApi'; import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; import { extendEnum } from './extendEnum'; import { getEnum } from './getEnum'; -import { getModelComposition } from './getModelComposition'; +import { findModelComposition, getModelComposition } from './getModelComposition'; import { getModelDefault } from './getModelDefault'; -import { getModelProperties } from './getModelProperties'; +import { getAdditionalPropertiesModel, getModelProperties } from './getModelProperties'; import { getType } from './getType'; export const getModel = ( openApi: OpenApi, definition: OpenApiSchema, isDefinition: boolean = false, - name: string = '' + name: string = '', + parentDefinition: OpenApiSchema | null = null ): Model => { const model: Model = { name, @@ -82,71 +83,36 @@ export const getModel = ( model.imports.push(...arrayItems.imports); model.default = getModelDefault(definition, model); return model; - } else { - const arrayItems = getModel(openApi, definition.items); - model.export = 'array'; - model.type = arrayItems.type; - model.base = arrayItems.base; - model.template = arrayItems.template; - model.link = arrayItems; - model.imports.push(...arrayItems.imports); - model.default = getModelDefault(definition, model); - return model; } - } - if ( - definition.type === 'object' && - (typeof definition.additionalProperties === 'object' || definition.additionalProperties === true) - ) { - const ap = typeof definition.additionalProperties === 'object' ? definition.additionalProperties : {}; - if (ap.$ref) { - const additionalProperties = getType(ap.$ref); - model.export = 'dictionary'; - model.type = additionalProperties.type; - model.base = additionalProperties.base; - model.template = additionalProperties.template; - model.imports.push(...additionalProperties.imports); - model.default = getModelDefault(definition, model); - return model; - } else { - const additionalProperties = getModel(openApi, ap); - model.export = 'dictionary'; - model.type = additionalProperties.type; - model.base = additionalProperties.base; - model.template = additionalProperties.template; - model.link = additionalProperties; - model.imports.push(...additionalProperties.imports); - model.default = getModelDefault(definition, model); - return model; + if (definition.items.anyOf && parentDefinition && parentDefinition.type) { + const foundComposition = findModelComposition(parentDefinition); + if (foundComposition && foundComposition.definitions.some(definition => definition.type !== 'array')) { + return getModel(openApi, definition.items); + } } - } - if (definition.oneOf?.length) { - const composition = getModelComposition(openApi, definition, definition.oneOf, 'one-of', getModel); - model.export = composition.type; - model.imports.push(...composition.imports); - model.properties.push(...composition.properties); - model.enums.push(...composition.enums); - return model; - } - - if (definition.anyOf?.length) { - const composition = getModelComposition(openApi, definition, definition.anyOf, 'any-of', getModel); - model.export = composition.type; - model.imports.push(...composition.imports); - model.properties.push(...composition.properties); - model.enums.push(...composition.enums); + const arrayItems = getModel(openApi, definition.items); + model.export = 'array'; + model.type = arrayItems.type; + model.base = arrayItems.base; + model.template = arrayItems.template; + model.link = arrayItems; + model.imports.push(...arrayItems.imports); + model.default = getModelDefault(definition, model); return model; } - if (definition.allOf?.length) { - const composition = getModelComposition(openApi, definition, definition.allOf, 'all-of', getModel); - model.export = composition.type; - model.imports.push(...composition.imports); - model.properties.push(...composition.properties); - model.enums.push(...composition.enums); - return model; + const foundComposition = findModelComposition(definition); + if (foundComposition) { + const composition = getModelComposition({ + ...foundComposition, + definition, + getModel, + model, + openApi, + }); + return { ...model, ...composition }; } if (definition.type === 'object') { @@ -165,18 +131,25 @@ export const getModel = ( model.enums.push(modelProperty); } }); - return model; - } else { - const additionalProperties = getModel(openApi, {}); - model.export = 'dictionary'; - model.type = additionalProperties.type; - model.base = additionalProperties.base; - model.template = additionalProperties.template; - model.link = additionalProperties; - model.imports.push(...additionalProperties.imports); - model.default = getModelDefault(definition, model); + + if (definition.additionalProperties === true) { + const modelProperty = getAdditionalPropertiesModel(openApi, definition, getModel, model); + model.properties.push(modelProperty); + } + return model; } + + return getAdditionalPropertiesModel(openApi, definition, getModel, model); + } + + if (definition.const !== undefined) { + model.export = 'const'; + const definitionConst = definition.const; + const modelConst = typeof definitionConst === 'string' ? `"${definitionConst}"` : `${definitionConst}`; + model.type = modelConst; + model.base = modelConst; + return model; } // If the schema has a type than it can be a basic or generic type. diff --git a/src/openApi/v3/parser/getModelComposition.ts b/src/openApi/v3/parser/getModelComposition.ts index 2c27d1815..c6d4fca72 100644 --- a/src/openApi/v3/parser/getModelComposition.ts +++ b/src/openApi/v3/parser/getModelComposition.ts @@ -9,32 +9,56 @@ import { getRequiredPropertiesFromComposition } from './getRequiredPropertiesFro // Fix for circular dependency export type GetModelFn = typeof getModel; -export const getModelComposition = ( - openApi: OpenApi, - definition: OpenApiSchema, - definitions: OpenApiSchema[], - type: 'one-of' | 'any-of' | 'all-of', - getModel: GetModelFn -): ModelComposition => { +type Composition = { + definitions: OpenApiSchema[]; + type: ModelComposition['export']; +}; + +export const findModelComposition = (definition: OpenApiSchema): Composition | undefined => { + const compositions: ReadonlyArray<{ + definitions: Composition['definitions'] | undefined; + type: Composition['type']; + }> = [ + { + definitions: definition.allOf, + type: 'all-of', + }, + { + definitions: definition.anyOf, + type: 'any-of', + }, + { + definitions: definition.oneOf, + type: 'one-of', + }, + ]; + return compositions.find(composition => composition.definitions?.length) as ReturnType; +}; + +export const getModelComposition = ({ + definition, + definitions, + getModel, + model, + openApi, + type, +}: Composition & { + definition: OpenApiSchema; + getModel: GetModelFn; + model: Model; + openApi: OpenApi; +}): ModelComposition => { const composition: ModelComposition = { - type, - imports: [], - enums: [], - properties: [], + enums: model.enums, + export: type, + imports: model.imports, + properties: model.properties, }; const properties: Model[] = []; definitions - .map(definition => getModel(openApi, definition)) - .filter(model => { - const hasProperties = model.properties.length; - const hasEnums = model.enums.length; - const isObject = model.type === 'any'; - const isDictionary = model.export === 'dictionary'; - const isEmpty = isObject && !hasProperties && !hasEnums; - return !isEmpty || isDictionary; - }) + .map(def => getModel(openApi, def, undefined, undefined, definition)) .forEach(model => { composition.imports.push(...model.imports); composition.enums.push(...model.enums); diff --git a/src/openApi/v3/parser/getModelProperties.ts b/src/openApi/v3/parser/getModelProperties.ts index 6e25ca833..e054bf9ed 100644 --- a/src/openApi/v3/parser/getModelProperties.ts +++ b/src/openApi/v3/parser/getModelProperties.ts @@ -5,11 +5,50 @@ import type { OpenApi } from '../interfaces/OpenApi'; import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; import { escapeName } from './escapeName'; import type { getModel } from './getModel'; +import { getModelDefault } from './getModelDefault'; import { getType } from './getType'; // Fix for circular dependency export type GetModelFn = typeof getModel; +export const getAdditionalPropertiesModel = ( + openApi: OpenApi, + definition: OpenApiSchema, + getModel: GetModelFn, + model: Model +): Model => { + const ap = typeof definition.additionalProperties === 'object' ? definition.additionalProperties : {}; + const apModel = getModel(openApi, ap); + + if (definition.additionalProperties === true && definition.properties) { + apModel.default = getModelDefault(definition, model); + apModel.export = 'generic'; + apModel.isRequired = true; + apModel.name = '[key: string]'; + return apModel; + } + + if (ap.$ref) { + const apType = getType(ap.$ref); + model.base = apType.base; + model.default = getModelDefault(definition, model); + model.export = 'dictionary'; + model.imports.push(...apType.imports); + model.template = apType.template; + model.type = apType.type; + return model; + } + + model.base = apModel.base; + model.default = getModelDefault(definition, model); + model.export = 'dictionary'; + model.imports.push(...apModel.imports); + model.link = apModel; + model.template = apModel.template; + model.type = apModel.type; + return model; +}; + export const getModelProperties = ( openApi: OpenApi, definition: OpenApiSchema, diff --git a/src/openApi/v3/parser/getOperationRequestBody.ts b/src/openApi/v3/parser/getOperationRequestBody.ts index 9f9cca241..b27ab1284 100644 --- a/src/openApi/v3/parser/getOperationRequestBody.ts +++ b/src/openApi/v3/parser/getOperationRequestBody.ts @@ -10,8 +10,8 @@ export const getOperationRequestBody = (openApi: OpenApi, body: OpenApiRequestBo const requestBody: OperationParameter = { in: 'body', export: 'interface', - prop: 'requestBody', - name: 'requestBody', + prop: body['x-body-name'] ?? 'requestBody', + name: body['x-body-name'] ?? 'requestBody', type: 'any', base: 'any', template: null, diff --git a/src/openApi/v3/parser/getOperationResponse.ts b/src/openApi/v3/parser/getOperationResponse.ts index dff19ec13..0fe5620c4 100644 --- a/src/openApi/v3/parser/getOperationResponse.ts +++ b/src/openApi/v3/parser/getOperationResponse.ts @@ -19,8 +19,8 @@ export const getOperationResponse = ( code: responseCode, description: response.description || null, export: 'generic', - type: 'any', - base: 'any', + type: responseCode !== 204 ? 'any' : 'void', + base: responseCode !== 204 ? 'any' : 'void', template: null, link: null, isDefinition: false, diff --git a/src/openApi/v3/parser/getOperationResults.ts b/src/openApi/v3/parser/getOperationResults.ts index 9d8111fe8..997c2059c 100644 --- a/src/openApi/v3/parser/getOperationResults.ts +++ b/src/openApi/v3/parser/getOperationResults.ts @@ -12,36 +12,14 @@ const areEqual = (a: Model, b: Model): boolean => { export const getOperationResults = (operationResponses: OperationResponse[]): OperationResponse[] => { const operationResults: OperationResponse[] = []; - // Filter out success response codes, but skip "204 No Content" + // Filter out success response codes operationResponses.forEach(operationResponse => { const { code } = operationResponse; - if (code && code !== 204 && code >= 200 && code < 300) { + if (code && code >= 200 && code < 300) { operationResults.push(operationResponse); } }); - if (!operationResults.length) { - operationResults.push({ - in: 'response', - name: '', - code: 200, - description: '', - export: 'generic', - type: 'void', - base: 'void', - template: null, - link: null, - isDefinition: false, - isReadOnly: false, - isRequired: false, - isNullable: false, - imports: [], - enum: [], - enums: [], - properties: [], - }); - } - return operationResults.filter((operationResult, index, arr) => { return ( arr.findIndex(item => { diff --git a/src/openApi/v3/parser/getType.ts b/src/openApi/v3/parser/getType.ts index e8ef4733d..4cb82d367 100644 --- a/src/openApi/v3/parser/getType.ts +++ b/src/openApi/v3/parser/getType.ts @@ -1,11 +1,10 @@ import type { Type } from '../../../client/interfaces/Type'; import { isDefined } from '../../../utils/isDefined'; +import sanitizeTypeName from '../../../utils/sanitizeTypeName'; import { getMappedType } from './getMappedType'; import { stripNamespace } from './stripNamespace'; -const encode = (value: string): string => { - return value.replace(/^[^a-zA-Z_$]+/g, '').replace(/[^\w$]+/g, '_'); -}; +const encode = (value: string): string => sanitizeTypeName(value); /** * Parse any string value into a type object. diff --git a/src/utils/sanitizeEnumName.spec.ts b/src/utils/sanitizeEnumName.spec.ts new file mode 100644 index 000000000..36db89743 --- /dev/null +++ b/src/utils/sanitizeEnumName.spec.ts @@ -0,0 +1,11 @@ +import sanitizeEnumName from './sanitizeEnumName'; + +describe('sanitizeEnumName', () => { + it('should replace illegal characters', () => { + expect(sanitizeEnumName('abc')).toEqual('ABC'); + expect(sanitizeEnumName('æbc')).toEqual('ÆBC'); + expect(sanitizeEnumName('æb.c')).toEqual('ÆB_C'); + expect(sanitizeEnumName('1æb.c')).toEqual('_1ÆB_C'); + expect(sanitizeEnumName("'quoted'")).toEqual('_QUOTED_'); + }); +}); diff --git a/src/utils/sanitizeEnumName.ts b/src/utils/sanitizeEnumName.ts new file mode 100644 index 000000000..1f1a6080c --- /dev/null +++ b/src/utils/sanitizeEnumName.ts @@ -0,0 +1,18 @@ +/** + * Sanitizes names of enums, so they are valid typescript identifiers of a certain form. + * + * 1: Replace all characters not legal as part of identifier with '_' + * 2: Add '_' prefix if first character of enum name has character not legal for start of identifier + * 3: Add '_' where the string transitions from lowercase to uppercase + * 4: Transform the whole string to uppercase + * + * Javascript identifier regexp pattern retrieved from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers + */ +const sanitizeEnumName = (name: string) => + name + .replace(/[^$\u200c\u200d\p{ID_Continue}]/gu, '_') + .replace(/^([^$_\p{ID_Start}])/u, '_$1') + .replace(/(\p{Lowercase})(\p{Uppercase}+)/gu, '$1_$2') + .toUpperCase(); + +export default sanitizeEnumName; diff --git a/src/utils/sanitizeTypeName.spec.ts b/src/utils/sanitizeTypeName.spec.ts new file mode 100644 index 000000000..d01c52cb7 --- /dev/null +++ b/src/utils/sanitizeTypeName.spec.ts @@ -0,0 +1,10 @@ +import sanitizeTypeName from './sanitizeTypeName'; + +describe('sanitizeTypeName', () => { + it('should remove/replace illegal characters', () => { + expect(sanitizeTypeName('abc')).toEqual('abc'); + expect(sanitizeTypeName('æbc')).toEqual('æbc'); + expect(sanitizeTypeName('æb.c')).toEqual('æb_c'); + expect(sanitizeTypeName('1æb.c')).toEqual('æb_c'); + }); +}); diff --git a/src/utils/sanitizeTypeName.ts b/src/utils/sanitizeTypeName.ts new file mode 100644 index 000000000..3c0a2079c --- /dev/null +++ b/src/utils/sanitizeTypeName.ts @@ -0,0 +1,16 @@ +/** + * Sanitizes names of types, so they are valid typescript identifiers of a certain form. + * + * 1: Remove any leading characters that are illegal as starting character of a typescript identifier. + * 2: Replace illegal characters in remaining part of type name with underscore (_). + * + * Step 1 should perhaps instead also replace illegal characters with underscore, or prefix with it, like sanitizeEnumName + * does. The way this is now one could perhaps end up removing all characters, if all are illegal start characters. It + * would be sort of a breaking change to do so, though, previously generated code might change then. + * + * Javascript identifier regexp pattern retrieved from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers + */ +const sanitizeTypeName = (name: string) => + name.replace(/^[^$_\p{ID_Start}]+/u, '').replace(/[^$\u200c\u200d\p{ID_Continue}]/gu, '_'); + +export default sanitizeTypeName; diff --git a/src/utils/validTypescriptIdentifierRegex.ts b/src/utils/validTypescriptIdentifierRegex.ts new file mode 100644 index 000000000..80501679b --- /dev/null +++ b/src/utils/validTypescriptIdentifierRegex.ts @@ -0,0 +1,4 @@ +// Javascript identifier regexp pattern retrieved from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers +const validTypescriptIdentifierRegex = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u; + +export default validTypescriptIdentifierRegex; diff --git a/src/utils/writeClient.spec.ts b/src/utils/writeClient.spec.ts index 3c06a95a5..2bfc3e284 100644 --- a/src/utils/writeClient.spec.ts +++ b/src/utils/writeClient.spec.ts @@ -43,6 +43,7 @@ describe('writeClient', () => { HttpClient.FETCH, false, false, + false, true, true, true, diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index cea2f3d88..c1c06ba39 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -1,3 +1,5 @@ +import { spawnSync } from 'child_process'; +import { createRequire } from 'module'; import { resolve } from 'path'; import type { Client } from '../client/interfaces/Client'; @@ -26,7 +28,6 @@ import { writeClientServices } from './writeClientServices'; * @param exportServices Generate services * @param exportModels Generate models * @param exportSchemas Generate schemas - * @param exportSchemas Generate schemas * @param indent Indentation options (4, 2 or tab) * @param postfixServices Service name postfix * @param postfixModels Model name postfix @@ -40,9 +41,10 @@ export const writeClient = async ( httpClient: HttpClient, useOptions: boolean, useUnionTypes: boolean, + autoformat: boolean, exportCore: boolean, - exportServices: boolean, - exportModels: boolean, + exportServices: boolean | string, + exportModels: boolean | string, exportSchemas: boolean, indent: Indent, postfixServices: string, @@ -60,6 +62,16 @@ export const writeClient = async ( throw new Error(`Output folder is not a subdirectory of the current working directory`); } + if (typeof exportServices === 'string') { + const regexp = new RegExp(exportServices); + client.services = client.services.filter(service => regexp.test(service.name)); + } + + if (typeof exportModels === 'string') { + const regexp = new RegExp(exportModels); + client.models = client.models.filter(model => regexp.test(model.name)); + } + if (exportCore) { await rmdir(outputPathCore); await mkdir(outputPathCore); @@ -115,4 +127,14 @@ export const writeClient = async ( clientName ); } + + if (autoformat) { + const pathPackageJson = resolve(process.cwd(), 'package.json'); + const require = createRequire('/'); + const json = require(pathPackageJson); + const usesPrettier = [json.dependencies, json.devDependencies].some(deps => Boolean(deps.prettier)); + if (usesPrettier) { + spawnSync('prettier', ['--ignore-unknown', '--write', output]); + } + } }; diff --git a/src/utils/writeClientIndex.ts b/src/utils/writeClientIndex.ts index 5044294d5..5f0ced589 100644 --- a/src/utils/writeClientIndex.ts +++ b/src/utils/writeClientIndex.ts @@ -29,8 +29,8 @@ export const writeClientIndex = async ( outputPath: string, useUnionTypes: boolean, exportCore: boolean, - exportServices: boolean, - exportModels: boolean, + exportServices: boolean | string, + exportModels: boolean | string, exportSchemas: boolean, postfixServices: string, postfixModels: string, diff --git a/test/__snapshots__/index.spec.ts.snap b/test/__snapshots__/index.spec.ts.snap index e1c221495..ba55abc6e 100644 --- a/test/__snapshots__/index.spec.ts.snap +++ b/test/__snapshots__/index.spec.ts.snap @@ -614,6 +614,7 @@ export type { ModelWithPattern } from './models/ModelWithPattern'; export type { ModelWithProperties } from './models/ModelWithProperties'; export type { ModelWithReference } from './models/ModelWithReference'; export type { ModelWithString } from './models/ModelWithString'; +export type { NonAsciiStringæøåÆØÅöôêÊ字符串 } from './models/NonAsciiStringæøåÆØÅöôêÊ字符串'; export type { SimpleBoolean } from './models/SimpleBoolean'; export type { SimpleFile } from './models/SimpleFile'; export type { SimpleInteger } from './models/SimpleInteger'; @@ -663,6 +664,7 @@ export { $ModelWithPattern } from './schemas/$ModelWithPattern'; export { $ModelWithProperties } from './schemas/$ModelWithProperties'; export { $ModelWithReference } from './schemas/$ModelWithReference'; export { $ModelWithString } from './schemas/$ModelWithString'; +export { $NonAsciiStringæøåÆØÅöôêÊ字符串 } from './schemas/$NonAsciiStringæøåÆØÅöôêÊ字符串'; export { $SimpleBoolean } from './schemas/$SimpleBoolean'; export { $SimpleFile } from './schemas/$SimpleFile'; export { $SimpleInteger } from './schemas/$SimpleInteger'; @@ -1009,6 +1011,7 @@ export enum EnumWithStrings { ERROR = 'Error', _SINGLE_QUOTE_ = '\\'Single Quote\\'', _DOUBLE_QUOTES_ = '"Double Quotes"', + NON_ASCII__ØÆÅÔÖ_ØÆÅÔÖ字符串 = 'Non-ascii: øæåôöØÆÅÔÖ字符串', } " `; @@ -1178,6 +1181,7 @@ export namespace ModelWithEnum { SUCCESS = 'Success', WARNING = 'Warning', ERROR = 'Error', + ØÆÅ字符串 = 'ØÆÅ字符串', } /** * These are the HTTP error code enums @@ -1388,6 +1392,18 @@ export type ModelWithString = { " `; +exports[`v2 should generate: test/generated/v2/models/NonAsciiStringæøåÆØÅöôêÊ字符串.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆØÅöôêÊ字符串 = string; +" +`; + exports[`v2 should generate: test/generated/v2/models/SimpleBoolean.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -2256,6 +2272,18 @@ export const $ModelWithString = { " `; +exports[`v2 should generate: test/generated/v2/schemas/$NonAsciiStringæøåÆØÅöôêÊ字符串.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $NonAsciiStringæøåÆØÅöôêÊ字符串 = { + type: 'string', + description: \`A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串)\`, +} as const; +" +`; + exports[`v2 should generate: test/generated/v2/schemas/$SimpleBoolean.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -2711,7 +2739,7 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class MultipleTags1Service { /** - * @returns void + * @returns void Success * @throws ApiError */ public static dummyA(): CancelablePromise { @@ -2721,7 +2749,7 @@ export class MultipleTags1Service { }); } /** - * @returns void + * @returns void Success * @throws ApiError */ public static dummyB(): CancelablePromise { @@ -2744,7 +2772,7 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class MultipleTags2Service { /** - * @returns void + * @returns void Success * @throws ApiError */ public static dummyA(): CancelablePromise { @@ -2754,7 +2782,7 @@ export class MultipleTags2Service { }); } /** - * @returns void + * @returns void Success * @throws ApiError */ public static dummyB(): CancelablePromise { @@ -2777,7 +2805,7 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class MultipleTags3Service { /** - * @returns void + * @returns void Success * @throws ApiError */ public static dummyB(): CancelablePromise { @@ -2800,7 +2828,7 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class NoContentService { /** - * @returns void + * @returns void Success * @throws ApiError */ public static callWithNoContentResponse(): CancelablePromise { @@ -2809,6 +2837,17 @@ export class NoContentService { url: '/api/v{api-version}/no-content', }); } + /** + * @returns any Response is a simple number + * @returns void Success + * @throws ApiError + */ + public static callWithResponseAndNoContentResponse(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + }); + } } " `; @@ -2913,6 +2952,17 @@ import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class ResponseService { + /** + * @returns any Response is a simple number + * @returns void Success + * @throws ApiError + */ + public static callWithResponseAndNoContentResponse(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + }); + } /** * @returns ModelWithString Message for default response * @throws ApiError @@ -3668,6 +3718,9 @@ export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; export type { _default } from './models/_default'; +export type { AnyOfAnyAndNull } from './models/AnyOfAnyAndNull'; +export type { AnyOfArrays } from './models/AnyOfArrays'; +export type { ArrayWithAnyOfProperties } from './models/ArrayWithAnyOfProperties'; export type { ArrayWithArray } from './models/ArrayWithArray'; export type { ArrayWithBooleans } from './models/ArrayWithBooleans'; export type { ArrayWithNumbers } from './models/ArrayWithNumbers'; @@ -3686,6 +3739,8 @@ export type { CompositionWithAllOfAndNullable } from './models/CompositionWithAl export type { CompositionWithAnyOf } from './models/CompositionWithAnyOf'; export type { CompositionWithAnyOfAndNullable } from './models/CompositionWithAnyOfAndNullable'; export type { CompositionWithAnyOfAnonymous } from './models/CompositionWithAnyOfAnonymous'; +export type { CompositionWithNestedAnyAndTypeNull } from './models/CompositionWithNestedAnyAndTypeNull'; +export type { CompositionWithNestedAnyOfAndNull } from './models/CompositionWithNestedAnyOfAndNull'; export type { CompositionWithOneOf } from './models/CompositionWithOneOf'; export type { CompositionWithOneOfAndComplexArrayDictionary } from './models/CompositionWithOneOfAndComplexArrayDictionary'; export type { CompositionWithOneOfAndNullable } from './models/CompositionWithOneOfAndNullable'; @@ -3693,12 +3748,14 @@ export type { CompositionWithOneOfAndSimpleArrayDictionary } from './models/Comp export type { CompositionWithOneOfAndSimpleDictionary } from './models/CompositionWithOneOfAndSimpleDictionary'; export type { CompositionWithOneOfAnonymous } from './models/CompositionWithOneOfAnonymous'; export type { CompositionWithOneOfDiscriminator } from './models/CompositionWithOneOfDiscriminator'; +export type { ConstValue } from './models/ConstValue'; export type { DeprecatedModel } from './models/DeprecatedModel'; export type { DictionaryWithArray } from './models/DictionaryWithArray'; export type { DictionaryWithDictionary } from './models/DictionaryWithDictionary'; export type { DictionaryWithProperties } from './models/DictionaryWithProperties'; export type { DictionaryWithReference } from './models/DictionaryWithReference'; export type { DictionaryWithString } from './models/DictionaryWithString'; +export { Enum1 } from './models/Enum1'; export type { EnumFromDescription } from './models/EnumFromDescription'; export { EnumWithExtensions } from './models/EnumWithExtensions'; export { EnumWithNumbers } from './models/EnumWithNumbers'; @@ -3711,9 +3768,11 @@ export type { ModelCircle } from './models/ModelCircle'; export type { ModelSquare } from './models/ModelSquare'; export type { ModelThatExtends } from './models/ModelThatExtends'; export type { ModelThatExtendsExtends } from './models/ModelThatExtendsExtends'; +export type { ModelWithAdditionalPropertiesEqTrue } from './models/ModelWithAdditionalPropertiesEqTrue'; export type { ModelWithArray } from './models/ModelWithArray'; export type { ModelWithBoolean } from './models/ModelWithBoolean'; export type { ModelWithCircularReference } from './models/ModelWithCircularReference'; +export type { ModelWithConst } from './models/ModelWithConst'; export type { ModelWithDictionary } from './models/ModelWithDictionary'; export type { ModelWithDuplicateImports } from './models/ModelWithDuplicateImports'; export type { ModelWithDuplicateProperties } from './models/ModelWithDuplicateProperties'; @@ -3728,6 +3787,8 @@ export type { ModelWithPattern } from './models/ModelWithPattern'; export type { ModelWithProperties } from './models/ModelWithProperties'; export type { ModelWithReference } from './models/ModelWithReference'; export type { ModelWithString } from './models/ModelWithString'; +export type { NestedAnyOfArraysNullable } from './models/NestedAnyOfArraysNullable'; +export type { NonAsciiStringæøåÆØÅöôêÊ字符串 } from './models/NonAsciiStringæøåÆØÅöôêÊ字符串'; export type { Pageable } from './models/Pageable'; export type { SimpleBoolean } from './models/SimpleBoolean'; export type { SimpleFile } from './models/SimpleFile'; @@ -3738,6 +3799,9 @@ export type { SimpleString } from './models/SimpleString'; export type { SimpleStringWithPattern } from './models/SimpleStringWithPattern'; export { $_default } from './schemas/$_default'; +export { $AnyOfAnyAndNull } from './schemas/$AnyOfAnyAndNull'; +export { $AnyOfArrays } from './schemas/$AnyOfArrays'; +export { $ArrayWithAnyOfProperties } from './schemas/$ArrayWithAnyOfProperties'; export { $ArrayWithArray } from './schemas/$ArrayWithArray'; export { $ArrayWithBooleans } from './schemas/$ArrayWithBooleans'; export { $ArrayWithNumbers } from './schemas/$ArrayWithNumbers'; @@ -3756,6 +3820,8 @@ export { $CompositionWithAllOfAndNullable } from './schemas/$CompositionWithAllO export { $CompositionWithAnyOf } from './schemas/$CompositionWithAnyOf'; export { $CompositionWithAnyOfAndNullable } from './schemas/$CompositionWithAnyOfAndNullable'; export { $CompositionWithAnyOfAnonymous } from './schemas/$CompositionWithAnyOfAnonymous'; +export { $CompositionWithNestedAnyAndTypeNull } from './schemas/$CompositionWithNestedAnyAndTypeNull'; +export { $CompositionWithNestedAnyOfAndNull } from './schemas/$CompositionWithNestedAnyOfAndNull'; export { $CompositionWithOneOf } from './schemas/$CompositionWithOneOf'; export { $CompositionWithOneOfAndComplexArrayDictionary } from './schemas/$CompositionWithOneOfAndComplexArrayDictionary'; export { $CompositionWithOneOfAndNullable } from './schemas/$CompositionWithOneOfAndNullable'; @@ -3763,12 +3829,14 @@ export { $CompositionWithOneOfAndSimpleArrayDictionary } from './schemas/$Compos export { $CompositionWithOneOfAndSimpleDictionary } from './schemas/$CompositionWithOneOfAndSimpleDictionary'; export { $CompositionWithOneOfAnonymous } from './schemas/$CompositionWithOneOfAnonymous'; export { $CompositionWithOneOfDiscriminator } from './schemas/$CompositionWithOneOfDiscriminator'; +export { $ConstValue } from './schemas/$ConstValue'; export { $DeprecatedModel } from './schemas/$DeprecatedModel'; export { $DictionaryWithArray } from './schemas/$DictionaryWithArray'; export { $DictionaryWithDictionary } from './schemas/$DictionaryWithDictionary'; export { $DictionaryWithProperties } from './schemas/$DictionaryWithProperties'; export { $DictionaryWithReference } from './schemas/$DictionaryWithReference'; export { $DictionaryWithString } from './schemas/$DictionaryWithString'; +export { $Enum1 } from './schemas/$Enum1'; export { $EnumFromDescription } from './schemas/$EnumFromDescription'; export { $EnumWithExtensions } from './schemas/$EnumWithExtensions'; export { $EnumWithNumbers } from './schemas/$EnumWithNumbers'; @@ -3781,9 +3849,11 @@ export { $ModelCircle } from './schemas/$ModelCircle'; export { $ModelSquare } from './schemas/$ModelSquare'; export { $ModelThatExtends } from './schemas/$ModelThatExtends'; export { $ModelThatExtendsExtends } from './schemas/$ModelThatExtendsExtends'; +export { $ModelWithAdditionalPropertiesEqTrue } from './schemas/$ModelWithAdditionalPropertiesEqTrue'; export { $ModelWithArray } from './schemas/$ModelWithArray'; export { $ModelWithBoolean } from './schemas/$ModelWithBoolean'; export { $ModelWithCircularReference } from './schemas/$ModelWithCircularReference'; +export { $ModelWithConst } from './schemas/$ModelWithConst'; export { $ModelWithDictionary } from './schemas/$ModelWithDictionary'; export { $ModelWithDuplicateImports } from './schemas/$ModelWithDuplicateImports'; export { $ModelWithDuplicateProperties } from './schemas/$ModelWithDuplicateProperties'; @@ -3798,6 +3868,8 @@ export { $ModelWithPattern } from './schemas/$ModelWithPattern'; export { $ModelWithProperties } from './schemas/$ModelWithProperties'; export { $ModelWithReference } from './schemas/$ModelWithReference'; export { $ModelWithString } from './schemas/$ModelWithString'; +export { $NestedAnyOfArraysNullable } from './schemas/$NestedAnyOfArraysNullable'; +export { $NonAsciiStringæøåÆØÅöôêÊ字符串 } from './schemas/$NonAsciiStringæøåÆØÅöôêÊ字符串'; export { $Pageable } from './schemas/$Pageable'; export { $SimpleBoolean } from './schemas/$SimpleBoolean'; export { $SimpleFile } from './schemas/$SimpleFile'; @@ -3843,6 +3915,53 @@ export type _default = { " `; +exports[`v3 should generate: test/generated/v3/models/AnyOfAnyAndNull.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type AnyOfAnyAndNull = { + data?: (any | null); +}; + +" +`; + +exports[`v3 should generate: test/generated/v3/models/AnyOfArrays.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * This is a simple array with any of properties + */ +export type AnyOfArrays = { + results?: Array<({ + foo?: string; + } | { + bar?: string; + })>; +}; + +" +`; + +exports[`v3 should generate: test/generated/v3/models/ArrayWithAnyOfProperties.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * This is a simple array with any of properties + */ +export type ArrayWithAnyOfProperties = Array<({ + foo?: string; +} | { + bar?: string; +})>; +" +`; + exports[`v3 should generate: test/generated/v3/models/ArrayWithArray.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -4105,6 +4224,40 @@ export type CompositionWithAnyOfAnonymous = { " `; +exports[`v3 should generate: test/generated/v3/models/CompositionWithNestedAnyAndTypeNull.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ModelWithArray } from './ModelWithArray'; +import type { ModelWithDictionary } from './ModelWithDictionary'; +/** + * This is a model with nested 'any of' property with a type null + */ +export type CompositionWithNestedAnyAndTypeNull = { + propA?: (Array<(ModelWithDictionary | null)> | Array<(ModelWithArray | null)>); +}; + +" +`; + +exports[`v3 should generate: test/generated/v3/models/CompositionWithNestedAnyOfAndNull.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ConstValue } from './ConstValue'; +import type { Enum1 } from './Enum1'; +/** + * This is a model with one property with a 'any of' relationship where the options are not $ref + */ +export type CompositionWithNestedAnyOfAndNull = { + propA?: (Array<(Enum1 | ConstValue)> | null); +}; + +" +`; + exports[`v3 should generate: test/generated/v3/models/CompositionWithOneOf.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -4221,6 +4374,15 @@ export type CompositionWithOneOfDiscriminator = (ModelCircle | ModelSquare); " `; +exports[`v3 should generate: test/generated/v3/models/ConstValue.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ConstValue = "ConstValue"; +" +`; + exports[`v3 should generate: test/generated/v3/models/DeprecatedModel.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -4306,6 +4468,18 @@ export type DictionaryWithString = Record; " `; +exports[`v3 should generate: test/generated/v3/models/Enum1.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export enum Enum1 { + BIRD = 'Bird', + DOG = 'Dog', +} +" +`; + exports[`v3 should generate: test/generated/v3/models/EnumFromDescription.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -4385,6 +4559,7 @@ export enum EnumWithStrings { ERROR = 'Error', _SINGLE_QUOTE_ = '\\'Single Quote\\'', _DOUBLE_QUOTES_ = '"Double Quotes"', + NON_ASCII__ØÆÅÔÖ_ØÆÅÔÖ字符串 = 'Non-ascii: øæåôöØÆÅÔÖ字符串', } " `; @@ -4508,6 +4683,25 @@ export type ModelThatExtendsExtends = (ModelWithString & ModelThatExtends & { " `; +exports[`v3 should generate: test/generated/v3/models/ModelWithAdditionalPropertiesEqTrue.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * This is a model with one property and additionalProperties: true + */ +export type ModelWithAdditionalPropertiesEqTrue = { + /** + * This is a simple string property + */ + prop?: string; + [key: string]: any; +}; + +" +`; + exports[`v3 should generate: test/generated/v3/models/ModelWithArray.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -4559,6 +4753,21 @@ export type ModelWithCircularReference = { " `; +exports[`v3 should generate: test/generated/v3/models/ModelWithConst.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ModelWithConst = { + String?: "String"; + number?: 0; + null?: null; + withType?: "Some string"; +}; + +" +`; + exports[`v3 should generate: test/generated/v3/models/ModelWithDictionary.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -4638,6 +4847,7 @@ export namespace ModelWithEnum { SUCCESS = 'Success', WARNING = 'Warning', ERROR = 'Error', + ØÆÅ字符串 = 'ØÆÅ字符串', } /** * These are the HTTP error code enums @@ -4857,6 +5067,31 @@ export type ModelWithString = { " `; +exports[`v3 should generate: test/generated/v3/models/NestedAnyOfArraysNullable.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type NestedAnyOfArraysNullable = { + nullableArray?: (Array<(string | boolean)> | null); +}; + +" +`; + +exports[`v3 should generate: test/generated/v3/models/NonAsciiStringæøåÆØÅöôêÊ字符串.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串) + */ +export type NonAsciiStringæøåÆØÅöôêÊ字符串 = string; + +" +`; + exports[`v3 should generate: test/generated/v3/models/Pageable.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -4971,6 +5206,86 @@ export const $_default = { " `; +exports[`v3 should generate: test/generated/v3/schemas/$AnyOfAnyAndNull.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $AnyOfAnyAndNull = { + properties: { + data: { + type: 'any-of', + contains: [{ + properties: { + }, + }, { + type: 'null', + }], + }, + }, +} as const; +" +`; + +exports[`v3 should generate: test/generated/v3/schemas/$AnyOfArrays.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $AnyOfArrays = { + description: \`This is a simple array with any of properties\`, + properties: { + results: { + type: 'array', + contains: { + type: 'any-of', + contains: [{ + properties: { + foo: { + type: 'string', + }, + }, + }, { + properties: { + bar: { + type: 'string', + }, + }, + }], + }, + }, + }, +} as const; +" +`; + +exports[`v3 should generate: test/generated/v3/schemas/$ArrayWithAnyOfProperties.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ArrayWithAnyOfProperties = { + type: 'array', + contains: { + type: 'any-of', + contains: [{ + properties: { + foo: { + type: 'string', + }, + }, + }, { + properties: { + bar: { + type: 'string', + }, + }, + }], + }, +} as const; +" +`; + exports[`v3 should generate: test/generated/v3/schemas/$ArrayWithArray.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -5303,6 +5618,72 @@ export const $CompositionWithAnyOfAnonymous = { " `; +exports[`v3 should generate: test/generated/v3/schemas/$CompositionWithNestedAnyAndTypeNull.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompositionWithNestedAnyAndTypeNull = { + description: \`This is a model with nested 'any of' property with a type null\`, + properties: { + propA: { + type: 'any-of', + contains: [{ + type: 'array', + contains: { + type: 'any-of', + contains: [{ + type: 'ModelWithDictionary', + }, { + type: 'null', + }], + }, + }, { + type: 'array', + contains: { + type: 'any-of', + contains: [{ + type: 'ModelWithArray', + }, { + type: 'null', + }], + }, + }], + }, + }, +} as const; +" +`; + +exports[`v3 should generate: test/generated/v3/schemas/$CompositionWithNestedAnyOfAndNull.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $CompositionWithNestedAnyOfAndNull = { + description: \`This is a model with one property with a 'any of' relationship where the options are not $ref\`, + properties: { + propA: { + type: 'any-of', + contains: [{ + type: 'array', + contains: { + type: 'any-of', + contains: [{ + type: 'Enum1', + }, { + type: 'ConstValue', + }], + }, + }, { + type: 'null', + }], + }, + }, +} as const; +" +`; + exports[`v3 should generate: test/generated/v3/schemas/$CompositionWithOneOf.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -5488,6 +5869,17 @@ export const $CompositionWithOneOfDiscriminator = { " `; +exports[`v3 should generate: test/generated/v3/schemas/$ConstValue.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ConstValue = { + type: '"ConstValue"', +} as const; +" +`; + exports[`v3 should generate: test/generated/v3/schemas/$DeprecatedModel.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -5588,6 +5980,17 @@ export const $DictionaryWithString = { " `; +exports[`v3 should generate: test/generated/v3/schemas/$Enum1.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $Enum1 = { + type: 'Enum', +} as const; +" +`; + exports[`v3 should generate: test/generated/v3/schemas/$EnumFromDescription.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -5806,6 +6209,27 @@ export const $ModelThatExtendsExtends = { " `; +exports[`v3 should generate: test/generated/v3/schemas/$ModelWithAdditionalPropertiesEqTrue.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithAdditionalPropertiesEqTrue = { + description: \`This is a model with one property and additionalProperties: true\`, + properties: { + prop: { + type: 'string', + description: \`This is a simple string property\`, + }, + [key: string]: { + type: 'any', + isRequired: true, + }, + }, +} as const; +" +`; + exports[`v3 should generate: test/generated/v3/schemas/$ModelWithArray.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -5870,6 +6294,30 @@ export const $ModelWithCircularReference = { " `; +exports[`v3 should generate: test/generated/v3/schemas/$ModelWithConst.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $ModelWithConst = { + properties: { + String: { + type: '"String"', + }, + number: { + type: '0', + }, + null: { + type: 'null', + }, + withType: { + type: '"Some string"', + }, + }, +} as const; +" +`; + exports[`v3 should generate: test/generated/v3/schemas/$ModelWithDictionary.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -6247,6 +6695,46 @@ export const $ModelWithString = { " `; +exports[`v3 should generate: test/generated/v3/schemas/$NestedAnyOfArraysNullable.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $NestedAnyOfArraysNullable = { + properties: { + nullableArray: { + type: 'any-of', + contains: [{ + type: 'array', + contains: { + type: 'any-of', + contains: [{ + type: 'string', + }, { + type: 'boolean', + }], + }, + }, { + type: 'null', + }], + }, + }, +} as const; +" +`; + +exports[`v3 should generate: test/generated/v3/schemas/$NonAsciiStringæøåÆØÅöôêÊ字符串.ts 1`] = ` +"/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $NonAsciiStringæøåÆØÅöôêÊ字符串 = { + type: 'string', + description: \`A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串)\`, +} as const; +" +`; + exports[`v3 should generate: test/generated/v3/schemas/$Pageable.ts 1`] = ` "/* generated using openapi-typescript-codegen -- do no edit */ /* istanbul ignore file */ @@ -6887,7 +7375,7 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class MultipleTags1Service { /** - * @returns void + * @returns void Success * @throws ApiError */ public static dummyA(): CancelablePromise { @@ -6897,7 +7385,7 @@ export class MultipleTags1Service { }); } /** - * @returns void + * @returns void Success * @throws ApiError */ public static dummyB(): CancelablePromise { @@ -6920,7 +7408,7 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class MultipleTags2Service { /** - * @returns void + * @returns void Success * @throws ApiError */ public static dummyA(): CancelablePromise { @@ -6930,7 +7418,7 @@ export class MultipleTags2Service { }); } /** - * @returns void + * @returns void Success * @throws ApiError */ public static dummyB(): CancelablePromise { @@ -6953,7 +7441,7 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class MultipleTags3Service { /** - * @returns void + * @returns void Success * @throws ApiError */ public static dummyB(): CancelablePromise { @@ -6976,7 +7464,7 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class NoContentService { /** - * @returns void + * @returns void Success * @throws ApiError */ public static callWithNoContentResponse(): CancelablePromise { @@ -6985,6 +7473,17 @@ export class NoContentService { url: '/api/v{api-version}/no-content', }); } + /** + * @returns number Response is a simple number + * @returns void Success + * @throws ApiError + */ + public static callWithResponseAndNoContentResponse(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + }); + } } " `; @@ -7141,12 +7640,12 @@ import { request as __request } from '../core/request'; export class RequestBodyService { /** * @param parameter This is a reusable parameter - * @param requestBody A reusable request body + * @param foo A reusable request body * @throws ApiError */ public static postApiRequestBody( parameter?: string, - requestBody?: ModelWithString, + foo?: ModelWithString, ): CancelablePromise { return __request(OpenAPI, { method: 'POST', @@ -7154,7 +7653,7 @@ export class RequestBodyService { query: { 'parameter': parameter, }, - body: requestBody, + body: foo, mediaType: 'application/json', }); } @@ -7174,6 +7673,17 @@ import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class ResponseService { + /** + * @returns number Response is a simple number + * @returns void Success + * @throws ApiError + */ + public static callWithResponseAndNoContentResponse(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/api/v{api-version}/multiple-tags/response-and-no-content', + }); + } /** * @returns ModelWithString * @throws ApiError diff --git a/test/index.js b/test/index.js index 6d276c412..f60c9b047 100644 --- a/test/index.js +++ b/test/index.js @@ -10,6 +10,7 @@ const generate = async (input, output) => { httpClient: OpenAPI.HttpClient.FETCH, useOptions: true, useUnionTypes: false, + autoformat: false, exportCore: true, exportSchemas: true, exportModels: true, diff --git a/test/index.spec.ts b/test/index.spec.ts index 78a0197d5..118a293ad 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -11,6 +11,7 @@ describe('v2', () => { httpClient: HttpClient.FETCH, useOptions: false, useUnionTypes: false, + autoformat: false, exportCore: true, exportSchemas: true, exportModels: true, @@ -32,6 +33,7 @@ describe('v3', () => { httpClient: HttpClient.FETCH, useOptions: false, useUnionTypes: false, + autoformat: false, exportCore: true, exportSchemas: true, exportModels: true, diff --git a/test/spec/v2.json b/test/spec/v2.json index e8eb19b51..af489ca99 100644 --- a/test/spec/v2.json +++ b/test/spec/v2.json @@ -456,6 +456,30 @@ } } }, + "/api/v{api-version}/multiple-tags/response-and-no-content": { + "get": { + "tags": [ + "Response", + "NoContent" + ], + "operationId": "CallWithResponseAndNoContentResponse", + "responses": { + "200": { + "description": "Response is a simple number", + "content": { + "application/json": { + "schema": { + "type": "number" + } + } + } + }, + "204": { + "description": "Success" + } + } + } + }, "/api/v{api-version}/multiple-tags/a": { "get": { "tags": [ @@ -944,6 +968,10 @@ "description": "This is a simple string", "type": "string" }, + "NonAsciiStringæøåÆØÅöôêÊ字符串": { + "description": "A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串)", + "type": "string" + }, "SimpleFile": { "description": "This is a simple file", "type": "file" @@ -965,7 +993,8 @@ "Warning", "Error", "'Single Quote'", - "\"Double Quotes\"" + "\"Double Quotes\"", + "Non-ascii: øæåôöØÆÅÔÖ字符串" ] }, "EnumWithNumbers": { @@ -1174,7 +1203,8 @@ "enum": [ "Success", "Warning", - "Error" + "Error", + "ØÆÅ字符串" ] }, "statusCode": { diff --git a/test/spec/v3.json b/test/spec/v3.json index cb590d0b7..9008f534a 100644 --- a/test/spec/v3.json +++ b/test/spec/v3.json @@ -663,6 +663,30 @@ } } }, + "/api/v{api-version}/multiple-tags/response-and-no-content": { + "get": { + "tags": [ + "Response", + "NoContent" + ], + "operationId": "CallWithResponseAndNoContentResponse", + "responses": { + "200": { + "description": "Response is a simple number", + "content": { + "application/json": { + "schema": { + "type": "number" + } + } + } + }, + "204": { + "description": "Success" + } + } + } + }, "/api/v{api-version}/multiple-tags/a": { "get": { "tags": [ @@ -1469,6 +1493,7 @@ "components": { "requestBodies": { "SimpleRequestBody": { + "x-body-name": "foo", "description": "A reusable request body", "required": false, "content": { @@ -1539,6 +1564,10 @@ "description": "This is a simple string", "type": "string" }, + "NonAsciiStringæøåÆØÅöôêÊ字符串": { + "description": "A string with non-ascii (unicode) characters valid in typescript identifiers (æøåÆØÅöÔèÈ字符串)", + "type": "string" + }, "SimpleFile": { "description": "This is a simple file", "type": "file" @@ -1561,7 +1590,8 @@ "Warning", "Error", "'Single Quote'", - "\"Double Quotes\"" + "\"Double Quotes\"", + "Non-ascii: øæåôöØÆÅÔÖ字符串" ] }, "EnumWithNumbers": { @@ -1659,6 +1689,72 @@ } } }, + "ArrayWithAnyOfProperties": { + "description": "This is a simple array with any of properties", + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + } + } + ] + } + }, + "AnyOfAnyAndNull": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + {}, + { + "type": "null" + } + ] + } + } + }, + "AnyOfArrays": { + "description": "This is a simple array with any of properties", + "type": "object", + "properties": { + "results": { + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + } + } + ] + }, + "type": "array" + } + } + }, "DictionaryWithString": { "description": "This is a string dictionary", "type": "object", @@ -1781,7 +1877,8 @@ "enum": [ "Success", "Warning", - "Error" + "Error", + "ØÆÅ字符串" ] }, "statusCode": { @@ -2071,6 +2168,81 @@ } } }, + "CompositionWithNestedAnyAndTypeNull": { + "description": "This is a model with nested 'any of' property with a type null", + "type": "object", + "properties": { + "propA": { + "type": "object", + "anyOf": [ + { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelWithDictionary" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelWithArray" + }, + { + "type": "null" + } + ] + }, + "type": "array" + } + ] + } + } + }, + "Enum1": { + "enum": [ + "Bird", + "Dog" + ], + "type": "string" + }, + "ConstValue": { + "type": "string", + "const": "ConstValue" + }, + "CompositionWithNestedAnyOfAndNull": { + "description": "This is a model with one property with a 'any of' relationship where the options are not $ref", + "type": "object", + "properties": { + "propA": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/Enum1" + }, + { + "$ref": "#/components/schemas/ConstValue" + } + ] + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Scopes" + } + } + }, "CompositionWithOneOfAndNullable": { "description": "This is a model with one property with a 'one of' relationship", "type": "object", @@ -2553,7 +2725,61 @@ "description": "This is a free-form object with additionalProperties: {}.", "type": "object", "additionalProperties": {} + }, + "ModelWithConst": { + "type": "object", + "properties": { + "String": { + "const": "String" + }, + "number": { + "const": 0 + }, + "null": { + "const": null + }, + "withType": { + "type": "string", + "const": "Some string" + } + } + }, + "ModelWithAdditionalPropertiesEqTrue": { + "description": "This is a model with one property and additionalProperties: true", + "type": "object", + "properties": { + "prop": { + "description": "This is a simple string property", + "type": "string" + } + }, + "additionalProperties": true + }, + "NestedAnyOfArraysNullable": { + "properties": { + "nullableArray": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "type": "array" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" } } } -} +} \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts index e2b5247e0..4b4b2beda 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -19,9 +19,10 @@ export type Options = { clientName?: string; useOptions?: boolean; useUnionTypes?: boolean; + autoformat?: boolean; exportCore?: boolean; - exportServices?: boolean; - exportModels?: boolean; + exportServices?: boolean | string; + exportModels?: boolean | string; exportSchemas?: boolean; indent?: Indent | '4' | '2' | 'tab'; postfixServices?: string;