diff --git a/.changeset/nasty-comics-smoke.md b/.changeset/nasty-comics-smoke.md new file mode 100644 index 000000000..97201bd0b --- /dev/null +++ b/.changeset/nasty-comics-smoke.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +fix: preserve leading indicators in enum keys diff --git a/packages/openapi-ts/src/compiler/typedef.ts b/packages/openapi-ts/src/compiler/typedef.ts index 73a865540..a4c5c0a54 100644 --- a/packages/openapi-ts/src/compiler/typedef.ts +++ b/packages/openapi-ts/src/compiler/typedef.ts @@ -226,7 +226,7 @@ export const createTypeArrayNode = ( ) => { const node = createTypeReferenceNode({ typeArguments: [ - // @ts-ignore + // @ts-expect-error Array.isArray(types) ? createTypeUnionNode({ types }) : types, ], typeName: 'Array', diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index 22b660f4e..2e9e3a758 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -13,7 +13,7 @@ import { sync } from 'cross-spawn'; import { generateLegacyOutput, generateOutput } from './generate/output'; import { ensureDirSync } from './generate/utils'; import type { IR } from './ir/types'; -import { parseExperimental, parseLegacy } from './openApi'; +import { parseLegacy, parseOpenApiSpec } from './openApi'; import type { ClientPlugins, UserPlugins } from './plugins'; import { defaultPluginConfigs } from './plugins'; import type { @@ -619,7 +619,7 @@ export async function createClient( !isLegacyClient(config) && !legacyNameFromConfig(config) ) { - context = parseExperimental({ config, spec: data }); + context = parseOpenApiSpec({ config, spec: data }); } // fallback to legacy parser diff --git a/packages/openapi-ts/src/openApi/2.0.x/index.ts b/packages/openapi-ts/src/openApi/2.0.x/index.ts new file mode 100644 index 000000000..843f93804 --- /dev/null +++ b/packages/openapi-ts/src/openApi/2.0.x/index.ts @@ -0,0 +1,2 @@ +export { parseV2_0_X } from './parser'; +export type { OpenApiV2_0_X } from './types/spec'; diff --git a/packages/openapi-ts/src/openApi/2.0.x/parser/index.ts b/packages/openapi-ts/src/openApi/2.0.x/parser/index.ts new file mode 100644 index 000000000..5d93dbc66 --- /dev/null +++ b/packages/openapi-ts/src/openApi/2.0.x/parser/index.ts @@ -0,0 +1,239 @@ +import type { IR } from '../../../ir/types'; +import { canProcessRef } from '../../shared/utils/filter'; +import { mergeParametersObjects } from '../../shared/utils/parameter'; +import type { + OpenApiV2_0_X, + OperationObject, + PathItemObject, + PathsObject, + SecuritySchemeObject, +} from '../types/spec'; +import { parseOperation } from './operation'; +import { parametersArrayToObject } from './parameter'; +import { parseSchema } from './schema'; + +type PathKeys = + keyof T extends infer K ? (K extends `/${string}` ? K : never) : never; + +export const parseV2_0_X = (context: IR.Context) => { + const operationIds = new Map(); + const securitySchemesMap = new Map(); + + const excludeRegExp = context.config.input.exclude + ? new RegExp(context.config.input.exclude) + : undefined; + const includeRegExp = context.config.input.include + ? new RegExp(context.config.input.include) + : undefined; + + const shouldProcessRef = ($ref: string) => + canProcessRef({ + $ref, + excludeRegExp, + includeRegExp, + }); + + // TODO: parser - support security schemas + + if (context.spec.definitions) { + for (const name in context.spec.definitions) { + const $ref = `#/definitions/${name}`; + if (!shouldProcessRef($ref)) { + continue; + } + + const schema = context.spec.definitions[name]!; + + parseSchema({ + $ref, + context, + schema, + }); + } + } + + for (const path in context.spec.paths) { + if (path.startsWith('x-')) { + continue; + } + + const pathItem = context.spec.paths[path as PathKeys]!; + + const finalPathItem = pathItem.$ref + ? { + ...context.resolveRef(pathItem.$ref), + ...pathItem, + } + : pathItem; + + const commonOperation: OperationObject = { + consumes: context.spec.consumes, + produces: context.spec.produces, + responses: {}, + security: context.spec.security, + }; + const operationArgs: Omit[0], 'method'> = + { + context, + operation: { + ...commonOperation, + id: '', + parameters: parametersArrayToObject({ + context, + operation: commonOperation, + parameters: finalPathItem.parameters, + }), + }, + operationIds, + path: path as PathKeys, + securitySchemesMap, + }; + + const $refDelete = `#/paths${path}/delete`; + if (finalPathItem.delete && shouldProcessRef($refDelete)) { + const parameters = mergeParametersObjects({ + source: parametersArrayToObject({ + context, + operation: finalPathItem.delete, + parameters: finalPathItem.delete.parameters, + }), + target: operationArgs.operation.parameters, + }); + parseOperation({ + ...operationArgs, + method: 'delete', + operation: { + ...operationArgs.operation, + ...finalPathItem.delete, + parameters, + }, + }); + } + + const $refGet = `#/paths${path}/get`; + if (finalPathItem.get && shouldProcessRef($refGet)) { + const parameters = mergeParametersObjects({ + source: parametersArrayToObject({ + context, + operation: finalPathItem.get, + parameters: finalPathItem.get.parameters, + }), + target: operationArgs.operation.parameters, + }); + parseOperation({ + ...operationArgs, + method: 'get', + operation: { + ...operationArgs.operation, + ...finalPathItem.get, + parameters, + }, + }); + } + + const $refHead = `#/paths${path}/head`; + if (finalPathItem.head && shouldProcessRef($refHead)) { + const parameters = mergeParametersObjects({ + source: parametersArrayToObject({ + context, + operation: finalPathItem.head, + parameters: finalPathItem.head.parameters, + }), + target: operationArgs.operation.parameters, + }); + parseOperation({ + ...operationArgs, + method: 'head', + operation: { + ...operationArgs.operation, + ...finalPathItem.head, + parameters, + }, + }); + } + + const $refOptions = `#/paths${path}/options`; + if (finalPathItem.options && shouldProcessRef($refOptions)) { + const parameters = mergeParametersObjects({ + source: parametersArrayToObject({ + context, + operation: finalPathItem.options, + parameters: finalPathItem.options.parameters, + }), + target: operationArgs.operation.parameters, + }); + parseOperation({ + ...operationArgs, + method: 'options', + operation: { + ...operationArgs.operation, + ...finalPathItem.options, + parameters, + }, + }); + } + + const $refPatch = `#/paths${path}/patch`; + if (finalPathItem.patch && shouldProcessRef($refPatch)) { + const parameters = mergeParametersObjects({ + source: parametersArrayToObject({ + context, + operation: finalPathItem.patch, + parameters: finalPathItem.patch.parameters, + }), + target: operationArgs.operation.parameters, + }); + parseOperation({ + ...operationArgs, + method: 'patch', + operation: { + ...operationArgs.operation, + ...finalPathItem.patch, + parameters, + }, + }); + } + + const $refPost = `#/paths${path}/post`; + if (finalPathItem.post && shouldProcessRef($refPost)) { + const parameters = mergeParametersObjects({ + source: parametersArrayToObject({ + context, + operation: finalPathItem.post, + parameters: finalPathItem.post.parameters, + }), + target: operationArgs.operation.parameters, + }); + parseOperation({ + ...operationArgs, + method: 'post', + operation: { + ...operationArgs.operation, + ...finalPathItem.post, + parameters, + }, + }); + } + + const $refPut = `#/paths${path}/put`; + if (finalPathItem.put && shouldProcessRef($refPut)) { + const parameters = mergeParametersObjects({ + source: parametersArrayToObject({ + context, + operation: finalPathItem.put, + parameters: finalPathItem.put.parameters, + }), + target: operationArgs.operation.parameters, + }); + parseOperation({ + ...operationArgs, + method: 'put', + operation: { + ...operationArgs.operation, + ...finalPathItem.put, + parameters, + }, + }); + } + } +}; diff --git a/packages/openapi-ts/src/openApi/2.0.x/parser/mediaType.ts b/packages/openapi-ts/src/openApi/2.0.x/parser/mediaType.ts new file mode 100644 index 000000000..154733cc6 --- /dev/null +++ b/packages/openapi-ts/src/openApi/2.0.x/parser/mediaType.ts @@ -0,0 +1,70 @@ +import type { IRMediaType } from '../../../ir/mediaType'; +import { + isMediaTypeFileLike, + mediaTypeToIrMediaType, +} from '../../../ir/mediaType'; +import type { + ReferenceObject, + ResponseObject, + SchemaObject, +} from '../types/spec'; + +interface Content { + mediaType: string; + schema: SchemaObject | ReferenceObject | undefined; + type: IRMediaType | undefined; +} + +export const contentToSchema = ({ + content, +}: { + content: Content; +}): SchemaObject | undefined => { + const { mediaType, schema } = content; + + if (schema && '$ref' in schema) { + return { + allOf: [{ ...schema }], + }; + } + + if (!schema) { + if (isMediaTypeFileLike({ mediaType })) { + return { + format: 'binary', + type: 'string', + }; + } + return; + } + + if ( + schema.type === 'string' && + !schema.format && + isMediaTypeFileLike({ mediaType }) + ) { + return { + ...schema, + format: 'binary', + }; + } + + return schema; +}; + +export const mediaTypeObject = ({ + mimeTypes, + response, +}: { + mimeTypes: ReadonlyArray | undefined; + response: Pick; +}): Content | undefined => { + // return the first supported MIME type + for (const mediaType of mimeTypes ?? []) { + return { + mediaType, + schema: response.schema, + type: mediaTypeToIrMediaType({ mediaType }), + }; + } +}; diff --git a/packages/openapi-ts/src/openApi/2.0.x/parser/operation.ts b/packages/openapi-ts/src/openApi/2.0.x/parser/operation.ts new file mode 100644 index 000000000..1fc957e34 --- /dev/null +++ b/packages/openapi-ts/src/openApi/2.0.x/parser/operation.ts @@ -0,0 +1,303 @@ +import type { IR, IRBodyObject } from '../../../ir/types'; +import { + ensureUniqueOperationId, + operationToId, +} from '../../shared/utils/operation'; +import type { + OperationObject, + ParameterObject, + PathItemObject, + ResponseObject, + SchemaObject, + SecuritySchemeObject, +} from '../types/spec'; +import { contentToSchema, mediaTypeObject } from './mediaType'; +import { paginationField } from './pagination'; +import { schemaToIrSchema } from './schema'; + +interface Operation + extends Omit, + Pick { + requestBody?: OperationObject['parameters']; +} + +const parseOperationJsDoc = ({ + irOperation, + operation, +}: { + irOperation: IR.OperationObject; + operation: Operation; +}) => { + if (operation.deprecated !== undefined) { + irOperation.deprecated = operation.deprecated; + } + + if (operation.description) { + irOperation.description = operation.description; + } + + if (operation.summary) { + irOperation.summary = operation.summary; + } + + if (operation.tags?.length) { + irOperation.tags = operation.tags; + } +}; + +const initIrOperation = ({ + method, + operation, + path, +}: Pick & { + operation: Operation; +}): IR.OperationObject => { + const irOperation: IR.OperationObject = { + id: operation.id, + method, + path, + }; + + parseOperationJsDoc({ + irOperation, + operation, + }); + + return irOperation; +}; + +const operationToIrOperation = ({ + context, + method, + operation, + path, + // securitySchemesMap, +}: Pick & { + context: IR.Context; + operation: Operation; + securitySchemesMap: Map; +}): IR.OperationObject => { + const irOperation = initIrOperation({ method, operation, path }); + + if (operation.parameters) { + irOperation.parameters = operation.parameters; + } + + let isRequestBodyRequired = false; + const requestBodyObject: IRBodyObject = { + mediaType: '', + schema: { + properties: {}, + required: [], + type: 'object', + }, + }; + const requestBodyObjectRequired: Array = []; + + for (const requestBodyParameter of operation.requestBody ?? []) { + const requestBody = + '$ref' in requestBodyParameter + ? context.resolveRef(requestBodyParameter.$ref) + : requestBodyParameter; + const schema: SchemaObject = + requestBody.in === 'body' + ? requestBody.schema + : { + ...requestBody, + format: requestBody.type === 'file' ? 'binary' : requestBody.format, + required: undefined, + type: requestBody.type === 'file' ? 'string' : requestBody.type, + }; + const content = mediaTypeObject({ + mimeTypes: operation.consumes, + response: { schema }, + }); + + if (content) { + const pagination = paginationField({ + context, + name: '', + schema: + content.schema && '$ref' in content.schema + ? { + allOf: [{ ...content.schema }], + description: requestBody.description, + } + : { + description: requestBody.description, + ...content.schema, + }, + }); + + const irSchema = schemaToIrSchema({ + context, + schema: + '$ref' in requestBody + ? { + allOf: [ + { + ...requestBody, + $ref: requestBody.$ref as string, + required: [], + type: 'string', + }, + ], + description: requestBody.description, + } + : content.schema && '$ref' in content.schema + ? { + allOf: [{ ...content.schema }], + description: requestBody.description, + } + : { + description: requestBody.description, + ...content.schema, + }, + }); + + requestBodyObject.mediaType = content.mediaType; + + if (requestBody.in === 'body') { + requestBodyObject.schema = irSchema; + } else { + requestBodyObject.schema.properties![requestBody.name] = irSchema; + + if (requestBody.required) { + requestBodyObjectRequired.push(requestBody.name); + } + } + + if (pagination) { + requestBodyObject.pagination = pagination; + } + + if (content.type) { + requestBodyObject.type = content.type; + } + } + + if (requestBody.required) { + isRequestBodyRequired = true; + } + } + + if (requestBodyObject.mediaType) { + if (requestBodyObjectRequired.length) { + requestBodyObject.schema.required = requestBodyObjectRequired; + } + + irOperation.body = requestBodyObject; + + if (isRequestBodyRequired) { + irOperation.body.required = isRequestBodyRequired; + } + } + + for (const name in operation.responses) { + if (!irOperation.responses) { + irOperation.responses = {}; + } + + const response = operation.responses[name]!; + const responseObject = + '$ref' in response + ? context.resolveRef(response.$ref) + : response; + const content = mediaTypeObject({ + // assume JSON by default + mimeTypes: operation.produces ? operation.produces : ['application/json'], + response: responseObject, + }); + + if (content) { + irOperation.responses[name] = { + mediaType: content.mediaType, + schema: schemaToIrSchema({ + context, + schema: { + description: responseObject.description, + ...contentToSchema({ content }), + }, + }), + }; + } else { + irOperation.responses[name] = { + schema: { + description: responseObject.description, + // TODO: parser - cover all statues with empty response bodies + // 1xx, 204, 205, 304 + type: name === '204' ? 'void' : 'unknown', + }, + }; + } + } + + if (operation.security) { + // const securitySchemeObjects: Array = []; + // for (const securityRequirementObject of operation.security) { + // for (const name in securityRequirementObject) { + // const securitySchemeObject = securitySchemesMap.get(name); + // if (securitySchemeObject) { + // securitySchemeObjects.push(securitySchemeObject); + // } + // } + // } + // if (securitySchemeObjects.length) { + // irOperation.security = securitySchemeObjects; + // } + } + + // TODO: parser - handle servers + // qux: operation.servers + + return irOperation; +}; + +export const parseOperation = ({ + context, + method, + operation, + operationIds, + path, + securitySchemesMap, +}: { + context: IR.Context; + method: Extract< + keyof PathItemObject, + 'delete' | 'get' | 'head' | 'options' | 'patch' | 'post' | 'put' | 'trace' + >; + operation: Operation; + operationIds: Map; + path: keyof IR.PathsObject; + securitySchemesMap: Map; +}) => { + ensureUniqueOperationId({ + id: operation.operationId, + method, + operationIds, + path, + }); + + if (!context.ir.paths) { + context.ir.paths = {}; + } + + if (!context.ir.paths[path]) { + context.ir.paths[path] = {}; + } + + operation.id = operationToId({ + context, + id: operation.operationId, + method, + path, + }); + + context.ir.paths[path][method] = operationToIrOperation({ + context, + method, + operation, + path, + securitySchemesMap, + }); +}; diff --git a/packages/openapi-ts/src/openApi/2.0.x/parser/pagination.ts b/packages/openapi-ts/src/openApi/2.0.x/parser/pagination.ts new file mode 100644 index 000000000..20bcfe28c --- /dev/null +++ b/packages/openapi-ts/src/openApi/2.0.x/parser/pagination.ts @@ -0,0 +1,106 @@ +import { paginationKeywordsRegExp } from '../../../ir/pagination'; +import type { IR } from '../../../ir/types'; +import type { ParameterObject, ReferenceObject } from '../types/spec'; +import { type SchemaObject } from '../types/spec'; +import { getSchemaType } from './schema'; + +// We handle only simple values for now, up to 1 nested field +export const paginationField = ({ + context, + name, + schema, +}: { + context: IR.Context; + name: string; + schema: + | ParameterObject + | SchemaObject + | ReferenceObject + | { + in: undefined; + }; +}): boolean | string => { + paginationKeywordsRegExp.lastIndex = 0; + if (paginationKeywordsRegExp.test(name)) { + return true; + } + + if ('$ref' in schema) { + const ref = context.resolveRef( + schema.$ref ?? '', + ); + + if ('in' in ref && ref.in) { + const refSchema = + 'schema' in ref + ? ref.schema + : { + ...ref, + in: undefined, + }; + + return paginationField({ + context, + name, + schema: refSchema, + }); + } + + return paginationField({ + context, + name, + schema: ref, + }); + } + + if ('in' in schema && schema.in) { + const finalSchema = + 'schema' in schema + ? schema.schema + : { + ...schema, + in: undefined, + }; + + return paginationField({ + context, + name, + schema: finalSchema, + }); + } + + for (const name in schema.properties) { + paginationKeywordsRegExp.lastIndex = 0; + + if (paginationKeywordsRegExp.test(name)) { + const property = schema.properties[name]!; + + if (typeof property !== 'boolean' && !('$ref' in property)) { + const schemaType = getSchemaType({ schema: property }); + // TODO: resolve deeper references + + if ( + schemaType === 'boolean' || + schemaType === 'integer' || + schemaType === 'number' || + schemaType === 'string' + ) { + return name; + } + } + } + } + + for (const allOf of schema.allOf ?? []) { + const pagination = paginationField({ + context, + name, + schema: allOf, + }); + if (pagination) { + return pagination; + } + } + + return false; +}; diff --git a/packages/openapi-ts/src/openApi/2.0.x/parser/parameter.ts b/packages/openapi-ts/src/openApi/2.0.x/parser/parameter.ts new file mode 100644 index 000000000..d5cba710e --- /dev/null +++ b/packages/openapi-ts/src/openApi/2.0.x/parser/parameter.ts @@ -0,0 +1,158 @@ +import type { IR } from '../../../ir/types'; +import type { + OperationObject, + ParameterObject, + ReferenceObject, + SchemaObject, +} from '../types/spec'; +import { paginationField } from './pagination'; +import { schemaToIrSchema } from './schema'; + +type Parameter = Exclude; + +/** + * Returns default parameter `explode` based on value of `collectionFormat`. + */ +const defaultExplode = ( + collectionFormat: Parameter['collectionFormat'], +): boolean => { + switch (collectionFormat) { + case 'multi': + return true; + case 'csv': + case 'pipes': + case 'ssv': + case 'tsv': + default: + return false; + } +}; + +/** + * Returns default parameter `style` based on value of `in`. + */ +const defaultStyle = ( + _in: Parameter['in'], +): Required['style'] => { + switch (_in) { + case 'header': + case 'path': + return 'simple'; + case 'query': + default: + return 'form'; + } +}; + +export const parametersArrayToObject = ({ + context, + operation, + parameters, +}: { + context: IR.Context; + operation: OperationObject; + parameters?: ReadonlyArray; +}): IR.ParametersObject | undefined => { + if (!parameters || !Object.keys(parameters).length) { + return; + } + + const parametersObject: IR.ParametersObject = {}; + + for (const parameterOrReference of parameters) { + const parameter = + '$ref' in parameterOrReference + ? context.resolveRef(parameterOrReference.$ref) + : parameterOrReference; + + // push request body parameters into a separate field + if (parameter.in === 'body' || parameter.in === 'formData') { + // @ts-expect-error + if (!operation.requestBody) { + // @ts-expect-error + operation.requestBody = []; + } + + // @ts-expect-error + operation.requestBody.push(parameter); + continue; + } + + if (!parametersObject[parameter.in]) { + parametersObject[parameter.in] = {}; + } + + parametersObject[parameter.in]![parameter.name] = parameterToIrParameter({ + context, + parameter, + }); + } + + return parametersObject; +}; + +const parameterToIrParameter = ({ + context, + parameter, +}: { + context: IR.Context; + parameter: Parameter; +}): IR.ParameterObject => { + const schema = parameter; + + const finalSchema: SchemaObject = + schema && '$ref' in schema + ? { + allOf: [ + { + ...schema, + $ref: schema.$ref as string, + required: Array.isArray(schema.required) ? schema.required : [], + type: schema.type as SchemaObject['type'], + }, + ], + description: parameter.description, + } + : { + description: parameter.description, + ...schema, + required: Array.isArray(schema.required) ? schema.required : [], + type: schema.type as SchemaObject['type'], + }; + + const pagination = paginationField({ + context, + name: parameter.name, + schema: finalSchema, + }); + + const style = defaultStyle(parameter.in); + const explode = defaultExplode(parameter.collectionFormat); + const allowReserved = false; + + const irParameter: IR.ParameterObject = { + allowReserved, + explode, + location: parameter.in as IR.ParameterObject['location'], + name: parameter.name, + schema: schemaToIrSchema({ + context, + schema: finalSchema, + }), + style, + }; + + if (parameter.description) { + irParameter.description = parameter.description; + } + + if (pagination) { + irParameter.pagination = pagination; + } + + if (parameter.required) { + irParameter.required = parameter.required; + } + + return irParameter; +}; diff --git a/packages/openapi-ts/src/openApi/2.0.x/parser/schema.ts b/packages/openapi-ts/src/openApi/2.0.x/parser/schema.ts new file mode 100644 index 000000000..2d9076e91 --- /dev/null +++ b/packages/openapi-ts/src/openApi/2.0.x/parser/schema.ts @@ -0,0 +1,677 @@ +import type { IR } from '../../../ir/types'; +import { addItemsToSchema } from '../../../ir/utils'; +import { refToName } from '../../../utils/ref'; +import type { + SchemaContext, + SchemaType, + SchemaWithRequired, +} from '../../shared/types/schema'; +import { discriminatorValue } from '../../shared/utils/discriminator'; +import type { SchemaObject } from '../types/spec'; + +export const getSchemaType = ({ + schema, +}: { + schema: SchemaObject; +}): SchemaType | undefined => { + if (schema.type) { + return schema.type; + } + + // infer object based on the presence of properties + if (schema.properties) { + return 'object'; + } +}; + +const parseSchemaJsDoc = ({ + irSchema, + schema, +}: { + irSchema: IR.SchemaObject; + schema: SchemaObject; +}) => { + if (schema.description) { + irSchema.description = schema.description; + } + + if (schema.title) { + irSchema.title = schema.title; + } +}; + +const parseSchemaMeta = ({ + irSchema, + schema, +}: { + irSchema: IR.SchemaObject; + schema: SchemaObject; +}) => { + if (schema.default !== undefined) { + irSchema.default = schema.default; + } + + if (schema.exclusiveMaximum) { + if (schema.maximum !== undefined) { + irSchema.exclusiveMaximum = schema.maximum; + } + } else if (schema.maximum !== undefined) { + irSchema.maximum = schema.maximum; + } + + if (schema.exclusiveMinimum) { + if (schema.minimum !== undefined) { + irSchema.exclusiveMinimum = schema.minimum; + } + } else if (schema.minimum !== undefined) { + irSchema.minimum = schema.minimum; + } + + if (schema.format) { + irSchema.format = schema.format; + } + + if (schema.maxItems !== undefined) { + irSchema.maxItems = schema.maxItems; + } + + if (schema.maxLength !== undefined) { + irSchema.maxLength = schema.maxLength; + } + + if (schema.minItems !== undefined) { + irSchema.minItems = schema.minItems; + } + + if (schema.minLength !== undefined) { + irSchema.minLength = schema.minLength; + } + + if (schema.pattern) { + irSchema.pattern = schema.pattern; + } + + if (schema.readOnly) { + irSchema.accessScope = 'read'; + } +}; + +const parseArray = ({ + context, + irSchema = {}, + schema, +}: SchemaContext & { + irSchema?: IR.SchemaObject; + schema: SchemaObject; +}): IR.SchemaObject => { + if (schema.maxItems && schema.maxItems === schema.minItems) { + irSchema.type = 'tuple'; + } else { + irSchema.type = 'array'; + } + + let schemaItems: Array = []; + + if (schema.items) { + const irItemsSchema = schemaToIrSchema({ + context, + schema: schema.items, + }); + + if ( + !schemaItems.length && + schema.maxItems && + schema.maxItems === schema.minItems + ) { + schemaItems = Array(schema.maxItems).fill(irItemsSchema); + } else { + if ('$ref' in schema.items) { + schemaItems.push(irItemsSchema); + } else { + const ofArray = schema.items.allOf; + // TODO: parser - add support for x-nullable + // && !schema.items.nullable + if (ofArray && ofArray.length > 1) { + // bring composition up to avoid incorrectly nested arrays + irSchema = { + ...irSchema, + ...irItemsSchema, + }; + } else { + schemaItems.push(irItemsSchema); + } + } + } + } + + irSchema = addItemsToSchema({ + items: schemaItems, + schema: irSchema, + }); + + return irSchema; +}; + +const parseBoolean = ({ + irSchema = {}, +}: SchemaContext & { + irSchema?: IR.SchemaObject; + schema: SchemaObject; +}): IR.SchemaObject => { + irSchema.type = 'boolean'; + + return irSchema; +}; + +const parseNumber = ({ + irSchema = {}, +}: SchemaContext & { + irSchema?: IR.SchemaObject; + schema: SchemaObject; +}): IR.SchemaObject => { + irSchema.type = 'number'; + + return irSchema; +}; + +const parseObject = ({ + context, + irSchema = {}, + schema, +}: SchemaContext & { + irSchema?: IR.SchemaObject; + schema: SchemaObject; +}): IR.SchemaObject => { + irSchema.type = 'object'; + + const schemaProperties: Record = {}; + + for (const name in schema.properties) { + const property = schema.properties[name]!; + if (typeof property === 'boolean') { + // TODO: parser - handle boolean properties + } else { + schemaProperties[name] = schemaToIrSchema({ + context, + schema: property, + }); + } + } + + if (Object.keys(schemaProperties).length) { + irSchema.properties = schemaProperties; + } + + if (schema.additionalProperties === undefined) { + if (!irSchema.properties) { + irSchema.additionalProperties = { + type: 'unknown', + }; + } + } else if (typeof schema.additionalProperties === 'boolean') { + irSchema.additionalProperties = { + type: schema.additionalProperties ? 'unknown' : 'never', + }; + } else { + const irAdditionalPropertiesSchema = schemaToIrSchema({ + context, + schema: schema.additionalProperties, + }); + // no need to add "any" additional properties if there are no defined properties + if ( + irSchema.properties || + irAdditionalPropertiesSchema.type !== 'unknown' + ) { + irSchema.additionalProperties = irAdditionalPropertiesSchema; + } + } + + if (schema.required) { + irSchema.required = schema.required; + } + + return irSchema; +}; + +const parseString = ({ + irSchema = {}, +}: SchemaContext & { + irSchema?: IR.SchemaObject; + schema: SchemaObject; +}): IR.SchemaObject => { + irSchema.type = 'string'; + + return irSchema; +}; + +const initIrSchema = ({ + schema, +}: { + schema: SchemaObject; +}): IR.SchemaObject => { + const irSchema: IR.SchemaObject = {}; + + parseSchemaJsDoc({ + irSchema, + schema, + }); + + return irSchema; +}; + +const parseAllOf = ({ + $ref, + context, + schema, +}: SchemaContext & { + schema: SchemaWithRequired; +}): IR.SchemaObject => { + let irSchema = initIrSchema({ schema }); + + const schemaItems: Array = []; + const schemaType = getSchemaType({ schema }); + + const compositionSchemas = schema.allOf; + + for (const compositionSchema of compositionSchemas) { + const irCompositionSchema = schemaToIrSchema({ + context, + schema: compositionSchema, + }); + + if (schema.required) { + if (irCompositionSchema.required) { + irCompositionSchema.required = [ + ...irCompositionSchema.required, + ...schema.required, + ]; + } else { + irCompositionSchema.required = schema.required; + } + } + + schemaItems.push(irCompositionSchema); + + if (compositionSchema.$ref) { + const ref = context.resolveRef(compositionSchema.$ref); + // `$ref` should be passed from the root `parseSchema()` call + if (ref.discriminator && $ref) { + const irDiscriminatorSchema: IR.SchemaObject = { + properties: { + [ref.discriminator]: { + const: discriminatorValue($ref), + type: 'string', + }, + }, + type: 'object', + }; + if (ref.required?.includes(ref.discriminator)) { + irDiscriminatorSchema.required = [ref.discriminator]; + } + schemaItems.push(irDiscriminatorSchema); + } + } + } + + if (schemaType === 'object') { + const irObjectSchema = parseOneType({ + context, + schema: { + ...schema, + type: 'object', + }, + }); + + if (irObjectSchema.properties) { + for (const requiredProperty of irObjectSchema.required ?? []) { + if (!irObjectSchema.properties[requiredProperty]) { + for (const compositionSchema of compositionSchemas) { + // TODO: parser - this could be probably resolved more accurately + const finalCompositionSchema = compositionSchema.$ref + ? context.resolveRef(compositionSchema.$ref) + : compositionSchema; + + if ( + getSchemaType({ schema: finalCompositionSchema }) === 'object' + ) { + const irCompositionSchema = parseOneType({ + context, + schema: { + ...finalCompositionSchema, + type: 'object', + }, + }); + + if (irCompositionSchema.properties?.[requiredProperty]) { + irObjectSchema.properties[requiredProperty] = + irCompositionSchema.properties[requiredProperty]; + break; + } + } + } + } + } + schemaItems.push(irObjectSchema); + } + } + + irSchema = addItemsToSchema({ + items: schemaItems, + logicalOperator: 'and', + mutateSchemaOneItem: true, + schema: irSchema, + }); + + // TODO: parser - add support for x-nullable + // if (schema.nullable) { + // // nest composition to avoid producing an intersection with null + // const nestedItems: Array = [ + // { + // type: 'null', + // }, + // ]; + + // if (schemaItems.length) { + // nestedItems.unshift(irSchema); + // } + + // irSchema = { + // items: nestedItems, + // logicalOperator: 'or', + // }; + + // // TODO: parser - this is a hack to bring back up meta fields + // // without it, some schemas were missing original deprecated + // if (nestedItems[0]!.deprecated) { + // irSchema.deprecated = nestedItems[0]!.deprecated; + // } + + // // TODO: parser - this is a hack to bring back up meta fields + // // without it, some schemas were missing original description + // if (nestedItems[0]!.description) { + // irSchema.description = nestedItems[0]!.description; + // } + // } + + return irSchema; +}; + +const parseEnum = ({ + context, + schema, +}: SchemaContext & { + schema: SchemaWithRequired; +}): IR.SchemaObject => { + let irSchema = initIrSchema({ schema }); + + irSchema.type = 'enum'; + + const schemaItems: Array = []; + + for (const [index, enumValue] of schema.enum.entries()) { + const typeOfEnumValue = typeof enumValue; + let enumType: SchemaType | 'null' | undefined; + + if ( + typeOfEnumValue === 'string' || + typeOfEnumValue === 'number' || + typeOfEnumValue === 'boolean' + ) { + enumType = typeOfEnumValue; + } else if (enumValue === null) { + // TODO: parser - add support for x-nullable + // nullable must be true + // if (schema.nullable) { + // enumType = 'null'; + // } + } else { + console.warn( + '🚨', + `unhandled "${typeOfEnumValue}" typeof value "${enumValue}" for enum`, + schema.enum, + ); + } + + if (!enumType) { + continue; + } + + const enumSchema = parseOneType({ + context, + schema: { + description: schema['x-enum-descriptions']?.[index], + title: + schema['x-enum-varnames']?.[index] ?? schema['x-enumNames']?.[index], + // cast enum to string temporarily + type: enumType === 'null' ? 'string' : enumType, + }, + }); + + enumSchema.const = enumValue; + + // cast enum back + if (enumType === 'null') { + enumSchema.type = enumType; + } + + schemaItems.push(enumSchema); + } + + irSchema = addItemsToSchema({ + items: schemaItems, + schema: irSchema, + }); + + return irSchema; +}; + +const parseRef = ({ + schema, +}: SchemaContext & { + schema: SchemaWithRequired; +}): IR.SchemaObject => { + const irSchema: IR.SchemaObject = {}; + + // refs using unicode characters become encoded, didn't investigate why + // but the suspicion is this comes from `@hey-api/json-schema-ref-parser` + irSchema.$ref = decodeURI(schema.$ref); + + // rewrite definitions refs as the internal schema follows OpenAPI 3.x syntax + // and stores all definitions as reusable schemas + irSchema.$ref = irSchema.$ref.replace( + /#\/definitions\/([^/]+)/g, + '#/components/schemas/$1', + ); + + return irSchema; +}; + +const parseType = ({ + context, + schema, +}: SchemaContext & { + schema: SchemaWithRequired; +}): IR.SchemaObject => { + const irSchema = initIrSchema({ schema }); + + parseSchemaMeta({ + irSchema, + schema, + }); + + const type = getSchemaType({ schema }); + + if (!type) { + return irSchema; + } + + // TODO: parser - add support for x-nullable + // if (!schema.nullable) { + return parseOneType({ + context, + irSchema, + schema: { + ...schema, + type, + }, + }); + // } + + // return parseNullableType({ + // context, + // irSchema, + // schema: { + // ...schema, + // type, + // }, + // }); +}; + +const parseOneType = ({ + context, + irSchema, + schema, +}: SchemaContext & { + irSchema?: IR.SchemaObject; + schema: SchemaWithRequired; +}): IR.SchemaObject => { + if (!irSchema) { + irSchema = initIrSchema({ schema }); + + parseSchemaMeta({ + irSchema, + schema, + }); + } + + switch (schema.type) { + case 'array': + return parseArray({ + context, + irSchema, + schema, + }); + case 'boolean': + return parseBoolean({ + context, + irSchema, + schema, + }); + case 'integer': + case 'number': + return parseNumber({ + context, + irSchema, + schema, + }); + case 'object': + return parseObject({ + context, + irSchema, + schema, + }); + case 'string': + return parseString({ + context, + irSchema, + schema, + }); + default: + // gracefully handle invalid type + return parseUnknown({ + context, + irSchema, + schema, + }); + } +}; + +const parseUnknown = ({ + irSchema, + schema, +}: SchemaContext & { + irSchema?: IR.SchemaObject; + schema: SchemaObject; +}): IR.SchemaObject => { + if (!irSchema) { + irSchema = initIrSchema({ schema }); + } + + irSchema.type = 'unknown'; + + parseSchemaMeta({ + irSchema, + schema, + }); + + return irSchema; +}; + +export const schemaToIrSchema = ({ + $ref, + context, + schema, +}: SchemaContext & { + schema: SchemaObject; +}): IR.SchemaObject => { + if (schema.$ref) { + return parseRef({ + $ref, + context, + schema: schema as SchemaWithRequired, + }); + } + + if (schema.enum) { + return parseEnum({ + $ref, + context, + schema: schema as SchemaWithRequired, + }); + } + + if (schema.allOf) { + return parseAllOf({ + $ref, + context, + schema: schema as SchemaWithRequired, + }); + } + + // infer object based on the presence of properties + if (schema.type || schema.properties) { + return parseType({ + $ref, + context, + schema: schema as SchemaWithRequired, + }); + } + + return parseUnknown({ + $ref, + context, + schema, + }); +}; + +export const parseSchema = ({ + $ref, + context, + schema, +}: Required & { + schema: SchemaObject; +}) => { + if (!context.ir.components) { + context.ir.components = {}; + } + + if (!context.ir.components.schemas) { + context.ir.components.schemas = {}; + } + + context.ir.components.schemas[refToName($ref)] = schemaToIrSchema({ + $ref, + context, + schema, + }); +}; diff --git a/packages/openapi-ts/src/openApi/2.0.x/types/json-schema-draft-4.d.ts b/packages/openapi-ts/src/openApi/2.0.x/types/json-schema-draft-4.d.ts new file mode 100644 index 000000000..844dae32b --- /dev/null +++ b/packages/openapi-ts/src/openApi/2.0.x/types/json-schema-draft-4.d.ts @@ -0,0 +1,160 @@ +import type { EnumExtensions } from '../../shared/types/openapi-spec-extensions'; + +export interface JsonSchemaDraft4 extends EnumExtensions { + /** + * A schema can reference another schema using the `$ref` keyword. The value of `$ref` is a URI-reference that is resolved against the schema's {@link https://json-schema.org/understanding-json-schema/structuring#base-uri Base URI}. When evaluating a `$ref`, an implementation uses the resolved identifier to retrieve the referenced schema and applies that schema to the {@link https://json-schema.org/learn/glossary#instance instance}. + * + * The `$ref` keyword may be used to create recursive schemas that refer to themselves. + */ + $ref?: string; + /** + * The `default` keyword specifies a default value. This value is not used to fill in missing values during the validation process. Non-validation tools such as documentation generators or form generators may use this value to give hints to users about how to use a value. However, `default` is typically used to express that if a value is missing, then the value is semantically the same as if the value was present with the default value. The value of `default` should validate against the schema in which it resides, but that isn't required. + */ + default?: unknown; + /** + * The `title` and `description` keywords must be strings. A "title" will preferably be short, whereas a "description" will provide a more lengthy explanation about the purpose of the data described by the schema. + */ + description?: string; + /** + * The `enum` {@link https://json-schema.org/learn/glossary#keyword keyword} is used to restrict a value to a fixed set of values. It must be an array with at least one element, where each element is unique. + * + * You can use `enum` even without a type, to accept values of different types. + */ + enum?: ReadonlyArray; + /** + * Ranges of numbers are specified using a combination of the `minimum` and `maximum` keywords, (or `exclusiveMinimum` and `exclusiveMaximum` for expressing exclusive range). + * + * If _x_ is the value being validated, the following must hold true: + * + * ``` + * x ≥ minimum + * x > exclusiveMinimum + * x ≤ maximum + * x < exclusiveMaximum + * ``` + * + * While you can specify both of `minimum` and `exclusiveMinimum` or both of `maximum` and `exclusiveMaximum`, it doesn't really make sense to do so. + */ + exclusiveMaximum?: boolean; + /** + * Ranges of numbers are specified using a combination of the `minimum` and `maximum` keywords, (or `exclusiveMinimum` and `exclusiveMaximum` for expressing exclusive range). + * + * If _x_ is the value being validated, the following must hold true: + * + * ``` + * x ≥ minimum + * x > exclusiveMinimum + * x ≤ maximum + * x < exclusiveMaximum + * ``` + * + * While you can specify both of `minimum` and `exclusiveMinimum` or both of `maximum` and `exclusiveMaximum`, it doesn't really make sense to do so. + */ + exclusiveMinimum?: boolean; + /** + * The `format` keyword allows for basic semantic identification of certain kinds of string values that are commonly used. For example, because JSON doesn't have a "DateTime" type, dates need to be encoded as strings. `format` allows the schema author to indicate that the string value should be interpreted as a date. By default, `format` is just an annotation and does not effect validation. + * + * Optionally, validator {@link https://json-schema.org/learn/glossary#implementation implementations} can provide a configuration option to enable `format` to function as an assertion rather than just an annotation. That means that validation will fail if, for example, a value with a `date` format isn't in a form that can be parsed as a date. This can allow values to be constrained beyond what the other tools in JSON Schema, including {@link https://json-schema.org/understanding-json-schema/reference/regular_expressions Regular Expressions} can do. + * + * There is a bias toward networking-related formats in the JSON Schema specification, most likely due to its heritage in web technologies. However, custom formats may also be used, as long as the parties exchanging the JSON documents also exchange information about the custom format types. A JSON Schema validator will ignore any format type that it does not understand. + */ + format?: JsonSchemaFormats; + /** + * The length of the array can be specified using the `minItems` and `maxItems` keywords. The value of each keyword must be a non-negative number. These keywords work whether doing {@link https://json-schema.org/understanding-json-schema/reference/array#items list validation} or {@link https://json-schema.org/understanding-json-schema/reference/array#tupleValidation tuple-validation}. + */ + maxItems?: number; + /** + * The length of a string can be constrained using the `minLength` and `maxLength` {@link https://json-schema.org/learn/glossary#keyword keywords}. For both keywords, the value must be a non-negative number. + */ + maxLength?: number; + /** + * The number of properties on an object can be restricted using the `minProperties` and `maxProperties` keywords. Each of these must be a non-negative integer. + */ + maxProperties?: number; + /** + * Ranges of numbers are specified using a combination of the `minimum` and `maximum` keywords, (or `exclusiveMinimum` and `exclusiveMaximum` for expressing exclusive range). + * + * If _x_ is the value being validated, the following must hold true: + * + * ``` + * x ≥ minimum + * x > exclusiveMinimum + * x ≤ maximum + * x < exclusiveMaximum + * ``` + * + * While you can specify both of `minimum` and `exclusiveMinimum` or both of `maximum` and `exclusiveMaximum`, it doesn't really make sense to do so. + */ + maximum?: number; + /** + * The length of the array can be specified using the `minItems` and `maxItems` keywords. The value of each keyword must be a non-negative number. These keywords work whether doing {@link https://json-schema.org/understanding-json-schema/reference/array#items list validation} or {@link https://json-schema.org/understanding-json-schema/reference/array#tupleValidation tuple-validation}. + */ + minItems?: number; + /** + * The length of a string can be constrained using the `minLength` and `maxLength` {@link https://json-schema.org/learn/glossary#keyword keywords}. For both keywords, the value must be a non-negative number. + */ + minLength?: number; + /** + * The number of properties on an object can be restricted using the `minProperties` and `maxProperties` keywords. Each of these must be a non-negative integer. + */ + minProperties?: number; + /** + * Ranges of numbers are specified using a combination of the `minimum` and `maximum` keywords, (or `exclusiveMinimum` and `exclusiveMaximum` for expressing exclusive range). + * + * If _x_ is the value being validated, the following must hold true: + * + * ``` + * x ≥ minimum + * x > exclusiveMinimum + * x ≤ maximum + * x < exclusiveMaximum + * ``` + * + * While you can specify both of `minimum` and `exclusiveMinimum` or both of `maximum` and `exclusiveMaximum`, it doesn't really make sense to do so. + */ + minimum?: number; + /** + * Numbers can be restricted to a multiple of a given number, using the `multipleOf` keyword. It may be set to any positive number. The multiple can be a floating point number. + */ + multipleOf?: number; + /** + * The `pattern` keyword is used to restrict a string to a particular regular expression. The regular expression syntax is the one defined in JavaScript ({@link https://www.ecma-international.org/publications-and-standards/standards/ecma-262/ ECMA 262} specifically) with Unicode support. See {@link https://json-schema.org/understanding-json-schema/reference/regular_expressions Regular Expressions} for more information. + */ + pattern?: string; + /** + * By default, the properties defined by the `properties` keyword are not required. However, one can provide a list of required properties using the `required` keyword. + * + * The `required` keyword takes an array of zero or more strings. Each of these strings must be unique. + */ + required?: ReadonlyArray; + /** + * The `title` and `description` keywords must be strings. A "title" will preferably be short, whereas a "description" will provide a more lengthy explanation about the purpose of the data described by the schema. + */ + title?: string; + /** + * Primitive data types in the Swagger Specification are based on the types supported by the {@link https://tools.ietf.org/html/draft-zyp-json-schema-04#section-3.5 JSON-Schema Draft 4}. Models are described using the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schema-object Schema Object} which is a subset of JSON Schema Draft 4. + */ + type?: JsonSchemaTypes; + /** + * A schema can ensure that each of the items in an array is unique. Simply set the `uniqueItems` keyword to `true`. + */ + uniqueItems?: boolean; +} + +type JsonSchemaFormats = + | 'date-time' + | 'email' + | 'hostname' + | 'ipv4' + | 'ipv6' + | 'uri' + // eslint-disable-next-line @typescript-eslint/ban-types + | (string & {}); + +type JsonSchemaTypes = + | 'array' + | 'boolean' + | 'integer' + | 'number' + | 'object' + | 'string'; diff --git a/packages/openapi-ts/src/openApi/2.0.x/types/spec.d.ts b/packages/openapi-ts/src/openApi/2.0.x/types/spec.d.ts new file mode 100644 index 000000000..603e8b8a7 --- /dev/null +++ b/packages/openapi-ts/src/openApi/2.0.x/types/spec.d.ts @@ -0,0 +1,1584 @@ +import type { EnumExtensions } from '../../shared/types/openapi-spec-extensions'; +import type { JsonSchemaDraft4 } from './json-schema-draft-4'; + +/** + * This is the root document object for the API specification. It combines what previously was the Resource Listing and API Declaration (version 1.2 and earlier) together into one document. + */ +export interface OpenApiV2_0_X { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * The base path on which the API is served, which is relative to the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerHost `host`}. If it is not included, the API is served directly under the `host`. The value MUST start with a leading slash (`/`). The `basePath` does not support {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#path-templating path templating}. + */ + basePath?: string; + /** + * A list of MIME types the APIs can consume. This is global to all APIs but can be overridden on specific API calls. Value MUST be as described under {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#mime-types Mime Types}. + */ + consumes?: ReadonlyArray; + /** + * An object to hold data types produced and consumed by operations. + */ + definitions?: DefinitionsObject; + /** + * Additional external documentation. + */ + externalDocs?: ExternalDocumentationObject; + /** + * The host (name or ip) serving the API. This MUST be the host only and does not include the scheme nor sub-paths. It MAY include a port. If the `host` is not included, the host serving the documentation is to be used (including the port). The `host` does not support {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#path-templating path templating}. + */ + host?: string; + /** + * **Required**. Provides metadata about the API. The metadata can be used by the clients if needed. + */ + info: InfoObject; + /** + * An object to hold parameters that can be used across operations. This property _does not_ define global parameters for all operations. + */ + parameters?: ParametersDefinitionsObject; + /** + * **Required**. The available paths and operations for the API. + */ + paths: PathsObject; + /** + * A list of MIME types the APIs can produce. This is global to all APIs but can be overridden on specific API calls. Value MUST be as described under {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#mime-types Mime Types}. + */ + produces?: ReadonlyArray; + /** + * An object to hold responses that can be used across operations. This property _does not_ define global responses for all operations. + */ + responses?: ResponsesDefinitionsObject; + /** + * The transfer protocol of the API. Values MUST be from the list: `"http"`, `"https"`, `"ws"`, `"wss"`. If the `schemes` is not included, the default scheme to be used is the one used to access the Swagger definition itself. + */ + schemes?: ReadonlyArray<'http' | 'https' | 'ws' | 'wss'>; + /** + * A declaration of which security schemes are applied for the API as a whole. The list of values describes alternative security schemes that can be used (that is, there is a logical OR between the security requirements). Individual operations can override this definition. + */ + security?: ReadonlyArray; + /** + * Security scheme definitions that can be used across the specification. + */ + securityDefinitions?: SecurityDefinitionsObject; + /** + * **Required**. Specifies the Swagger Specification version being used. It can be used by the Swagger UI and other clients to interpret the API listing. The value MUST be `"2.0"`. + */ + swagger: string; + /** + * A list of tags used by the specification with additional metadata. The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used by the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#operation-object Operation Object} must be declared. The tags that are not declared may be organized randomly or based on the tools' logic. Each tag name in the list MUST be unique. + */ + tags?: ReadonlyArray; +} + +/** + * Contact information for the exposed API. + * + * @example + * ```yaml + * name: API Support + * url: http://www.swagger.io/support + * email: support@swagger.io + * ``` + */ +export interface ContactObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * The email address of the contact person/organization. MUST be in the format of an email address. + */ + email?: string; + /** + * The identifying name of the contact person/organization. + */ + name?: string; + /** + * The URL pointing to the contact information. MUST be in the format of a URL. + */ + url?: string; +} + +/** + * An object to hold data types that can be consumed and produced by operations. These data types can be primitives, arrays or models. + * + * **Definitions Object Example** + * + * @example + * ```yaml + * Category: + * type: object + * properties: + * id: + * type: integer + * format: int64 + * name: + * type: string + * Tag: + * type: object + * properties: + * id: + * type: integer + * format: int64 + * name: + * type: string + * ``` + */ +export interface DefinitionsObject { + /** + * A single definition, mapping a "name" to the schema it defines. + */ + [name: string]: SchemaObject; +} + +/** + * Allows sharing examples for operation responses. + * + * **Example Object Example** + * + * Example response for application/json mimetype of a Pet data type: + * + * @example + * ```yaml + * application/json: + * name: Puma + * type: Dog + * color: Black + * gender: Female + * breed: Mixed + * ``` + */ +export interface ExampleObject { + /** + * The name of the property MUST be one of the Operation `produces` values (either implicit or inherited). The value SHOULD be an example of what such a response would look like. + */ + [mimeType: string]: unknown; +} + +/** + * Allows referencing an external resource for extended documentation. + * + * @example + * ```yaml + * description: Find more info here + * url: https://swagger.io + * ``` + */ +export interface ExternalDocumentationObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * A short description of the target documentation. {@link https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown GFM syntax} can be used for rich text representation. + */ + description?: string; + /** + * **Required**. The URL for the target documentation. Value MUST be in the format of a URL. + */ + url: string; +} + +/** + * **Header Object Example** + * + * A simple header with of an integer type: + * + * @example + * ```yaml + * description: The number of allowed requests in the current period + * type: integer + * ``` + */ +export interface HeaderObject extends EnumExtensions { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * Determines the format of the array if type array is used. Possible values are: + * + * - `csv` - comma separated values `foo,bar`. + * - `ssv` - space separated values `foo bar`. + * - `tsv` - tab separated values `foo\tbar`. + * - `pipes` - pipe separated values `foo|bar`. + * + * Default value is `csv`. + */ + collectionFormat?: 'csv' | 'pipes' | 'ssv' | 'tsv'; + /** + * Declares the value of the item that the server will use if none is provided. (Note: "default" has no meaning for required items.) See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2}. Unlike JSON Schema this value MUST conform to the defined {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#itemsType `type`} for the data type. + */ + default?: unknown; + /** + * A short description of the header. + */ + description?: string; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1}. + */ + enum?: ReadonlyArray; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2}. + */ + exclusiveMaximum?: boolean; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3}. + */ + exclusiveMinimum?: boolean; + /** + * The extending format for the previously mentioned {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#stType `type`}. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#dataTypeFormat Data Type Formats} for further details. + */ + format?: string; + /** + * **Required if {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterType `type`} is "array"**. Describes the type of items in the array. + */ + items?: ItemsObject; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2}. + */ + maxItems?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1}. + */ + maxLength?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2}. + */ + maximum?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3}. + */ + minItems?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2}. + */ + minLength?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3}. + */ + minimum?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1}. + */ + multipleOf?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3}. + */ + pattern?: string; + /** + * Required. The type of the object. The value MUST be one of `"string"`, `"number"`, `"integer"`, `"boolean"`, or `"array"`. + */ + type: 'array' | 'boolean' | 'integer' | 'number' | 'string'; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4}. + */ + uniqueItems?: boolean; +} + +/** + * Lists the headers that can be sent as part of a response. + * + * **Headers Object Example** + * + * Rate-limit headers: + * + * @example + * ```yaml + * X-Rate-Limit-Limit: + * description: The number of allowed requests in the current period + * type: integer + * X-Rate-Limit-Remaining: + * description: The number of remaining requests in the current period + * type: integer + * X-Rate-Limit-Reset: + * description: The number of seconds left in the current period + * type: integer + * ``` + */ +export interface HeadersObject { + /** + * The name of the property corresponds to the name of the header. The value describes the type of the header. + */ + [name: string]: HeaderObject; +} + +/** + * The object provides metadata about the API. The metadata can be used by the clients if needed, and can be presented in the Swagger-UI for convenience. + * + * @example + * ```yaml + * title: Swagger Sample App + * description: This is a sample server Petstore server. + * termsOfService: http://swagger.io/terms/ + * contact: + * name: API Support + * url: http://www.swagger.io/support + * email: support@swagger.io + * license: + * name: Apache 2.0 + * url: http://www.apache.org/licenses/LICENSE-2.0.html + * version: 1.0.1 + * ``` + */ +export interface InfoObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * The contact information for the exposed API. + */ + contact?: ContactObject; + /** + * A short description of the application. {@link https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown GFM syntax} can be used for rich text representation. + */ + description?: string; + /** + * The license information for the exposed API. + */ + license?: LicenseObject; + /** + * The Terms of Service for the API. + */ + termsOfService?: string; + /** + * **Required**. The title of the application. + */ + title: string; + /** + * **Required** Provides the version of the application API (not to be confused with the specification version). + */ + version: string; +} + +/** + * A limited subset of JSON-Schema's items object. It is used by parameter definitions that are not located {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterIn `in`} `"body"`. + * + * **Items Object Examples** + * + * Items must be of type string and have the minimum length of 2 characters: + * + * @example + * ```yaml + * type: string + * minLength: 2 + * ``` + * + * An array of arrays, the internal array being of type integer, numbers must be between 0 and 63 (inclusive): + * + * @example + * ```yaml + * type: array + * items: + * type: integer + * minimum: 0 + * maximum: 63 + * ``` + */ +export interface ItemsObject extends EnumExtensions { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * Determines the format of the array if type array is used. Possible values are: + * + * - `csv` - comma separated values `foo,bar`. + * - `ssv` - space separated values `foo bar`. + * - `tsv` - tab separated values `foo\tbar`. + * - `pipes` - pipe separated values `foo|bar`. + * + * Default value is `csv`. + */ + collectionFormat?: 'csv' | 'pipes' | 'ssv' | 'tsv'; + /** + * Declares the value of the item that the server will use if none is provided. (Note: "default" has no meaning for required items.) See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2}. Unlike JSON Schema this value MUST conform to the defined {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#itemsType `type`} for the data type. + */ + default?: unknown; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1}. + */ + enum?: ReadonlyArray; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2}. + */ + exclusiveMaximum?: boolean; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3}. + */ + exclusiveMinimum?: boolean; + /** + * The extending format for the previously mentioned {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterType `type`}. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#dataTypeFormat Data Type Formats} for further details. + */ + format?: string; + /** + * **Required if {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterType `type`} is "array"**. Describes the type of items in the array. + */ + items?: ItemsObject; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2}. + */ + maxItems?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1}. + */ + maxLength?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2}. + */ + maximum?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3}. + */ + minItems?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2}. + */ + minLength?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3}. + */ + minimum?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1}. + */ + multipleOf?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3}. + */ + pattern?: string; + /** + * **Required**. The internal type of the array. The value MUST be one of `"string"`, `"number"`, `"integer"`, `"boolean"`, or `"array"`. Files and models are not allowed. + */ + type: 'array' | 'boolean' | 'integer' | 'number' | 'string'; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4}. + */ + uniqueItems?: boolean; +} + +/** + * License information for the exposed API. + * + * @example + * ```yaml + * name: Apache 2.0 + * url: http://www.apache.org/licenses/LICENSE-2.0.html + * ``` + */ +export interface LicenseObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * **Required**. The license name used for the API. + */ + name: string; + /** + * A URL to the license used for the API. MUST be in the format of a URL. + */ + url?: string; +} + +/** + * Describes a single API operation on a path. + * + * @example + * ```yaml + * tags: + * - pet + * summary: Updates a pet in the store with form data + * description: "" + * operationId: updatePetWithForm + * consumes: + * - application/x-www-form-urlencoded + * produces: + * - application/json + * - application/xml + * parameters: + * - name: petId + * in: path + * description: ID of pet that needs to be updated + * required: true + * type: string + * - name: name + * in: formData + * description: Updated name of the pet + * required: false + * type: string + * - name: status + * in: formData + * description: Updated status of the pet + * required: false + * type: string + * responses: + * '200': + * description: Pet updated. + * '405': + * description: Invalid input + * security: + * - petstore_auth: + * - write:pets + * - read:pets + * ``` + */ +export interface OperationObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * A list of MIME types the operation can consume. This overrides the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerConsumes `consumes`} definition at the Swagger Object. An empty value MAY be used to clear the global definition. Value MUST be as described under {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#mime-types Mime Types}. + */ + consumes?: ReadonlyArray; + /** + * Declares this operation to be deprecated. Usage of the declared operation should be refrained. Default value is `false`. + */ + deprecated?: boolean; + /** + * A verbose explanation of the operation behavior. {@link https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown GFM syntax} can be used for rich text representation. + */ + description?: string; + /** + * Additional external documentation for this operation. + */ + externalDocs?: ExternalDocumentationObject; + /** + * Unique string used to identify the operation. The id MUST be unique among all operations described in the API. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is recommended to follow common programming naming conventions. + */ + operationId?: string; + /** + * A list of parameters that are applicable for this operation. If a parameter is already defined at the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#pathItemParameters Path Item}, the new definition will override it, but can never remove it. The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterName name} and {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterIn location}. The list can use the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object Reference Object} to link to parameters that are defined at the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerParameters Swagger Object's parameters}. There can be one "body" parameter at most. + */ + parameters?: ReadonlyArray; + /** + * A list of MIME types the operation can produce. This overrides the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerProduces `produces`} definition at the Swagger Object. An empty value MAY be used to clear the global definition. Value MUST be as described under {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#mime-types Mime Types}. + */ + produces?: ReadonlyArray; + /** + * **Required**. The list of possible responses as they are returned from executing this operation. + */ + responses: ResponsesObject; + /** + * The transfer protocol for the operation. Values MUST be from the list: `"http"`, `"https"`, `"ws"`, `"wss"`. The value overrides the Swagger Object {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerSchemes `schemes`} definition. + */ + schemes?: ReadonlyArray<'http' | 'https' | 'ws' | 'wss'>; + /** + * A declaration of which security schemes are applied for this operation. The list of values describes alternative security schemes that can be used (that is, there is a logical OR between the security requirements). This definition overrides any declared top-level {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerSecurity `security`}. To remove a top-level security declaration, an empty array can be used. + */ + security?: ReadonlyArray; + /** + * A short summary of what the operation does. For maximum readability in the swagger-ui, this field SHOULD be less than 120 characters. + */ + summary?: string; + /** + * A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier. + */ + tags?: ReadonlyArray; +} + +/** + * Describes a single operation parameter. + * + * A unique parameter is defined by a combination of a {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterName name} and {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterIn location}. + * + * There are five possible parameter types. + * + * - Path - Used together with {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#path-templating Path Templating}, where the parameter value is actually part of the operation's URL. This does not include the host or base path of the API. For example, in `/items/{itemId}`, the path parameter is `itemId`. + * - Query - Parameters that are appended to the URL. For example, in `/items?id=###`, the query parameter is `id`. + * - Header - Custom headers that are expected as part of the request. + * - Body - The payload that's appended to the HTTP request. Since there can only be one payload, there can only be _one_ body parameter. The name of the body parameter has no effect on the parameter itself and is used for documentation purposes only. Since Form parameters are also in the payload, body and form parameters cannot exist together for the same operation. + * - Form - Used to describe the payload of an HTTP request when either `application/x-www-form-urlencoded`, `multipart/form-data` or both are used as the content type of the request (in Swagger's definition, the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#operationConsumes `consumes`} property of an operation). This is the only parameter type that can be used to send files, thus supporting the `file` type. Since form parameters are sent in the payload, they cannot be declared together with a body parameter for the same operation. Form parameters have a different format based on the content-type used (for further details, consult {@link http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4 http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4}): + * - `application/x-www-form-urlencoded` - Similar to the format of Query parameters but as a payload. For example, `foo=1&bar=swagger` - both `foo` and `bar` are form parameters. This is normally used for simple parameters that are being transferred. + * - `multipart/form-data` - each parameter takes a section in the payload with an internal header. For example, for the header `Content-Disposition: form-data; name="submit-name"` the name of the parameter is `submit-name`. This type of form parameters is more commonly used for file transfers. + * + * **Parameter Object Examples** + * + * Body Parameters + * + * A body parameter with a referenced schema definition (normally for a model definition): + * + * @example + * ```yaml + * name: user + * in: body + * description: user to add to the system + * required: true + * schema: + * $ref: '#/definitions/User' + * ``` + * + * A body parameter that is an array of string values: + * + * @example + * ```yaml + * name: user + * in: body + * description: user to add to the system + * required: true + * schema: + * type: array + * items: + * type: string + * ``` + * + * Other Parameters + * + * A header parameter with an array of 64 bit integer numbers: + * + * @example + * ```yaml + * name: token + * in: header + * description: token to be passed as a header + * required: true + * type: array + * items: + * type: integer + * format: int64 + * collectionFormat: csv + * ``` + * + * A path parameter of a string value: + * + * @example + * ```yaml + * name: username + * in: path + * description: username to fetch + * required: true + * type: string + * ``` + * + * An optional query parameter of a string value, allowing multiple values by repeating the query parameter: + * + * @example + * ```yaml + * name: id + * in: query + * description: ID of the object to fetch + * required: false + * type: array + * items: + * type: string + * collectionFormat: multi + * ``` + * + * A form data with file type for a file upload: + * + * @example + * ```yaml + * name: avatar + * in: formData + * description: The avatar of the user + * required: true + * type: file + * ``` + */ +export type ParameterObject = EnumExtensions & { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * A brief description of the parameter. This could contain examples of use. {@link https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown GFM syntax} can be used for rich text representation. + */ + description?: string; + /** + * **Required**. The name of the parameter. Parameter names are _case sensitive_. + * + * - If {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterIn `in`} is `"path"`, the `name` field MUST correspond to the associated path segment from the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#pathsPath path} field in the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#paths-object Paths Object}. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#path-templating Path Templating} for further information. + * - For all other cases, the `name` corresponds to the parameter name used based on the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterIn `in`} property. + */ + name: string; + /** + * Determines whether this parameter is mandatory. If the parameter is {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterIn `in`} "path", this property is **required** and its value MUST be `true`. Otherwise, the property MAY be included and its default value is `false`. + */ + required?: boolean; +} & ( + | { + /** + * **Required**. The location of the parameter. Possible values are "query", "header", "path", "formData" or "body". + */ + in: 'body'; + /** + * **Required**. The schema defining the type used for the body parameter. + */ + schema: SchemaObject; + } + | { + /** + * Sets the ability to pass empty-valued parameters. This is valid only for either `query` or `formData` parameters and allows you to send a parameter with a name only or an empty value. Default value is `false`. + */ + allowEmptyValue?: boolean; + /** + * Determines the format of the array if type array is used. Possible values are: + * + * - `csv` - comma separated values `foo,bar`. + * - `ssv` - space separated values `foo bar`. + * - `tsv` - tab separated values `foo\tbar`. + * - `pipes` - pipe separated values `foo|bar`. + * - `multi` - corresponds to multiple parameter instances instead of multiple values for a single instance `foo=bar&foo=baz`. This is valid only for parameters {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterIn `in`} "query" or "formData". + * + * Default value is `csv`. + */ + collectionFormat?: 'csv' | 'multi' | 'pipes' | 'ssv' | 'tsv'; + /** + * Declares the value of the parameter that the server will use if none is provided, for example a "count" to control the number of results per page might default to 100 if not supplied by the client in the request. (Note: "default" has no meaning for required parameters.) See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2}. Unlike JSON Schema this value MUST conform to the defined {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterType `type`} for this parameter. + */ + default?: unknown; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1}. + */ + enum?: ReadonlyArray; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2}. + */ + exclusiveMaximum?: boolean; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3}. + */ + exclusiveMinimum?: boolean; + /** + * The extending format for the previously mentioned {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterType `type`}. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#dataTypeFormat Data Type Formats} for further details. + */ + format?: string; + /** + * **Required**. The location of the parameter. Possible values are "query", "header", "path", "formData" or "body". + */ + in: 'formData' | 'header' | 'path' | 'query'; + /** + * **Required if {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterType `type`} is "array"**. Describes the type of items in the array. + */ + items?: ItemsObject; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2}. + */ + maxItems?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1}. + */ + maxLength?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2}. + */ + maximum?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3}. + */ + minItems?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2}. + */ + minLength?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3}. + */ + minimum?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1}. + */ + multipleOf?: number; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3}. + */ + pattern?: string; + /** + * **Required**. The type of the parameter. Since the parameter is not located at the request body, it is limited to simple types (that is, not an object). The value MUST be one of `"string"`, `"number"`, `"integer"`, `"boolean"`, `"array"` or `"file"`. If `type` is `"file"`, the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#operationConsumes `consumes`} MUST be either `"multipart/form-data"`, `"application/x-www-form-urlencoded"` or both and the parameter MUST be {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterIn `in`} `"formData"`. + */ + type: 'array' | 'boolean' | 'file' | 'integer' | 'number' | 'string'; + /** + * See {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4}. + */ + uniqueItems?: boolean; + } + ); + +/** + * An object to hold parameters to be reused across operations. Parameter definitions can be referenced to the ones defined here. + * + * This does _not_ define global operation parameters. + * + * **Parameters Definition Object Example** + * + * @example + * ```yaml + * skipParam: + * name: skip + * in: query + * description: number of items to skip + * required: true + * type: integer + * format: int32 + * limitParam: + * name: limit + * in: query + * description: max records to return + * required: true + * type: integer + * format: int32 + * ``` + */ +export interface ParametersDefinitionsObject { + /** + * A single parameter definition, mapping a "name" to the parameter it defines. + */ + [name: string]: ParameterObject; +} + +/** + * Describes the operations available on a single path. A Path Item may be empty, due to {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#security-filtering ACL constraints}. The path itself is still exposed to the documentation viewer but they will not know which operations and parameters are available. + * + * @example + * ```yaml + * get: + * description: Returns pets based on ID + * summary: Find pets by ID + * operationId: getPetsById + * produces: + * - application/json + * - text/html + * responses: + * '200': + * description: pet response + * schema: + * type: array + * items: + * $ref: '#/definitions/Pet' + * default: + * description: error payload + * schema: + * $ref: '#/definitions/ErrorModel' + * parameters: + * - name: id + * in: path + * description: ID of pet to use + * required: true + * type: array + * items: + * type: string + * collectionFormat: csv + * ``` + */ +export interface PathItemObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * Allows for an external definition of this path item. The referenced structure MUST be in the format of a {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#path-item-object Path Item Object}. If there are conflicts between the referenced definition and this Path Item's definition, the behavior is _undefined_. + */ + $ref?: string; + /** + * A definition of a DELETE operation on this path. + */ + delete?: OperationObject; + /** + * A definition of a GET operation on this path. + */ + get?: OperationObject; + /** + * A definition of a HEAD operation on this path. + */ + head?: OperationObject; + /** + * A definition of a OPTIONS operation on this path. + */ + options?: OperationObject; + /** + * A list of parameters that are applicable for all the operations described under this path. These parameters can be overridden at the operation level, but cannot be removed there. The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterName name} and {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#parameterIn location}. The list can use the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object Reference Object} to link to parameters that are defined at the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerParameters Swagger Object's parameters}. There can be one "body" parameter at most. + */ + parameters?: ReadonlyArray; + /** + * A definition of a PATCH operation on this path. + */ + patch?: OperationObject; + /** + * A definition of a POST operation on this path. + */ + post?: OperationObject; + /** + * A definition of a PUT operation on this path. + */ + put?: OperationObject; +} + +/** + * Holds the relative paths to the individual endpoints. The path is appended to the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerBasePath `basePath`} in order to construct the full URL. The Paths may be empty, due to {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#security-filtering ACL constraints}. + * + * @example + * ```yaml + * /pets: + * get: + * description: Returns all pets from the system that the user has access to + * produces: + * - application/json + * responses: + * '200': + * description: A list of pets. + * schema: + * type: array + * items: + * $ref: '#/definitions/pet' + * ``` + */ +export interface PathsObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * A relative path to an individual endpoint. The field name MUST begin with a slash. The path is appended to the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerBasePath `basePath`} in order to construct the full URL. {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#path-templating Path templating} is allowed. + */ + [path: `/${string}`]: PathItemObject; +} + +/** + * A simple object to allow referencing other definitions in the specification. It can be used to reference parameters and responses that are defined at the top level for reuse. + * + * The Reference Object is a {@link http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-02 JSON Reference} that uses a {@link http://tools.ietf.org/html/rfc6901 JSON Pointer} as its value. For this specification, only {@link https://tools.ietf.org/html/draft-zyp-json-schema-04#section-7.2.3 canonical dereferencing} is supported. + * + * **Reference Object Example** + * + * @example + * ```yaml + * $ref: '#/definitions/Pet' + * ``` + * + * **Relative Schema File Example** + * + * @example + * ```yaml + * $ref: 'Pet.yaml' + * ``` + * + * **Relative Files With Embedded Schema Example** + * + * @example + * ```yaml + * $ref: 'definitions.yaml#/Pet' + * ``` + */ +export interface ReferenceObject { + /** + * **Required**. The reference string. + */ + $ref: string; +} + +/** + * Describes a single response from an API Operation. + * + * **Response Object Examples** + * + * Response of an array of a complex type: + * + * @example + * ```yaml + * description: A complex object array response + * schema: + * type: array + * items: + * $ref: '#/definitions/VeryComplexType' + * ``` + * + * Response with a string type: + * + * @example + * ```yaml + * description: A simple string response + * schema: + * type: string + * ``` + * + * Response with headers: + * + * @example + * ```yaml + * description: A simple string response + * schema: + * type: string + * headers: + * X-Rate-Limit-Limit: + * description: The number of allowed requests in the current period + * type: integer + * X-Rate-Limit-Remaining: + * description: The number of remaining requests in the current period + * type: integer + * X-Rate-Limit-Reset: + * description: The number of seconds left in the current period + * type: integer + * ``` + * + * Response with no return value: + * + * @example + * ```yaml + * description: object created + * ``` + */ +export interface ResponseObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * **Required**. A short description of the response. {@link https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown GFM syntax} can be used for rich text representation. + */ + description: string; + /** + * An example of the response message. + */ + examples?: ExampleObject; + /** + * A list of headers that are sent with the response. + */ + headers?: HeadersObject; + /** + * A definition of the response structure. It can be a primitive, an array or an object. If this field does not exist, it means no content is returned as part of the response. As an extension to the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schema-object Schema Object}, its root `type` value may also be `"file"`. This SHOULD be accompanied by a relevant `produces` mime-type. + */ + schema?: SchemaObject; +} + +/** + * An object to hold responses to be reused across operations. Response definitions can be referenced to the ones defined here. + * + * This does _not_ define global operation responses. + * + * **Responses Definitions Object Example** + * + * @example + * ```yaml + * NotFound: + * description: Entity not found. + * IllegalInput: + * description: Illegal input for operation. + * GeneralError: + * description: General Error + * schema: + * $ref: '#/definitions/GeneralError' + * ``` + */ +export interface ResponsesDefinitionsObject { + /** + * A single response definition, mapping a "name" to the response it defines. + */ + [name: string]: ResponseObject; +} + +/** + * A container for the expected responses of an operation. The container maps a HTTP response code to the expected response. It is not expected from the documentation to necessarily cover all possible HTTP response codes, since they may not be known in advance. However, it is expected from the documentation to cover a successful operation response and any known errors. + * + * The `default` can be used as the default response object for all HTTP codes that are not covered individually by the specification. + * + * The `Responses Object` MUST contain at least one response code, and it SHOULD be the response for a successful operation call. + * + * **Responses Object Example** + * + * A 200 response for successful operation and a default response for others (implying an error): + * + * @example + * ```yaml + * '200': + * description: a pet to be returned + * schema: + * $ref: '#/definitions/Pet' + * default: + * description: Unexpected error + * schema: + * $ref: '#/definitions/ErrorModel' + * ``` + */ +export interface ResponsesObject { + /** + * Any {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#http-status-codes HTTP status code} can be used as the property name (one property per HTTP status code). Describes the expected response for that HTTP status code. {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object Reference Object} can be used to link to a response that is defined at the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerResponses Swagger Object's responses} section. + */ + [httpStatusCode: string]: ResponseObject | ReferenceObject | undefined; + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: any; + /** + * The documentation of responses other than the ones declared for specific HTTP response codes. It can be used to cover undeclared responses. {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object Reference Object} can be used to link to a response that is defined at the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swaggerResponses Swagger Object's responses} section. + */ + default?: ResponseObject | ReferenceObject; +} + +/** + * The Schema Object allows the definition of input and output data types. These types can be objects, but also primitives and arrays. This object is based on the {@link http://json-schema.org/ JSON Schema Specification Draft 4} and uses a predefined subset of it. On top of this subset, there are extensions provided by this specification to allow for more complete documentation. + * + * Further information about the properties can be found in {@link https://tools.ietf.org/html/draft-zyp-json-schema-04 JSON Schema Core} and {@link https://tools.ietf.org/html/draft-fge-json-schema-validation-00 JSON Schema Validation}. Unless stated otherwise, the property definitions follow the JSON Schema specification as referenced here. + * + * The following properties are taken directly from the JSON Schema definition and follow the same specifications: + * + * - $ref - As a {@link https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03 JSON Reference} + * - format (See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#dataTypeFormat Data Type Formats} for further details) + * - title + * - description ({@link https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown GFM syntax} can be used for rich text representation) + * - default (Unlike JSON Schema, the value MUST conform to the defined type for the Schema Object) + * - multipleOf + * - maximum + * - exclusiveMaximum + * - minimum + * - exclusiveMinimum + * - maxLength + * - minLength + * - pattern + * - maxItems + * - minItems + * - uniqueItems + * - maxProperties + * - minProperties + * - required + * - enum + * - type + * + * The following properties are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification. Their definition is the same as the one from JSON Schema, only where the original definition references the JSON Schema definition, the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schema-object Schema Object} definition is used instead. + * + * - items + * - allOf + * - properties + * - additionalProperties + * + * Other than the JSON Schema subset fields, the following fields may be used for further schema documentation. + * + * **Composition and Inheritance (Polymorphism)** + * + * Swagger allows combining and extending model definitions using the `allOf` property of JSON Schema, in effect offering model composition. `allOf` takes in an array of object definitions that are validated _independently_ but together compose a single object. + * + * While composition offers model extensibility, it does not imply a hierarchy between the models. To support polymorphism, Swagger adds the support of the `discriminator` field. When used, the `discriminator` will be the name of the property used to decide which schema definition is used to validate the structure of the model. As such, the `discriminator` field MUST be a required field. The value of the chosen property has to be the friendly name given to the model under the `definitions` property. As such, inline schema definitions, which do not have a given id, _cannot_ be used in polymorphism. + * + * **XML Modeling** + * + * The {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#schemaXml xml} property allows extra definitions when translating the JSON definition to XML. The {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#xml-object XML Object} contains additional information about the available options. + * + * **Schema Object Examples** + * + * Primitive Sample + * + * Unlike previous versions of Swagger, Schema definitions can be used to describe primitive and arrays as well. + * + * @example + * ```yaml + * type: string + * format: email + * ``` + * + * Simple Model + * + * @example + * ```yaml + * type: object + * required: + * - name + * properties: + * name: + * type: string + * address: + * $ref: '#/definitions/Address' + * age: + * type: integer + * format: int32 + * minimum: 0 + * ``` + * + * Model with Map/Dictionary Properties + * + * For a simple string to string mapping: + * + * @example + * ```yaml + * type: object + * additionalProperties: + * type: string + * ``` + * + * For a string to model mapping: + * + * @example + * ```yaml + * type: object + * additionalProperties: + * $ref: '#/definitions/ComplexModel' + * ``` + * + * Model with Example + * + * @example + * ```yaml + * type: object + * properties: + * id: + * type: integer + * format: int64 + * name: + * type: string + * required: + * - name + * example: + * name: Puma + * id: 1 + * ``` + * + * Models with Composition + * + * @example + * ```yaml + * definitions: + * ErrorModel: + * type: object + * required: + * - message + * - code + * properties: + * message: + * type: string + * code: + * type: integer + * minimum: 100 + * maximum: 600 + * ExtendedErrorModel: + * allOf: + * - $ref: '#/definitions/ErrorModel' + * - type: object + * required: + * - rootCause + * properties: + * rootCause: + * type: string + * ``` + * + * Models with Polymorphism Support + * + * @example + * ```yaml + * definitions: + * Pet: + * type: object + * discriminator: petType + * properties: + * name: + * type: string + * petType: + * type: string + * required: + * - name + * - petType + * Cat: + * description: A representation of a cat + * allOf: + * - $ref: '#/definitions/Pet' + * - type: object + * properties: + * huntingSkill: + * type: string + * description: The measured skill for hunting + * default: lazy + * enum: + * - clueless + * - lazy + * - adventurous + * - aggressive + * required: + * - huntingSkill + * Dog: + * description: A representation of a dog + * allOf: + * - $ref: '#/definitions/Pet' + * - type: object + * properties: + * packSize: + * type: integer + * format: int32 + * description: the size of the pack the dog is from + * default: 0 + * minimum: 0 + * required: + * - packSize + * ``` + */ +export interface SchemaObject extends JsonSchemaDraft4 { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * The `additionalProperties` keyword is used to control the handling of extra stuff, that is, properties whose names are not listed in the `properties` keyword or match any of the regular expressions in the `patternProperties` keyword. By default any additional properties are allowed. + * + * The value of the `additionalProperties` keyword is a schema that will be used to validate any properties in the {@link https://json-schema.org/learn/glossary#instance instance} that are not matched by `properties` or `patternProperties`. Setting the `additionalProperties` schema to `false` means no additional properties will be allowed. + * + * It's important to note that `additionalProperties` only recognizes properties declared in the same {@link https://json-schema.org/learn/glossary#subschema subschema} as itself. So, `additionalProperties` can restrict you from "extending" a schema using {@link https://json-schema.org/understanding-json-schema/reference/combining combining} keywords such as {@link https://json-schema.org/understanding-json-schema/reference/combining#allof allOf}. + */ + additionalProperties?: SchemaObject | boolean; + /** + * `allOf`: (AND) Must be valid against _all_ of the {@link https://json-schema.org/learn/glossary#subschema subschemas} + * + * To validate against `allOf`, the given data must be valid against all of the given subschemas. + * + * {@link https://json-schema.org/understanding-json-schema/reference/combining#allof allOf} can not be used to "extend" a schema to add more details to it in the sense of object-oriented inheritance. {@link https://json-schema.org/learn/glossary#instance Instances} must independently be valid against "all of" the schemas in the `allOf`. See the section on {@link https://json-schema.org/understanding-json-schema/reference/object#extending Extending Closed Schemas} for more information. + */ + allOf?: ReadonlyArray; + /** + * Adds support for polymorphism. The discriminator is the schema property name that is used to differentiate between other schema that inherit this schema. The property name used MUST be defined at this schema and it MUST be in the `required` property list. When used, the value MUST be the name of this schema or any schema that inherits it. + */ + discriminator?: string; + /** + * A free-form property to include an example of an instance for this schema. + */ + example?: unknown; + /** + * Additional external documentation for this schema. + */ + externalDocs?: ExternalDocumentationObject; + /** + * List validation is useful for arrays of arbitrary length where each item matches the same schema. For this kind of array, set the `items` {@link https://json-schema.org/learn/glossary#keyword keyword} to a single schema that will be used to validate all of the items in the array. + * + * The `items` keyword can be used to control whether it's valid to have additional items in a tuple beyond what is defined in `prefixItems`. The value of the `items` keyword is a schema that all additional items must pass in order for the keyword to validate. + * + * Note that `items` doesn't "see inside" any {@link https://json-schema.org/learn/glossary#instance instances} of `allOf`, `anyOf`, or `oneOf` in the same {@link https://json-schema.org/learn/glossary#subschema subschema}. + */ + items?: SchemaObject; + /** + * The properties (key-value pairs) on an object are defined using the `properties` {@link https://json-schema.org/learn/glossary#keyword keyword}. The value of `properties` is an object, where each key is the name of a property and each value is a {@link https://json-schema.org/learn/glossary#schema schema} used to validate that property. Any property that doesn't match any of the property names in the `properties` keyword is ignored by this keyword. + */ + properties?: Record; + /** + * Relevant only for Schema `"properties"` definitions. Declares the property as "read only". This means that it MAY be sent as part of a response but MUST NOT be sent as part of the request. Properties marked as `readOnly` being `true` SHOULD NOT be in the `required` list of the defined schema. Default value is `false`. + */ + readOnly?: boolean; + /** + * This MAY be used only on properties schemas. It has no effect on root schemas. Adds Additional metadata to describe the XML representation format of this property. + */ + xml?: XMLObject; +} + +/** + * Lists the available scopes for an OAuth2 security scheme. + * + * **Scopes Object Example** + * + * @example + * ```yaml + * write:pets: modify pets in your account + * read:pets: read your pets + * ``` + */ +export interface ScopesObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: any; + /** + * Maps between a name of a scope to a short description of it (as the value of the property). + */ + [name: string]: string; +} + +/** + * A declaration of the security schemes available to be used in the specification. This does not enforce the security schemes on the operations and only serves to provide the relevant details for each scheme. + * + * **Security Definitions Object Example** + * + * @example + * ```yaml + * api_key: + * type: apiKey + * name: api_key + * in: header + * petstore_auth: + * type: oauth2 + * authorizationUrl: http://swagger.io/api/oauth/dialog + * flow: implicit + * scopes: + * write:pets: modify pets in your account + * read:pets: read your pets + * ``` + */ +export interface SecurityDefinitionsObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: any; + /** + * A single security scheme definition, mapping a "name" to the scheme it defines. + */ + [name: string]: SecuritySchemeObject; +} + +/** + * Lists the required security schemes to execute this operation. The object can have multiple security schemes declared in it which are all required (that is, there is a logical AND between the schemes). + * + * The name used for each property MUST correspond to a security scheme declared in the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#security-definitions-object Security Definitions}. + * + * **Security Requirement Object Examples** + * + * Non-OAuth2 Security Requirement + * + * @example + * ```yaml + * api_key: [] + * ``` + * + * OAuth2 Security Requirement + * + * @example + * ```yaml + * petstore_auth: + * - write:pets + * - read:pets + * ``` + */ +export interface SecurityRequirementObject { + /** + * Each name must correspond to a security scheme which is declared in the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#securityDefinitions Security Definitions}. If the security scheme is of type `"oauth2"`, then the value is a list of scope names required for the execution. For other security scheme types, the array MUST be empty. + */ + [name: string]: ReadonlyArray; +} + +/** + * Allows the definition of a security scheme that can be used by the operations. Supported schemes are basic authentication, an API key (either as a header or as a query parameter) and OAuth2's common flows (implicit, password, application and access code). + * + * **Security Scheme Object Example** + * + * Basic Authentication Sample + * + * @example + * ```yaml + * type: basic + * ``` + * + * API Key Sample + * + * @example + * ```yaml + * type: apiKey + * name: api_key + * in: header + * ``` + * + * Implicit OAuth2 Sample + * + * @example + * ```yaml + * type: oauth2 + * authorizationUrl: http://swagger.io/api/oauth/dialog + * flow: implicit + * scopes: + * write:pets: modify pets in your account + * read:pets: read your pets + * ``` + */ +export type SecuritySchemeObject = { + /** + * A short description for security scheme. + */ + description?: string; +} & ( + | { + /** + * **Required** The location of the API key. Valid values are `"query"` or `"header"`. + */ + in: 'header' | 'query'; + /** + * **Required**. The name of the header or query parameter to be used. + */ + name: string; + /** + * **Required**. The type of the security scheme. Valid values are `"basic"`, `"apiKey"` or `"oauth2"`. + */ + type: 'apiKey'; + } + | { + /** + * **Required (`"implicit"`, `"accessCode"`)**. The authorization URL to be used for this flow. This SHOULD be in the form of a URL. + */ + authorizationUrl?: string; + /** + * **Required**. The flow used by the OAuth2 security scheme. Valid values are `"implicit"`, `"password"`, `"application"` or `"accessCode"`. + */ + flow: 'accessCode' | 'application' | 'implicit' | 'password'; + /** + * **Required**. The available scopes for the OAuth2 security scheme. + */ + scopes: ScopesObject; + /** + * **Required (`"password"`, `"application"`, `"accessCode"`)**. The token URL to be used for this flow. This SHOULD be in the form of a URL. + */ + tokenUrl?: string; + /** + * **Required**. The type of the security scheme. Valid values are `"basic"`, `"apiKey"` or `"oauth2"`. + */ + type: 'oauth2'; + } + | { + /** + * **Required**. The type of the security scheme. Valid values are `"basic"`, `"apiKey"` or `"oauth2"`. + */ + type: 'basic'; + } +); + +/** + * Allows adding meta data to a single tag that is used by the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#operation-object Operation Object}. It is not mandatory to have a Tag Object per tag used there. + * + * **Tag Object Example** + * + * @example + * ```yaml + * name: pet + * description: Pets operations + * ``` + */ +export interface TagObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * A short description for the tag. {@link https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown GFM syntax} can be used for rich text representation. + */ + description?: string; + /** + * Additional external documentation for this tag. + */ + externalDocs?: ExternalDocumentationObject; + /** + * **Required**. The name of the tag. + */ + name: string; +} + +/** + * A metadata object that allows for more fine-tuned XML model definitions. + * + * When using arrays, XML element names are _not_ inferred (for singular/plural forms) and the `name` property should be used to add that information. See examples for expected behavior. + */ +export interface XMLObject { + /** + * Allows extensions to the Swagger Schema. The field name MUST begin with `x-`, for example, `x-internal-id`. The value can be `null`, a primitive, an array or an object. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#specification-extensions Vendor Extensions} for further details. + */ + [name: `x-${string}`]: unknown; + /** + * Declares whether the property definition translates to an attribute instead of an element. Default value is `false`. + */ + attribute?: boolean; + /** + * Replaces the name of the element/attribute used for the described schema property. When defined within the Items Object (`items`), it will affect the name of the individual XML elements within the list. When defined alongside `type` being `array` (outside the `items`), it will affect the wrapping element and only if `wrapped` is `true`. If `wrapped` is `false`, it will be ignored. + */ + name?: string; + /** + * The URL of the namespace definition. Value SHOULD be in the form of a URL. + */ + namespace?: string; + /** + * The prefix to be used for the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#xmlName name}. + */ + prefix?: string; + /** + * MAY be used only for an array definition. Signifies whether the array is wrapped (for example, ``) or unwrapped (``). Default value is `false`. The definition takes effect only when defined alongside `type` being `array` (outside the `items`). + */ + wrapped?: boolean; +} 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 a2a8cc3f1..4c5380e00 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,5 +1,6 @@ import type { IR } from '../../../ir/types'; import { canProcessRef } from '../../shared/utils/filter'; +import { mergeParametersObjects } from '../../shared/utils/parameter'; import type { OpenApiV3_0_X, ParameterObject, @@ -9,11 +10,7 @@ import type { SecuritySchemeObject, } from '../types/spec'; import { parseOperation } from './operation'; -import { - mergeParametersObjects, - parametersArrayToObject, - parseParameter, -} from './parameter'; +import { parametersArrayToObject, parseParameter } from './parameter'; import { parseRequestBody } from './requestBody'; import { parseSchema } from './schema'; 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 39fd98511..bccc97b0e 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 @@ -1,5 +1,8 @@ import type { IR } from '../../../ir/types'; -import { operationToId } from '../../shared/utils/operation'; +import { + ensureUniqueOperationId, + operationToId, +} from '../../shared/utils/operation'; import type { OperationObject, PathItemObject, @@ -34,7 +37,7 @@ const parseOperationJsDoc = ({ irOperation.summary = operation.summary; } - if (operation.tags && operation.tags.length) { + if (operation.tags?.length) { irOperation.tags = operation.tags; } }; @@ -215,18 +218,12 @@ export const parseOperation = ({ path: keyof IR.PathsObject; securitySchemesMap: Map; }) => { - // 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.`, - ); - } else { - operationIds.set(operation.operationId, operationKey); - } - } + ensureUniqueOperationId({ + id: operation.operationId, + method, + operationIds, + path, + }); if (!context.ir.paths) { context.ir.paths = {}; diff --git a/packages/openapi-ts/src/openApi/3.0.x/parser/parameter.ts b/packages/openapi-ts/src/openApi/3.0.x/parser/parameter.ts index 8769f5f4c..4d3cea44a 100644 --- a/packages/openapi-ts/src/openApi/3.0.x/parser/parameter.ts +++ b/packages/openapi-ts/src/openApi/3.0.x/parser/parameter.ts @@ -44,7 +44,7 @@ const defaultExplode = (style: Required['style']): boolean => { */ const defaultStyle = ( _in: ParameterObject['in'], -): Required['style'] => { +): Required['style'] => { switch (_in) { case 'header': case 'path': @@ -87,68 +87,6 @@ export const parametersArrayToObject = ({ return parametersObject; }; -export const mergeParametersObjects = ({ - source, - target, -}: { - source: IR.ParametersObject | undefined; - target: IR.ParametersObject | undefined; -}): IR.ParametersObject | undefined => { - const result = { ...target }; - - if (source) { - if (source.cookie) { - if (result.cookie) { - result.cookie = { - ...result.cookie, - ...source.cookie, - }; - } else { - result.cookie = source.cookie; - } - } - - if (source.header) { - if (result.header) { - result.header = { - ...result.header, - ...source.header, - }; - } else { - result.header = source.header; - } - } - - if (source.path) { - if (result.path) { - result.path = { - ...result.path, - ...source.path, - }; - } else { - result.path = source.path; - } - } - - if (source.query) { - if (result.query) { - result.query = { - ...result.query, - ...source.query, - }; - } else { - result.query = source.query; - } - } - } - - if (!Object.keys(result).length) { - return; - } - - return result; -}; - const parameterToIrParameter = ({ context, parameter, diff --git a/packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts b/packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts index 847a81906..cabb492a3 100644 --- a/packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts +++ b/packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts @@ -1,31 +1,19 @@ import type { IR } from '../../../ir/types'; import { addItemsToSchema } from '../../../ir/utils'; import { refToName } from '../../../utils/ref'; +import type { + SchemaContext, + SchemaType, + SchemaWithRequired, +} from '../../shared/types/schema'; import { discriminatorValue } from '../../shared/utils/discriminator'; import type { ReferenceObject, SchemaObject } from '../types/spec'; -interface SchemaContext { - /** - * Optional schema $ref. This will be only defined for reusable components - * from the OpenAPI specification. - */ - $ref?: string; - context: IR.Context; -} - -type SchemaWithRequired> = Omit< - SchemaObject, - K -> & - Pick, K>; - -type SchemaType = Required['type']; - export const getSchemaType = ({ schema, }: { schema: SchemaObject; -}): SchemaType | undefined => { +}): SchemaType | undefined => { if (schema.type) { return schema.type; } @@ -281,7 +269,7 @@ const parseAllOf = ({ context, schema, }: SchemaContext & { - schema: SchemaWithRequired<'allOf'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { let irSchema = initIrSchema({ schema }); @@ -417,7 +405,7 @@ const parseAnyOf = ({ context, schema, }: SchemaContext & { - schema: SchemaWithRequired<'anyOf'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { let irSchema = initIrSchema({ schema }); @@ -490,7 +478,7 @@ const parseEnum = ({ context, schema, }: SchemaContext & { - schema: SchemaWithRequired<'enum'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { let irSchema = initIrSchema({ schema }); @@ -500,7 +488,7 @@ const parseEnum = ({ for (const [index, enumValue] of schema.enum.entries()) { const typeOfEnumValue = typeof enumValue; - let enumType: SchemaType | 'null' | undefined; + let enumType: SchemaType | 'null' | undefined; if ( typeOfEnumValue === 'string' || @@ -558,7 +546,7 @@ const parseOneOf = ({ context, schema, }: SchemaContext & { - schema: SchemaWithRequired<'oneOf'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { let irSchema = initIrSchema({ schema }); @@ -657,7 +645,7 @@ const parseNullableType = ({ schema, }: SchemaContext & { irSchema?: IR.SchemaObject; - schema: SchemaWithRequired<'type'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { if (!irSchema) { irSchema = initIrSchema({ schema }); @@ -699,7 +687,7 @@ const parseType = ({ context, schema, }: SchemaContext & { - schema: SchemaWithRequired<'type'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { const irSchema = initIrSchema({ schema }); @@ -741,7 +729,7 @@ const parseOneType = ({ schema, }: SchemaContext & { irSchema?: IR.SchemaObject; - schema: SchemaWithRequired<'type'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { if (!irSchema) { irSchema = initIrSchema({ schema }); @@ -834,7 +822,7 @@ export const schemaToIrSchema = ({ return parseEnum({ $ref, context, - schema: schema as SchemaWithRequired<'enum'>, + schema: schema as SchemaWithRequired, }); } @@ -842,7 +830,7 @@ export const schemaToIrSchema = ({ return parseAllOf({ $ref, context, - schema: schema as SchemaWithRequired<'allOf'>, + schema: schema as SchemaWithRequired, }); } @@ -850,7 +838,7 @@ export const schemaToIrSchema = ({ return parseAnyOf({ $ref, context, - schema: schema as SchemaWithRequired<'anyOf'>, + schema: schema as SchemaWithRequired, }); } @@ -858,7 +846,7 @@ export const schemaToIrSchema = ({ return parseOneOf({ $ref, context, - schema: schema as SchemaWithRequired<'oneOf'>, + schema: schema as SchemaWithRequired, }); } @@ -867,7 +855,7 @@ export const schemaToIrSchema = ({ return parseType({ $ref, context, - schema: schema as SchemaWithRequired<'type'>, + schema: schema as SchemaWithRequired, }); } 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 c1110a554..88efea1ca 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,5 +1,6 @@ import type { IR } from '../../../ir/types'; import { canProcessRef } from '../../shared/utils/filter'; +import { mergeParametersObjects } from '../../shared/utils/parameter'; import type { OpenApiV3_1_X, ParameterObject, @@ -9,11 +10,7 @@ import type { SecuritySchemeObject, } from '../types/spec'; import { parseOperation } from './operation'; -import { - mergeParametersObjects, - parametersArrayToObject, - parseParameter, -} from './parameter'; +import { parametersArrayToObject, parseParameter } from './parameter'; import { parseRequestBody } from './requestBody'; import { parseSchema } from './schema'; 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 e7ada0d17..66ea9b947 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 @@ -1,5 +1,8 @@ import type { IR } from '../../../ir/types'; -import { operationToId } from '../../shared/utils/operation'; +import { + ensureUniqueOperationId, + operationToId, +} from '../../shared/utils/operation'; import type { OperationObject, PathItemObject, @@ -34,7 +37,7 @@ const parseOperationJsDoc = ({ irOperation.summary = operation.summary; } - if (operation.tags && operation.tags.length) { + if (operation.tags?.length) { irOperation.tags = operation.tags; } }; @@ -200,18 +203,12 @@ export const parseOperation = ({ path: keyof IR.PathsObject; securitySchemesMap: Map; }) => { - // 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.`, - ); - } else { - operationIds.set(operation.operationId, operationKey); - } - } + ensureUniqueOperationId({ + id: operation.operationId, + method, + operationIds, + path, + }); if (!context.ir.paths) { context.ir.paths = {}; diff --git a/packages/openapi-ts/src/openApi/3.1.x/parser/parameter.ts b/packages/openapi-ts/src/openApi/3.1.x/parser/parameter.ts index 95575cd9d..4ff96f272 100644 --- a/packages/openapi-ts/src/openApi/3.1.x/parser/parameter.ts +++ b/packages/openapi-ts/src/openApi/3.1.x/parser/parameter.ts @@ -87,68 +87,6 @@ export const parametersArrayToObject = ({ return parametersObject; }; -export const mergeParametersObjects = ({ - source, - target, -}: { - source: IR.ParametersObject | undefined; - target: IR.ParametersObject | undefined; -}): IR.ParametersObject | undefined => { - const result = { ...target }; - - if (source) { - if (source.cookie) { - if (result.cookie) { - result.cookie = { - ...result.cookie, - ...source.cookie, - }; - } else { - result.cookie = source.cookie; - } - } - - if (source.header) { - if (result.header) { - result.header = { - ...result.header, - ...source.header, - }; - } else { - result.header = source.header; - } - } - - if (source.path) { - if (result.path) { - result.path = { - ...result.path, - ...source.path, - }; - } else { - result.path = source.path; - } - } - - if (source.query) { - if (result.query) { - result.query = { - ...result.query, - ...source.query, - }; - } else { - result.query = source.query; - } - } - } - - if (!Object.keys(result).length) { - return; - } - - return result; -}; - const parameterToIrParameter = ({ context, parameter, diff --git a/packages/openapi-ts/src/openApi/3.1.x/parser/schema.ts b/packages/openapi-ts/src/openApi/3.1.x/parser/schema.ts index 8a3bb5bfb..16e5aa202 100644 --- a/packages/openapi-ts/src/openApi/3.1.x/parser/schema.ts +++ b/packages/openapi-ts/src/openApi/3.1.x/parser/schema.ts @@ -1,31 +1,19 @@ import type { IR } from '../../../ir/types'; import { addItemsToSchema } from '../../../ir/utils'; import { refToName } from '../../../utils/ref'; +import type { + SchemaContext, + SchemaType, + SchemaWithRequired, +} from '../../shared/types/schema'; import { discriminatorValue } from '../../shared/utils/discriminator'; import type { SchemaObject } from '../types/spec'; -interface SchemaContext { - /** - * Optional schema $ref. This will be only defined for reusable components - * from the OpenAPI specification. - */ - $ref?: string; - context: IR.Context; -} - -type SchemaWithRequired> = Omit< - SchemaObject, - K -> & - Pick, K>; - -type SchemaType = Extract['type'], string>; - export const getSchemaTypes = ({ schema, }: { schema: SchemaObject; -}): ReadonlyArray => { +}): ReadonlyArray> => { if (typeof schema.type === 'string') { return [schema.type]; } @@ -334,7 +322,7 @@ const parseAllOf = ({ context, schema, }: SchemaContext & { - schema: SchemaWithRequired<'allOf'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { let irSchema = initIrSchema({ schema }); @@ -459,7 +447,7 @@ const parseAnyOf = ({ context, schema, }: SchemaContext & { - schema: SchemaWithRequired<'anyOf'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { let irSchema = initIrSchema({ schema }); @@ -532,7 +520,7 @@ const parseEnum = ({ context, schema, }: SchemaContext & { - schema: SchemaWithRequired<'enum'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { let irSchema = initIrSchema({ schema }); @@ -543,7 +531,7 @@ const parseEnum = ({ for (const [index, enumValue] of schema.enum.entries()) { const typeOfEnumValue = typeof enumValue; - let enumType: SchemaType | undefined; + let enumType: SchemaType | undefined; if ( typeOfEnumValue === 'string' || @@ -595,7 +583,7 @@ const parseOneOf = ({ context, schema, }: SchemaContext & { - schema: SchemaWithRequired<'oneOf'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { let irSchema = initIrSchema({ schema }); @@ -677,7 +665,7 @@ const parseOneOf = ({ const parseRef = ({ schema, }: SchemaContext & { - schema: SchemaWithRequired<'$ref'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { const irSchema = initIrSchema({ schema }); @@ -695,7 +683,7 @@ const parseOneType = ({ }: SchemaContext & { irSchema?: IR.SchemaObject; schema: Omit & { - type: SchemaType; + type: SchemaType; }; }): IR.SchemaObject => { if (!irSchema) { @@ -762,7 +750,7 @@ const parseManyTypes = ({ }: SchemaContext & { irSchema?: IR.SchemaObject; schema: Omit & { - type: ReadonlyArray; + type: ReadonlyArray>; }; }): IR.SchemaObject => { if (!irSchema) { @@ -813,7 +801,7 @@ const parseType = ({ context, schema, }: SchemaContext & { - schema: SchemaWithRequired<'type'>; + schema: SchemaWithRequired; }): IR.SchemaObject => { const irSchema = initIrSchema({ schema }); @@ -877,7 +865,7 @@ export const schemaToIrSchema = ({ return parseRef({ $ref, context, - schema: schema as SchemaWithRequired<'$ref'>, + schema: schema as SchemaWithRequired, }); } @@ -885,7 +873,7 @@ export const schemaToIrSchema = ({ return parseEnum({ $ref, context, - schema: schema as SchemaWithRequired<'enum'>, + schema: schema as SchemaWithRequired, }); } @@ -893,7 +881,7 @@ export const schemaToIrSchema = ({ return parseAllOf({ $ref, context, - schema: schema as SchemaWithRequired<'allOf'>, + schema: schema as SchemaWithRequired, }); } @@ -901,7 +889,7 @@ export const schemaToIrSchema = ({ return parseAnyOf({ $ref, context, - schema: schema as SchemaWithRequired<'anyOf'>, + schema: schema as SchemaWithRequired, }); } @@ -909,7 +897,7 @@ export const schemaToIrSchema = ({ return parseOneOf({ $ref, context, - schema: schema as SchemaWithRequired<'oneOf'>, + schema: schema as SchemaWithRequired, }); } @@ -918,7 +906,7 @@ export const schemaToIrSchema = ({ return parseType({ $ref, context, - schema: schema as SchemaWithRequired<'type'>, + schema: schema as SchemaWithRequired, }); } diff --git a/packages/openapi-ts/src/openApi/__tests__/index.test.ts b/packages/openapi-ts/src/openApi/__tests__/index.test.ts index 4f749310e..a3c4faa5e 100644 --- a/packages/openapi-ts/src/openApi/__tests__/index.test.ts +++ b/packages/openapi-ts/src/openApi/__tests__/index.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { type OpenApi, parseExperimental, parseLegacy } from '..'; +import { type OpenApi, parseLegacy, parseOpenApiSpec } from '..'; import type { OpenApiV3_0_X } from '../3.0.x'; import { parseV3_0_X } from '../3.0.x'; import type { OpenApiV3_1_X } from '../3.1.x'; @@ -104,8 +104,8 @@ describe('experimentalParser', () => { openapi: '3.0.0', paths: {}, }; - parseExperimental({ - // @ts-ignore + parseOpenApiSpec({ + // @ts-expect-error config: {}, spec, }); @@ -121,8 +121,8 @@ describe('experimentalParser', () => { openapi: '3.0.1', paths: {}, }; - parseExperimental({ - // @ts-ignore + parseOpenApiSpec({ + // @ts-expect-error config: {}, spec, }); @@ -138,8 +138,8 @@ describe('experimentalParser', () => { openapi: '3.0.2', paths: {}, }; - parseExperimental({ - // @ts-ignore + parseOpenApiSpec({ + // @ts-expect-error config: {}, spec, }); @@ -155,8 +155,8 @@ describe('experimentalParser', () => { openapi: '3.0.3', paths: {}, }; - parseExperimental({ - // @ts-ignore + parseOpenApiSpec({ + // @ts-expect-error config: {}, spec, }); @@ -172,8 +172,8 @@ describe('experimentalParser', () => { openapi: '3.0.4', paths: {}, }; - parseExperimental({ - // @ts-ignore + parseOpenApiSpec({ + // @ts-expect-error config: {}, spec, }); @@ -188,8 +188,8 @@ describe('experimentalParser', () => { }, openapi: '3.1.0', }; - parseExperimental({ - // @ts-ignore + parseOpenApiSpec({ + // @ts-expect-error config: {}, spec, }); @@ -204,8 +204,8 @@ describe('experimentalParser', () => { }, openapi: '3.1.1', }; - parseExperimental({ - // @ts-ignore + parseOpenApiSpec({ + // @ts-expect-error config: {}, spec, }); diff --git a/packages/openapi-ts/src/openApi/index.ts b/packages/openapi-ts/src/openApi/index.ts index 0fdfe4c2d..51e2de24e 100644 --- a/packages/openapi-ts/src/openApi/index.ts +++ b/packages/openapi-ts/src/openApi/index.ts @@ -1,6 +1,7 @@ import { IRContext } from '../ir/context'; import type { IR } from '../ir/types'; import type { Config } from '../types/config'; +import { parseV2_0_X } from './2.0.x'; import { parseV3_0_X } from './3.0.x'; import { parseV3_1_X } from './3.1.x'; import type { Client } from './common/interfaces/client'; @@ -55,8 +56,11 @@ export function parseLegacy({ ); } -// TODO: parser - add JSDoc comment -export const parseExperimental = ({ +/** + * Parse the resolved OpenAPI specification. This will populate and return + * `context` with intermediate representation obtained from the parsed spec. + */ +export const parseOpenApiSpec = ({ config, spec, }: { @@ -65,13 +69,15 @@ export const parseExperimental = ({ }): IR.Context | undefined => { const context = new IRContext({ config, - spec: spec as Record, + spec: spec as OpenApi.V2_0_X | OpenApi.V3_0_X | OpenApi.V3_1_X, }); - // TODO: parser - handle Swagger 2.0 + if ('swagger' in context.spec) { + parseV2_0_X(context as IR.Context); + return context; + } - const ctx = context as IR.Context; - switch (ctx.spec.openapi) { + switch (context.spec.openapi) { case '3.0.0': case '3.0.1': case '3.0.2': @@ -84,10 +90,8 @@ export const parseExperimental = ({ parseV3_1_X(context as IR.Context); return context; default: - // TODO: parser - uncomment after removing legacy parser. - // For now, we fall back to legacy parser if spec version - // is not supported - // throw new Error('Unsupported OpenAPI specification'); - return; + break; } + + throw new Error('Unsupported OpenAPI specification'); }; diff --git a/packages/openapi-ts/src/openApi/shared/types/schema.d.ts b/packages/openapi-ts/src/openApi/shared/types/schema.d.ts new file mode 100644 index 000000000..5a9883043 --- /dev/null +++ b/packages/openapi-ts/src/openApi/shared/types/schema.d.ts @@ -0,0 +1,27 @@ +import type { IR } from '../../../ir/types'; + +export interface SchemaContext { + /** + * Optional schema $ref. This will be only defined for reusable components + * from the OpenAPI specification. + */ + $ref?: string; + context: IR.Context; +} + +export type SchemaWithRequired< + S extends { + type?: unknown; + }, + K extends keyof S, +> = { + [P in keyof S as P extends K ? never : P]: S[P]; +} & { + [P in K]-?: S[P]; +}; + +export type SchemaType< + S extends { + type?: unknown; + }, +> = Extract['type'], string>; diff --git a/packages/openapi-ts/src/openApi/shared/utils/operation.ts b/packages/openapi-ts/src/openApi/shared/utils/operation.ts index aa352f76a..1128a80ca 100644 --- a/packages/openapi-ts/src/openApi/shared/utils/operation.ts +++ b/packages/openapi-ts/src/openApi/shared/utils/operation.ts @@ -2,6 +2,38 @@ import type { IR } from '../../../ir/types'; import { stringCase } from '../../../utils/stringCase'; import { sanitizeNamespaceIdentifier } from '../../common/parser/sanitize'; +/** + * Verifies that operation ID is unique. For now, we only warn when this isn't + * true as people like to not follow this part of the specification. In the + * future, we should add a strict check and throw on duplicate identifiers. + */ +export const ensureUniqueOperationId = ({ + id, + method, + operationIds, + path, +}: { + id: string | undefined; + method: IR.OperationObject['method']; + operationIds: Map; + path: keyof IR.PathsObject; +}) => { + if (!id) { + return; + } + + const operationKey = `${method.toUpperCase()} ${path}`; + + if (operationIds.has(id)) { + // TODO: parser - support throw on duplicate + console.warn( + `❗️ Duplicate operationId: ${id} in ${operationKey}. Please ensure your operation IDs are unique. This behavior is not supported and will likely lead to unexpected results.`, + ); + } else { + operationIds.set(id, operationKey); + } +}; + /** * Returns an operation ID to use across the application. By default, we try * to use the provided ID. If it's not provided or the SDK is configured diff --git a/packages/openapi-ts/src/openApi/shared/utils/parameter.ts b/packages/openapi-ts/src/openApi/shared/utils/parameter.ts new file mode 100644 index 000000000..a7136ed15 --- /dev/null +++ b/packages/openapi-ts/src/openApi/shared/utils/parameter.ts @@ -0,0 +1,63 @@ +import type { IR } from '../../../ir/types'; + +export const mergeParametersObjects = ({ + source, + target, +}: { + source: IR.ParametersObject | undefined; + target: IR.ParametersObject | undefined; +}): IR.ParametersObject | undefined => { + const result = { ...target }; + + if (source) { + if (source.cookie) { + if (result.cookie) { + result.cookie = { + ...result.cookie, + ...source.cookie, + }; + } else { + result.cookie = source.cookie; + } + } + + if (source.header) { + if (result.header) { + result.header = { + ...result.header, + ...source.header, + }; + } else { + result.header = source.header; + } + } + + if (source.path) { + if (result.path) { + result.path = { + ...result.path, + ...source.path, + }; + } else { + result.path = source.path; + } + } + + if (source.query) { + if (result.query) { + result.query = { + ...result.query, + ...source.query, + }; + } else { + result.query = source.query; + } + } + } + + if (!Object.keys(result).length) { + return; + } + + return result; +}; diff --git a/packages/openapi-ts/src/openApi/types.d.ts b/packages/openapi-ts/src/openApi/types.d.ts index d850e9ccd..5a4031d9e 100644 --- a/packages/openapi-ts/src/openApi/types.d.ts +++ b/packages/openapi-ts/src/openApi/types.d.ts @@ -1,7 +1,10 @@ +import type { OpenApiV2_0_X } from './2.0.x'; import type { OpenApiV3_0_X } from './3.0.x'; import type { OpenApiV3_1_X } from './3.1.x'; export namespace OpenApi { + export type V2_0_X = OpenApiV2_0_X; + export type V3_0_X = OpenApiV3_0_X; export type V3_1_X = OpenApiV3_1_X; diff --git a/packages/openapi-ts/src/plugins/@hey-api/schemas/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/schemas/plugin.ts index 5070ac5f4..989755c55 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/schemas/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/schemas/plugin.ts @@ -1,5 +1,6 @@ import { compiler } from '../../../compiler'; import type { IR } from '../../../ir/types'; +import type { SchemaObject as OpenApiV2_0_XSchemaObject } from '../../../openApi/2.0.x/types/spec'; import type { ReferenceObject as OpenApiV3_0_XReferenceObject, SchemaObject as OpenApiV3_0_XSchemaObject, @@ -17,7 +18,10 @@ const stripSchema = ({ schema, }: { plugin: Plugin.Instance; - schema: OpenApiV3_0_XSchemaObject | OpenApiV3_1_XSchemaObject; + schema: + | OpenApiV2_0_XSchemaObject + | OpenApiV3_0_XSchemaObject + | OpenApiV3_1_XSchemaObject; }) => { if (plugin.type === 'form') { if (schema.description) { @@ -42,6 +46,82 @@ const stripSchema = ({ } }; +const schemaToJsonSchemaDraft_04 = ({ + context, + plugin, + schema: _schema, +}: { + context: IR.Context; + plugin: Plugin.Instance; + schema: OpenApiV2_0_XSchemaObject; +}): OpenApiV2_0_XSchemaObject => { + if (Array.isArray(_schema)) { + return _schema.map((item) => + schemaToJsonSchemaDraft_04({ + context, + plugin, + schema: item, + }), + ) as unknown as OpenApiV2_0_XSchemaObject; + } + + const schema = structuredClone(_schema); + + if (schema.$ref) { + // refs using unicode characters become encoded, didn't investigate why + // but the suspicion is this comes from `@hey-api/json-schema-ref-parser` + schema.$ref = decodeURI(schema.$ref); + return schema; + } + + stripSchema({ plugin, schema }); + + if ( + schema.additionalProperties && + typeof schema.additionalProperties !== 'boolean' + ) { + schema.additionalProperties = schemaToJsonSchemaDraft_04({ + context, + plugin, + schema: schema.additionalProperties, + }); + } + + if (schema.allOf) { + schema.allOf = schema.allOf.map((item) => + schemaToJsonSchemaDraft_04({ + context, + plugin, + schema: item, + }), + ); + } + + if (schema.items) { + schema.items = schemaToJsonSchemaDraft_04({ + context, + plugin, + schema: schema.items as OpenApiV2_0_XSchemaObject, + }); + } + + if (schema.properties) { + for (const name in schema.properties) { + const property = schema.properties[name]!; + + if (typeof property !== 'boolean') { + schema.properties[name] = schemaToJsonSchemaDraft_04({ + context, + plugin, + schema: property, + }); + } + } + } + + return schema; +}; + const schemaToJsonSchemaDraft_05 = ({ context, plugin, @@ -50,7 +130,7 @@ const schemaToJsonSchemaDraft_05 = ({ context: IR.Context; plugin: Plugin.Instance; schema: OpenApiV3_0_XSchemaObject | OpenApiV3_0_XReferenceObject; -}): object => { +}): OpenApiV3_0_XSchemaObject | OpenApiV3_0_XReferenceObject => { if (Array.isArray(_schema)) { return _schema.map((item) => schemaToJsonSchemaDraft_05({ @@ -58,7 +138,7 @@ const schemaToJsonSchemaDraft_05 = ({ plugin, schema: item, }), - ); + ) as OpenApiV3_0_XSchemaObject | OpenApiV3_0_XReferenceObject; } const schema = structuredClone(_schema); @@ -146,7 +226,7 @@ const schemaToJsonSchema2020_12 = ({ context: IR.Context; plugin: Plugin.Instance; schema: OpenApiV3_1_XSchemaObject; -}): object => { +}): OpenApiV3_1_XSchemaObject => { if (Array.isArray(_schema)) { return _schema.map((item) => schemaToJsonSchema2020_12({ @@ -154,7 +234,7 @@ const schemaToJsonSchema2020_12 = ({ plugin, schema: item, }), - ); + ) as OpenApiV3_1_XSchemaObject; } const schema = structuredClone(_schema); @@ -251,6 +331,7 @@ const schemaName = ({ name: string; plugin: Plugin.Instance; schema: + | OpenApiV2_0_XSchemaObject | OpenApiV3_0_XReferenceObject | OpenApiV3_0_XSchemaObject | OpenApiV3_1_XSchemaObject; @@ -259,6 +340,34 @@ const schemaName = ({ return ensureValidIdentifier(customName); }; +const schemasV2_0_X = ({ + context, + plugin, +}: { + context: IR.Context; + plugin: Plugin.Instance; +}) => { + if (!context.spec.definitions) { + return; + } + + for (const name in context.spec.definitions) { + const schema = context.spec.definitions[name]!; + const obj = schemaToJsonSchemaDraft_04({ + context, + plugin, + schema, + }); + const statement = compiler.constVariable({ + assertion: 'const', + exportConst: true, + expression: compiler.objectExpression({ obj }), + name: schemaName({ name, plugin, schema }), + }); + context.file({ id: schemasId })!.add(statement); + } +}; + const schemasV3_0_X = ({ context, plugin, @@ -322,36 +431,33 @@ export const handler: Plugin.Handler = ({ context, plugin }) => { path: plugin.output, }); - if (context.spec.openapi) { - const ctx = context as IR.Context; - switch (ctx.spec.openapi) { - // TODO: parser - handle Swagger 2.0 - case '3.0.0': - case '3.0.1': - case '3.0.2': - case '3.0.3': - case '3.0.4': - schemasV3_0_X({ - context: context as IR.Context, - plugin, - }); - break; - case '3.1.0': - case '3.1.1': - schemasV3_1_X({ - context: context as IR.Context, - plugin, - }); - break; - default: - break; - } + if ('swagger' in context.spec) { + schemasV2_0_X({ + context: context as IR.Context, + plugin, + }); + return; } - // OpenAPI 2.0 - // if ('swagger' in openApi) { - // Object.entries(openApi.definitions ?? {}).forEach(([name, definition]) => { - // addSchema(name, definition); - // }); - // } + switch (context.spec.openapi) { + case '3.0.0': + case '3.0.1': + case '3.0.2': + case '3.0.3': + case '3.0.4': + schemasV3_0_X({ + context: context as IR.Context, + plugin, + }); + break; + case '3.1.0': + case '3.1.1': + schemasV3_1_X({ + context: context as IR.Context, + plugin, + }); + break; + default: + break; + } }; diff --git a/packages/openapi-ts/src/plugins/@hey-api/schemas/types.d.ts b/packages/openapi-ts/src/plugins/@hey-api/schemas/types.d.ts index 0281e3716..c95c1182c 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/schemas/types.d.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/schemas/types.d.ts @@ -1,4 +1,5 @@ import type { OpenApiV2Schema, OpenApiV3Schema } from '../../../openApi'; +import type { SchemaObject as OpenApiV2_0_XSchemaObject } from '../../../openApi/2.0.x/types/spec'; import type { ReferenceObject as OpenApiV3_0_XReferenceObject, SchemaObject as OpenApiV3_0_XSchemaObject, @@ -17,6 +18,7 @@ export interface Config extends Plugin.Name<'@hey-api/schemas'> { schema: | OpenApiV2Schema | OpenApiV3Schema + | OpenApiV2_0_XSchemaObject | OpenApiV3_0_XReferenceObject | OpenApiV3_0_XSchemaObject | OpenApiV3_1_XSchemaObject, diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts index 4fb060496..42c88b818 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts @@ -115,7 +115,11 @@ const schemaToEnumObject = ({ } if (key) { - key = stringCase({ case: plugin.enumsCase, value: key }); + key = stringCase({ + case: plugin.enumsCase, + stripLeadingSeparators: false, + value: key, + }); digitsRegExp.lastIndex = 0; // TypeScript enum keys cannot be numbers diff --git a/packages/openapi-ts/src/plugins/types.d.ts b/packages/openapi-ts/src/plugins/types.d.ts index fdc85a35d..0abe93932 100644 --- a/packages/openapi-ts/src/plugins/types.d.ts +++ b/packages/openapi-ts/src/plugins/types.d.ts @@ -1,5 +1,6 @@ import type { IR } from '../ir/types'; -import type { OpenApi } from '../openApi'; +import type { OpenApi as LegacyOpenApi } from '../openApi'; +import type { OpenApi } from '../openApi/types'; import type { Client } from '../types/client'; import type { Files } from '../types/utils'; @@ -99,7 +100,7 @@ export namespace Plugin { * Plugin implementation for experimental parser. */ export type Handler = (args: { - context: IR.Context; + context: IR.Context; plugin: Plugin.Instance; }) => void; @@ -113,7 +114,7 @@ export namespace Plugin { export type LegacyHandler = (args: { client: Client; files: Files; - openApi: OpenApi; + openApi: LegacyOpenApi; plugin: Plugin.Instance; }) => void; diff --git a/packages/openapi-ts/src/utils/__tests__/stringCase.test.ts b/packages/openapi-ts/src/utils/__tests__/stringCase.test.ts index a3b5fbd46..3819304cc 100644 --- a/packages/openapi-ts/src/utils/__tests__/stringCase.test.ts +++ b/packages/openapi-ts/src/utils/__tests__/stringCase.test.ts @@ -1,154 +1,224 @@ import { describe, expect, it } from 'vitest'; +import type { StringCase } from '../../types/config'; import { stringCase } from '../stringCase'; -const cases = ['camelCase', 'PascalCase', 'snake_case'] as const; +const cases: ReadonlyArray = [ + 'camelCase', + 'PascalCase', + 'SCREAMING_SNAKE_CASE', + 'snake_case', +]; const scenarios: ReadonlyArray<{ PascalCase: string; + SCREAMING_SNAKE_CASE: string; camelCase: string; snake_case: string; + stripLeadingSeparators?: boolean; value: string; }> = [ { PascalCase: 'FooDtoById', + SCREAMING_SNAKE_CASE: 'FOO_DTO_BY_ID', camelCase: 'fooDtoById', snake_case: 'foo_dto_by_id', value: 'fooDTOById', }, { PascalCase: 'FooDtos', + SCREAMING_SNAKE_CASE: 'FOO_DTOS', camelCase: 'fooDtos', snake_case: 'foo_dtos', value: 'fooDTOs', }, { PascalCase: 'FooDtosById', + SCREAMING_SNAKE_CASE: 'FOO_DTOS_BY_ID', camelCase: 'fooDtosById', snake_case: 'foo_dtos_by_id', value: 'fooDTOsById', }, { PascalCase: 'DtoById', + SCREAMING_SNAKE_CASE: 'DTO_BY_ID', camelCase: 'dtoById', snake_case: 'dto_by_id', value: 'DTOById', }, { PascalCase: 'Dtos', + SCREAMING_SNAKE_CASE: 'DTOS', camelCase: 'dtos', snake_case: 'dtos', value: 'DTOs', }, { PascalCase: 'DtosById', + SCREAMING_SNAKE_CASE: 'DTOS_BY_ID', camelCase: 'dtosById', snake_case: 'dtos_by_id', value: 'DTOsById', }, { PascalCase: 'SomeJsonFile', + SCREAMING_SNAKE_CASE: 'SOME_JSON_FILE', camelCase: 'someJsonFile', snake_case: 'some_json_file', value: 'SOME_JSON_FILE', }, { PascalCase: 'SomeJsonsFile', + SCREAMING_SNAKE_CASE: 'SOME_JSONS_FILE', camelCase: 'someJsonsFile', snake_case: 'some_jsons_file', value: 'SOME_JSONs_FILE', }, { PascalCase: 'PostHtmlGuide', + SCREAMING_SNAKE_CASE: 'POST_HTML_GUIDE', camelCase: 'postHtmlGuide', snake_case: 'post_html_guide', value: 'postHTMLGuide', }, { PascalCase: 'PostHtmlScale', + SCREAMING_SNAKE_CASE: 'POST_HTML_SCALE', camelCase: 'postHtmlScale', snake_case: 'post_html_scale', value: 'postHTMLScale', }, { PascalCase: 'SnakeCase', + SCREAMING_SNAKE_CASE: 'SNAKE_CASE', camelCase: 'snakeCase', snake_case: 'snake_case', value: 'snake_case', }, { PascalCase: 'CamelCase', + SCREAMING_SNAKE_CASE: 'CAMEL_CASE', camelCase: 'camelCase', snake_case: 'camel_case', value: 'camelCase', }, { PascalCase: 'PascalCase', + SCREAMING_SNAKE_CASE: 'PASCAL_CASE', camelCase: 'pascalCase', snake_case: 'pascal_case', value: 'PascalCase', }, { PascalCase: 'IsXRated', + SCREAMING_SNAKE_CASE: 'IS_X_RATED', camelCase: 'isXRated', snake_case: 'is_x_rated', value: 'isXRated', }, { PascalCase: 'IsHtmlSafe', + SCREAMING_SNAKE_CASE: 'IS_HTML_SAFE', camelCase: 'isHtmlSafe', snake_case: 'is_html_safe', value: 'isHTMLSafe', }, { PascalCase: 'MyAspirations', + SCREAMING_SNAKE_CASE: 'MY_ASPIRATIONS', camelCase: 'myAspirations', snake_case: 'my_aspirations', value: 'MyAspirations', }, { PascalCase: 'IoK8sApimachineryPkgApisMetaV1DeleteOptions', + SCREAMING_SNAKE_CASE: 'IO_K8S_APIMACHINERY_PKG_APIS_META_V1_DELETE_OPTIONS', camelCase: 'ioK8sApimachineryPkgApisMetaV1DeleteOptions', snake_case: 'io_k8s_apimachinery_pkg_apis_meta_v1_delete_options', value: 'io.k8sApimachinery.pkg.apis.meta.v1.DeleteOptions', }, { PascalCase: 'GenericSchemaDuplicateIssue1SystemBoolean', + SCREAMING_SNAKE_CASE: 'GENERIC_SCHEMA_DUPLICATE_ISSUE_1_SYSTEM_BOOLEAN', camelCase: 'genericSchemaDuplicateIssue1SystemBoolean', snake_case: 'generic_schema_duplicate_issue_1_system_boolean', value: 'Generic.Schema.Duplicate.Issue`1[System.Boolean]', }, { PascalCase: 'GetApiVApiVersionUsersUserIdLocationLocationId', + SCREAMING_SNAKE_CASE: + 'GET_API_V_API_VERSION_USERS_USER_ID_LOCATION_LOCATION_ID', camelCase: 'getApiVApiVersionUsersUserIdLocationLocationId', snake_case: 'get_api_v_api_version_users_user_id_location_location_id', value: 'GET /api/v{api-version}/users/{userId}/location/{locationId}', }, { PascalCase: 'IPhoneS', + SCREAMING_SNAKE_CASE: 'I_PHONE_S', camelCase: 'iPhoneS', snake_case: 'i_phone_s', value: 'iPhone S', }, + { + PascalCase: '-100', + SCREAMING_SNAKE_CASE: '-100', + camelCase: '-100', + snake_case: '-100', + stripLeadingSeparators: false, + value: '-100', + }, + { + PascalCase: 'MyFoo', + SCREAMING_SNAKE_CASE: 'MY_FOO', + camelCase: 'myFoo', + snake_case: 'my_foo', + stripLeadingSeparators: false, + value: 'MyFoo', + }, ]; describe('stringCase', () => { describe.each(cases)('%s', (style) => { switch (style) { case 'PascalCase': - it.each(scenarios)('$value -> $PascalCase', ({ PascalCase, value }) => { - expect(stringCase({ case: style, value })).toBe(PascalCase); - }); + it.each(scenarios)( + '$value -> $PascalCase', + ({ PascalCase, stripLeadingSeparators, value }) => { + expect( + stringCase({ case: style, stripLeadingSeparators, value }), + ).toBe(PascalCase); + }, + ); break; case 'camelCase': - it.each(scenarios)('$value -> $camelCase', ({ camelCase, value }) => { - expect(stringCase({ case: style, value })).toBe(camelCase); - }); + it.each(scenarios)( + '$value -> $camelCase', + ({ camelCase, stripLeadingSeparators, value }) => { + expect( + stringCase({ case: style, stripLeadingSeparators, value }), + ).toBe(camelCase); + }, + ); + break; + case 'SCREAMING_SNAKE_CASE': + it.each(scenarios)( + '$value -> $SCREAMING_SNAKE_CASE', + ({ SCREAMING_SNAKE_CASE, stripLeadingSeparators, value }) => { + expect( + stringCase({ case: style, stripLeadingSeparators, value }), + ).toBe(SCREAMING_SNAKE_CASE); + }, + ); break; case 'snake_case': - it.each(scenarios)('$value -> $snake_case', ({ snake_case, value }) => { - expect(stringCase({ case: style, value })).toBe(snake_case); - }); + it.each(scenarios)( + '$value -> $snake_case', + ({ snake_case, stripLeadingSeparators, value }) => { + expect( + stringCase({ case: style, stripLeadingSeparators, value }), + ).toBe(snake_case); + }, + ); break; } }); diff --git a/packages/openapi-ts/src/utils/handlebars.ts b/packages/openapi-ts/src/utils/handlebars.ts index 4099f0529..53fdd1de6 100644 --- a/packages/openapi-ts/src/utils/handlebars.ts +++ b/packages/openapi-ts/src/utils/handlebars.ts @@ -1,92 +1,92 @@ import Handlebars from 'handlebars'; -// @ts-ignore +// @ts-expect-error import templateClient from '../legacy/handlebars/compiled/client.js'; -// @ts-ignore +// @ts-expect-error import angularGetHeaders from '../legacy/handlebars/compiled/core/angular/getHeaders.js'; -// @ts-ignore +// @ts-expect-error import angularGetRequestBody from '../legacy/handlebars/compiled/core/angular/getRequestBody.js'; -// @ts-ignore +// @ts-expect-error import angularGetResponseBody from '../legacy/handlebars/compiled/core/angular/getResponseBody.js'; -// @ts-ignore +// @ts-expect-error import angularGetResponseHeader from '../legacy/handlebars/compiled/core/angular/getResponseHeader.js'; -// @ts-ignore +// @ts-expect-error import angularRequest from '../legacy/handlebars/compiled/core/angular/request.js'; -// @ts-ignore +// @ts-expect-error import angularSendRequest from '../legacy/handlebars/compiled/core/angular/sendRequest.js'; -// @ts-ignore +// @ts-expect-error import templateCoreApiError from '../legacy/handlebars/compiled/core/ApiError.js'; -// @ts-ignore +// @ts-expect-error import templateCoreApiRequestOptions from '../legacy/handlebars/compiled/core/ApiRequestOptions.js'; -// @ts-ignore +// @ts-expect-error import templateCoreApiResult from '../legacy/handlebars/compiled/core/ApiResult.js'; -// @ts-ignore +// @ts-expect-error import axiosGetHeaders from '../legacy/handlebars/compiled/core/axios/getHeaders.js'; -// @ts-ignore +// @ts-expect-error import axiosGetRequestBody from '../legacy/handlebars/compiled/core/axios/getRequestBody.js'; -// @ts-ignore +// @ts-expect-error import axiosGetResponseBody from '../legacy/handlebars/compiled/core/axios/getResponseBody.js'; -// @ts-ignore +// @ts-expect-error import axiosGetResponseHeader from '../legacy/handlebars/compiled/core/axios/getResponseHeader.js'; -// @ts-ignore +// @ts-expect-error import axiosRequest from '../legacy/handlebars/compiled/core/axios/request.js'; -// @ts-ignore +// @ts-expect-error import axiosSendRequest from '../legacy/handlebars/compiled/core/axios/sendRequest.js'; -// @ts-ignore +// @ts-expect-error import templateCoreBaseHttpRequest from '../legacy/handlebars/compiled/core/BaseHttpRequest.js'; -// @ts-ignore +// @ts-expect-error import templateCancelablePromise from '../legacy/handlebars/compiled/core/CancelablePromise.js'; -// @ts-ignore +// @ts-expect-error import fetchGetHeaders from '../legacy/handlebars/compiled/core/fetch/getHeaders.js'; -// @ts-ignore +// @ts-expect-error import fetchGetRequestBody from '../legacy/handlebars/compiled/core/fetch/getRequestBody.js'; -// @ts-ignore +// @ts-expect-error import fetchGetResponseBody from '../legacy/handlebars/compiled/core/fetch/getResponseBody.js'; -// @ts-ignore +// @ts-expect-error import fetchGetResponseHeader from '../legacy/handlebars/compiled/core/fetch/getResponseHeader.js'; -// @ts-ignore +// @ts-expect-error import fetchRequest from '../legacy/handlebars/compiled/core/fetch/request.js'; -// @ts-ignore +// @ts-expect-error import fetchSendRequest from '../legacy/handlebars/compiled/core/fetch/sendRequest.js'; -// @ts-ignore +// @ts-expect-error import functionBase64 from '../legacy/handlebars/compiled/core/functions/base64.js'; -// @ts-ignore +// @ts-expect-error import functionCatchErrorCodes from '../legacy/handlebars/compiled/core/functions/catchErrorCodes.js'; -// @ts-ignore +// @ts-expect-error import functionGetFormData from '../legacy/handlebars/compiled/core/functions/getFormData.js'; -// @ts-ignore +// @ts-expect-error import functionGetQueryString from '../legacy/handlebars/compiled/core/functions/getQueryString.js'; -// @ts-ignore +// @ts-expect-error import functionGetUrl from '../legacy/handlebars/compiled/core/functions/getUrl.js'; -// @ts-ignore +// @ts-expect-error import functionIsBlob from '../legacy/handlebars/compiled/core/functions/isBlob.js'; -// @ts-ignore +// @ts-expect-error import functionIsFormData from '../legacy/handlebars/compiled/core/functions/isFormData.js'; -// @ts-ignore +// @ts-expect-error import functionIsString from '../legacy/handlebars/compiled/core/functions/isString.js'; -// @ts-ignore +// @ts-expect-error import functionIsStringWithValue from '../legacy/handlebars/compiled/core/functions/isStringWithValue.js'; -// @ts-ignore +// @ts-expect-error import functionIsSuccess from '../legacy/handlebars/compiled/core/functions/isSuccess.js'; -// @ts-ignore +// @ts-expect-error import functionResolve from '../legacy/handlebars/compiled/core/functions/resolve.js'; -// @ts-ignore +// @ts-expect-error import templateCoreHttpRequest from '../legacy/handlebars/compiled/core/HttpRequest.js'; -// @ts-ignore +// @ts-expect-error import templateCoreSettings from '../legacy/handlebars/compiled/core/OpenAPI.js'; -// @ts-ignore +// @ts-expect-error import templateCoreRequest from '../legacy/handlebars/compiled/core/request.js'; -// @ts-ignore +// @ts-expect-error import xhrGetHeaders from '../legacy/handlebars/compiled/core/xhr/getHeaders.js'; -// @ts-ignore +// @ts-expect-error import xhrGetRequestBody from '../legacy/handlebars/compiled/core/xhr/getRequestBody.js'; -// @ts-ignore +// @ts-expect-error import xhrGetResponseBody from '../legacy/handlebars/compiled/core/xhr/getResponseBody.js'; -// @ts-ignore +// @ts-expect-error import xhrGetResponseHeader from '../legacy/handlebars/compiled/core/xhr/getResponseHeader.js'; -// @ts-ignore +// @ts-expect-error import xhrRequest from '../legacy/handlebars/compiled/core/xhr/request.js'; -// @ts-ignore +// @ts-expect-error import xhrSendRequest from '../legacy/handlebars/compiled/core/xhr/sendRequest.js'; import { getConfig } from './config'; import { stringCase } from './stringCase'; diff --git a/packages/openapi-ts/src/utils/stringCase.ts b/packages/openapi-ts/src/utils/stringCase.ts index eeff2b2b6..d88d0d262 100644 --- a/packages/openapi-ts/src/utils/stringCase.ts +++ b/packages/openapi-ts/src/utils/stringCase.ts @@ -5,13 +5,13 @@ const lowercaseRegExp = /[\p{Ll}]/u; const identifierRegExp = /([\p{Alpha}\p{N}_]|$)/u; const separatorsRegExp = /[_.\- `\\[\]{}\\/]+/; -const leadingSeparatorsRegExp = new RegExp('^' + separatorsRegExp.source); +const leadingSeparatorsRegExp = new RegExp(`^${separatorsRegExp.source}`); const separatorsAndIdentifierRegExp = new RegExp( - separatorsRegExp.source + identifierRegExp.source, + `${separatorsRegExp.source}${identifierRegExp.source}`, 'gu', ); const numbersAndIdentifierRegExp = new RegExp( - '\\d+' + identifierRegExp.source, + `\\d+${identifierRegExp.source}`, 'gu', ); @@ -92,9 +92,15 @@ const preserveCase = ({ export const stringCase = ({ case: _case, + stripLeadingSeparators = true, value, }: { readonly case: StringCase | undefined; + /** + * If leading separators have a semantic meaning, we might not want to + * remove them. + */ + stripLeadingSeparators?: boolean; value: string; }): string => { let result = value.trim(); @@ -124,7 +130,10 @@ export const stringCase = ({ result = preserveCase({ case: _case, string: result }); } - result = result.replace(leadingSeparatorsRegExp, ''); + if (stripLeadingSeparators || result[0] !== value[0]) { + result = result.replace(leadingSeparatorsRegExp, ''); + } + result = _case === 'SCREAMING_SNAKE_CASE' ? result.toLocaleUpperCase() @@ -137,7 +146,12 @@ export const stringCase = ({ if (_case === 'snake_case' || _case === 'SCREAMING_SNAKE_CASE') { result = result.replaceAll( separatorsAndIdentifierRegExp, - (_, identifier) => `_${identifier}`, + (match, identifier, offset) => { + if (offset === 0 && !stripLeadingSeparators) { + return match; + } + return `_${identifier}`; + }, ); if (result[result.length - 1] === '_') { @@ -159,8 +173,19 @@ export const stringCase = ({ }, ); - result = result.replaceAll(separatorsAndIdentifierRegExp, (_, identifier) => - identifier.toLocaleUpperCase(), + result = result.replaceAll( + separatorsAndIdentifierRegExp, + (match, identifier, offset) => { + if ( + offset === 0 && + !stripLeadingSeparators && + match[0] && + value.startsWith(match[0]) + ) { + return match; + } + return identifier.toLocaleUpperCase(); + }, ); } diff --git a/packages/openapi-ts/test/2.0.test.ts b/packages/openapi-ts/test/2.0.test.ts deleted file mode 100644 index 09f5351db..000000000 --- a/packages/openapi-ts/test/2.0.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -import { describe, expect, it } from 'vitest'; - -import { createClient } from '../'; -import type { UserConfig } from '../src/types/config'; -import { getFilePaths } from './utils'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const VERSION = '2.0'; - -const outputDir = path.join(__dirname, 'generated', VERSION); - -describe(`OpenAPI ${VERSION}`, () => { - const createConfig = (userConfig: UserConfig): UserConfig => ({ - client: '@hey-api/client-fetch', - plugins: ['@hey-api/sdk', '@hey-api/typescript'], - ...userConfig, - input: path.join( - __dirname, - 'spec', - VERSION, - typeof userConfig.input === 'string' ? userConfig.input : '', - ), - output: path.join( - outputDir, - typeof userConfig.output === 'string' ? userConfig.output : '', - ), - }); - - const scenarios = [ - { - config: createConfig({ - input: 'form-data.json', - output: 'form-data', - }), - description: 'handles form data', - }, - ]; - - it.each(scenarios)('$description', async ({ config }) => { - await createClient(config); - - const outputPath = typeof config.output === 'string' ? config.output : ''; - const filePaths = getFilePaths(outputPath); - - filePaths.forEach((filePath) => { - const fileContent = fs.readFileSync(filePath, 'utf-8'); - expect(fileContent).toMatchFileSnapshot( - path.join( - __dirname, - '__snapshots__', - VERSION, - filePath.slice(outputDir.length + 1), - ), - ); - }); - }); -}); diff --git a/packages/openapi-ts/test/2.0.x.test.ts b/packages/openapi-ts/test/2.0.x.test.ts new file mode 100644 index 000000000..52d816ef7 --- /dev/null +++ b/packages/openapi-ts/test/2.0.x.test.ts @@ -0,0 +1,230 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +import { createClient } from '../'; +import type { UserConfig } from '../src/types/config'; +import { getFilePaths } from './utils'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const VERSION = '2.0.x'; + +const outputDir = path.join(__dirname, 'generated', VERSION); + +describe(`OpenAPI ${VERSION}`, () => { + const createConfig = (userConfig: UserConfig): UserConfig => ({ + client: '@hey-api/client-fetch', + experimentalParser: true, + plugins: ['@hey-api/typescript'], + ...userConfig, + input: path.join( + __dirname, + 'spec', + VERSION, + typeof userConfig.input === 'string' ? userConfig.input : '', + ), + output: path.join( + outputDir, + typeof userConfig.output === 'string' ? userConfig.output : '', + ), + }); + + const scenarios = [ + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values', + }), + description: 'handles various enum names and values', + }, + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values-javascript-SCREAMING_SNAKE_CASE', + plugins: [ + { + enums: 'javascript', + enumsCase: 'SCREAMING_SNAKE_CASE', + name: '@hey-api/typescript', + }, + ], + }), + description: + 'handles various enum names and values (JavaScript, SCREAMING_SNAKE_CASE)', + }, + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values-javascript-PascalCase', + plugins: [ + { + enums: 'javascript', + enumsCase: 'PascalCase', + name: '@hey-api/typescript', + }, + ], + }), + description: + 'handles various enum names and values (JavaScript, PascalCase)', + }, + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values-javascript-camelCase', + plugins: [ + { + enums: 'javascript', + enumsCase: 'camelCase', + name: '@hey-api/typescript', + }, + ], + }), + description: + 'handles various enum names and values (JavaScript, camelCase)', + }, + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values-javascript-snake_case', + plugins: [ + { + enums: 'javascript', + enumsCase: 'snake_case', + name: '@hey-api/typescript', + }, + ], + }), + description: + 'handles various enum names and values (JavaScript, snake_case)', + }, + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values-javascript-preserve', + plugins: [ + { + enums: 'javascript', + enumsCase: 'preserve', + name: '@hey-api/typescript', + }, + ], + }), + description: + 'handles various enum names and values (JavaScript, preserve)', + }, + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values-typescript-SCREAMING_SNAKE_CASE', + plugins: [ + { + enums: 'typescript', + enumsCase: 'SCREAMING_SNAKE_CASE', + name: '@hey-api/typescript', + }, + ], + }), + description: + 'handles various enum names and values (TypeScript, SCREAMING_SNAKE_CASE)', + }, + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values-typescript-PascalCase', + plugins: [ + { + enums: 'typescript', + enumsCase: 'PascalCase', + name: '@hey-api/typescript', + }, + ], + }), + description: + 'handles various enum names and values (TypeScript, PascalCase)', + }, + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values-typescript-camelCase', + plugins: [ + { + enums: 'typescript', + enumsCase: 'camelCase', + name: '@hey-api/typescript', + }, + ], + }), + description: + 'handles various enum names and values (TypeScript, camelCase)', + }, + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values-typescript-snake_case', + plugins: [ + { + enums: 'typescript', + enumsCase: 'snake_case', + name: '@hey-api/typescript', + }, + ], + }), + description: + 'handles various enum names and values (TypeScript, snake_case)', + }, + { + config: createConfig({ + input: 'enum-names-values.json', + output: 'enum-names-values-typescript-preserve', + plugins: [ + { + enums: 'typescript', + enumsCase: 'preserve', + name: '@hey-api/typescript', + }, + ], + }), + description: + 'handles various enum names and values (TypeScript, preserve)', + }, + { + config: createConfig({ + input: 'form-data.json', + output: 'form-data', + plugins: ['@hey-api/sdk'], + }), + description: 'handles form data', + }, + { + config: createConfig({ + input: 'schema-unknown.yaml', + output: 'schema-unknown', + plugins: ['@hey-api/sdk'], + }), + description: 'generates correct schemas instead of unknown', + }, + ]; + + it.each(scenarios)('$description', async ({ config }) => { + await createClient(config); + + const outputPath = typeof config.output === 'string' ? config.output : ''; + const filePaths = getFilePaths(outputPath); + + filePaths.forEach((filePath) => { + const fileContent = fs.readFileSync(filePath, 'utf-8'); + expect(fileContent).toMatchFileSnapshot( + path.join( + __dirname, + '__snapshots__', + VERSION, + filePath.slice(outputDir.length + 1), + ), + ); + }); + }); +}); diff --git a/packages/openapi-ts/test/__snapshots__/2.0/form-data/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-PascalCase/index.ts similarity index 75% rename from packages/openapi-ts/test/__snapshots__/2.0/form-data/index.ts rename to packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-PascalCase/index.ts index 81abc8221..56bade120 100644 --- a/packages/openapi-ts/test/__snapshots__/2.0/form-data/index.ts +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-PascalCase/index.ts @@ -1,3 +1,2 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './sdk.gen'; export * from './types.gen'; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-PascalCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-PascalCase/types.gen.ts new file mode 100644 index 000000000..f8a255bf1 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-PascalCase/types.gen.ts @@ -0,0 +1,43 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type _110 = '1-10' | '11-20'; + +export const _110 = { + 110: '1-10', + 1120: '11-20' +} as const; + +export type MyFoo = 'myFoo' | 'myBar'; + +export const MyFoo = { + MyFoo: 'myFoo', + MyBar: 'myBar' +} as const; + +export type MyFoo2 = 'MyFoo' | 'MyBar'; + +export const MyFoo2 = { + MyFoo: 'MyFoo', + MyBar: 'MyBar' +} as const; + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export const Foo = { + Foo: 'foo', + Bar: 'bar', + '': '', + True: true, + False: false +} as const; + +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; + +export const Numbers = { + 100: 100, + 200: 200, + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 +} as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/index.ts new file mode 100644 index 000000000..56bade120 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/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__/2.0.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts new file mode 100644 index 000000000..167ba3e94 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts @@ -0,0 +1,43 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type _110 = '1-10' | '11-20'; + +export const _110 = { + '1_10': '1-10', + '11_20': '11-20' +} as const; + +export type MyFoo = 'myFoo' | 'myBar'; + +export const MyFoo = { + MY_FOO: 'myFoo', + MY_BAR: 'myBar' +} as const; + +export type MyFoo2 = 'MyFoo' | 'MyBar'; + +export const MyFoo2 = { + MY_FOO: 'MyFoo', + MY_BAR: 'MyBar' +} as const; + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export const Foo = { + FOO: 'foo', + BAR: 'bar', + '': '', + TRUE: true, + FALSE: false +} as const; + +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; + +export const Numbers = { + 100: 100, + 200: 200, + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 +} as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-camelCase/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-camelCase/index.ts new file mode 100644 index 000000000..56bade120 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-camelCase/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__/2.0.x/enum-names-values-javascript-camelCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-camelCase/types.gen.ts new file mode 100644 index 000000000..1621e9948 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-camelCase/types.gen.ts @@ -0,0 +1,43 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type _110 = '1-10' | '11-20'; + +export const _110 = { + 110: '1-10', + 1120: '11-20' +} as const; + +export type MyFoo = 'myFoo' | 'myBar'; + +export const MyFoo = { + myFoo: 'myFoo', + myBar: 'myBar' +} as const; + +export type MyFoo2 = 'MyFoo' | 'MyBar'; + +export const MyFoo2 = { + myFoo: 'MyFoo', + myBar: 'MyBar' +} as const; + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export const Foo = { + foo: 'foo', + bar: 'bar', + '': '', + true: true, + false: false +} as const; + +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; + +export const Numbers = { + 100: 100, + 200: 200, + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 +} as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-preserve/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-preserve/index.ts new file mode 100644 index 000000000..56bade120 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-preserve/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__/2.0.x/enum-names-values-javascript-preserve/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-preserve/types.gen.ts new file mode 100644 index 000000000..1e8c1efc3 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-preserve/types.gen.ts @@ -0,0 +1,43 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type _110 = '1-10' | '11-20'; + +export const _110 = { + '1-10': '1-10', + '11-20': '11-20' +} as const; + +export type MyFoo = 'myFoo' | 'myBar'; + +export const MyFoo = { + myFoo: 'myFoo', + myBar: 'myBar' +} as const; + +export type MyFoo2 = 'MyFoo' | 'MyBar'; + +export const MyFoo2 = { + MyFoo: 'MyFoo', + MyBar: 'MyBar' +} as const; + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export const Foo = { + foo: 'foo', + bar: 'bar', + '': '', + true: true, + false: false +} as const; + +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; + +export const Numbers = { + 100: 100, + 200: 200, + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 +} as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-snake_case/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-snake_case/index.ts new file mode 100644 index 000000000..56bade120 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-snake_case/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__/2.0.x/enum-names-values-javascript-snake_case/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-snake_case/types.gen.ts new file mode 100644 index 000000000..2d7d3f985 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-javascript-snake_case/types.gen.ts @@ -0,0 +1,43 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type _110 = '1-10' | '11-20'; + +export const _110 = { + '1_10': '1-10', + '11_20': '11-20' +} as const; + +export type MyFoo = 'myFoo' | 'myBar'; + +export const MyFoo = { + my_foo: 'myFoo', + my_bar: 'myBar' +} as const; + +export type MyFoo2 = 'MyFoo' | 'MyBar'; + +export const MyFoo2 = { + my_foo: 'MyFoo', + my_bar: 'MyBar' +} as const; + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export const Foo = { + foo: 'foo', + bar: 'bar', + '': '', + true: true, + false: false +} as const; + +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; + +export const Numbers = { + 100: 100, + 200: 200, + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 +} as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-PascalCase/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-PascalCase/index.ts new file mode 100644 index 000000000..56bade120 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-PascalCase/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__/2.0.x/enum-names-values-typescript-PascalCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-PascalCase/types.gen.ts new file mode 100644 index 000000000..bd3ac9a78 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-PascalCase/types.gen.ts @@ -0,0 +1,27 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export enum _110 { + _110 = '1-10', + _1120 = '11-20' +} + +export enum MyFoo { + MyFoo = 'myFoo', + MyBar = 'myBar' +} + +export enum MyFoo2 { + MyFoo = 'MyFoo', + MyBar = 'MyBar' +} + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export enum Numbers { + _100 = 100, + _200 = 200, + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 +} \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/index.ts new file mode 100644 index 000000000..56bade120 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/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__/2.0.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts new file mode 100644 index 000000000..df0e97a32 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts @@ -0,0 +1,27 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export enum _110 { + '1_10' = '1-10', + '11_20' = '11-20' +} + +export enum MyFoo { + MY_FOO = 'myFoo', + MY_BAR = 'myBar' +} + +export enum MyFoo2 { + MY_FOO = 'MyFoo', + MY_BAR = 'MyBar' +} + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export enum Numbers { + _100 = 100, + _200 = 200, + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 +} \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-camelCase/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-camelCase/index.ts new file mode 100644 index 000000000..56bade120 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-camelCase/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__/2.0.x/enum-names-values-typescript-camelCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-camelCase/types.gen.ts new file mode 100644 index 000000000..7381d05c6 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-camelCase/types.gen.ts @@ -0,0 +1,27 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export enum _110 { + _110 = '1-10', + _1120 = '11-20' +} + +export enum MyFoo { + myFoo = 'myFoo', + myBar = 'myBar' +} + +export enum MyFoo2 { + myFoo = 'MyFoo', + myBar = 'MyBar' +} + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export enum Numbers { + _100 = 100, + _200 = 200, + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 +} \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-preserve/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-preserve/index.ts new file mode 100644 index 000000000..56bade120 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-preserve/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__/2.0.x/enum-names-values-typescript-preserve/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-preserve/types.gen.ts new file mode 100644 index 000000000..812642cb3 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-preserve/types.gen.ts @@ -0,0 +1,27 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export enum _110 { + '1-10' = '1-10', + '11-20' = '11-20' +} + +export enum MyFoo { + myFoo = 'myFoo', + myBar = 'myBar' +} + +export enum MyFoo2 { + MyFoo = 'MyFoo', + MyBar = 'MyBar' +} + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export enum Numbers { + _100 = 100, + _200 = 200, + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 +} \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-snake_case/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-snake_case/index.ts new file mode 100644 index 000000000..56bade120 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-snake_case/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__/2.0.x/enum-names-values-typescript-snake_case/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-snake_case/types.gen.ts new file mode 100644 index 000000000..fb952702d --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values-typescript-snake_case/types.gen.ts @@ -0,0 +1,27 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export enum _110 { + '1_10' = '1-10', + '11_20' = '11-20' +} + +export enum MyFoo { + my_foo = 'myFoo', + my_bar = 'myBar' +} + +export enum MyFoo2 { + my_foo = 'MyFoo', + my_bar = 'MyBar' +} + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export enum Numbers { + _100 = 100, + _200 = 200, + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 +} \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values/index.ts new file mode 100644 index 000000000..56bade120 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values/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__/2.0.x/enum-names-values/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values/types.gen.ts new file mode 100644 index 000000000..3e23a58c3 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/enum-names-values/types.gen.ts @@ -0,0 +1,11 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type _110 = '1-10' | '11-20'; + +export type MyFoo = 'myFoo' | 'myBar'; + +export type MyFoo2 = 'MyFoo' | 'MyBar'; + +export type Foo = 'foo' | 'bar' | '' | true | false; + +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/form-data/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/form-data/index.ts new file mode 100644 index 000000000..e64537d21 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/form-data/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/form-data/sdk.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/form-data/sdk.gen.ts new file mode 100644 index 000000000..0f9d50a6f --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/form-data/sdk.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig, type Options, formDataBodySerializer } from '@hey-api/client-fetch'; +import type { PostV1FooData, PostV1FooResponse } from './types.gen'; + +export const client = createClient(createConfig()); + +export const postV1Foo = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + ...formDataBodySerializer, + headers: { + 'Content-Type': null, + ...options?.headers + }, + url: '/v1/foo' + }); +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/form-data/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/form-data/types.gen.ts new file mode 100644 index 000000000..3be8ebe0d --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/form-data/types.gen.ts @@ -0,0 +1,24 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Foo = { + status?: number; +}; + +export type PostV1FooData = { + body: { + file: Blob | File; + info: string; + }; + path?: never; + query?: never; + url: '/v1/foo'; +}; + +export type PostV1FooResponses = { + /** + * OK + */ + 200: Foo; +}; + +export type PostV1FooResponse = PostV1FooResponses[keyof PostV1FooResponses]; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/schema-unknown/index.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/schema-unknown/index.ts new file mode 100644 index 000000000..e64537d21 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/schema-unknown/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/schema-unknown/sdk.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/schema-unknown/sdk.gen.ts new file mode 100644 index 000000000..2c5ed556f --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/schema-unknown/sdk.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; +import type { SendEmailData, SendEmailError, SendEmailResponse2 } from './types.gen'; + +export const client = createClient(createConfig()); + +/** + * Send a single email + */ +export const sendEmail = (options: Options) => { + return (options?.client ?? client).post({ + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + }, + url: '/email' + }); +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0.x/schema-unknown/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0.x/schema-unknown/types.gen.ts new file mode 100644 index 000000000..36effcb37 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/2.0.x/schema-unknown/types.gen.ts @@ -0,0 +1,132 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type SendEmailRequest = { + /** + * The sender email address. Must have a registered and confirmed Sender Signature. + */ + From?: string; + /** + * Recipient email address. Multiple addresses are comma seperated. Max 50. + */ + To?: string; + /** + * Recipient email address. Multiple addresses are comma seperated. Max 50. + */ + Cc?: string; + /** + * Bcc recipient email address. Multiple addresses are comma seperated. Max 50. + */ + Bcc?: string; + /** + * Email Subject + */ + Subject?: string; + /** + * Email tag that allows you to categorize outgoing emails and get detailed statistics. + */ + Tag?: string; + /** + * If no TextBody specified HTML email message + */ + HtmlBody?: string; + /** + * If no HtmlBody specified Plain text email message + */ + TextBody?: string; + /** + * Reply To override email address. Defaults to the Reply To set in the sender signature. + */ + ReplyTo?: string; + /** + * Activate open tracking for this email. + */ + TrackOpens?: boolean; + /** + * Replace links in content to enable "click tracking" stats. Default is 'null', which uses the server's LinkTracking setting'. + */ + TrackLinks?: 'None' | 'HtmlAndText' | 'HtmlOnly' | 'TextOnly'; + Headers?: HeaderCollection; + Attachments?: AttachmentCollection; +}; + +/** + * A single header for an email message. + */ +export type MessageHeader = { + /** + * The header's name. + */ + Name?: string; + /** + * The header's value. + */ + Value?: string; +}; + +export type HeaderCollection = Array; + +/** + * An attachment for an email message. + */ +export type Attachment = { + Name?: string; + Content?: string; + ContentType?: string; + ContentID?: string; +}; + +export type AttachmentCollection = Array; + +/** + * The standard response when a postmark message is sent + */ +export type SendEmailResponse = { + To?: string; + SubmittedAt?: string; + MessageID?: string; + ErrorCode?: number; + Message?: string; +}; + +/** + * A Postmark API error. + */ +export type StandardPostmarkResponse = { + ErrorCode?: number; + Message?: string; +}; + +export type SendEmailData = { + body?: SendEmailRequest; + headers: { + /** + * The token associated with the Server on which this request will operate. + */ + 'X-Postmark-Server-Token': string; + }; + path?: never; + query?: never; + url: '/email'; +}; + +export type SendEmailErrors = { + /** + * An error was generated due to incorrect use of the API. See the Message associated with this response for more information. + */ + 422: StandardPostmarkResponse; + /** + * Indicates an internal server error occurred. + */ + 500: unknown; +}; + +export type SendEmailError = SendEmailErrors[keyof SendEmailErrors]; + +export type SendEmailResponses = { + /** + * OK + */ + 200: SendEmailResponse; +}; + +export type SendEmailResponse2 = SendEmailResponses[keyof SendEmailResponses]; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0/form-data/sdk.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0/form-data/sdk.gen.ts deleted file mode 100644 index bcf57a1d3..000000000 --- a/packages/openapi-ts/test/__snapshots__/2.0/form-data/sdk.gen.ts +++ /dev/null @@ -1,18 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createClient, createConfig, type OptionsLegacyParser, formDataBodySerializer } from '@hey-api/client-fetch'; -import type { PostV1FooData, PostV1FooError, PostV1FooResponse } from './types.gen'; - -export const client = createClient(createConfig()); - -export const postV1Foo = (options: OptionsLegacyParser) => { - return (options?.client ?? client).post({ - ...options, - ...formDataBodySerializer, - headers: { - 'Content-Type': null, - ...options?.headers - }, - url: '/v1/foo' - }); -}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/2.0/form-data/types.gen.ts b/packages/openapi-ts/test/__snapshots__/2.0/form-data/types.gen.ts deleted file mode 100644 index bfd9b326c..000000000 --- a/packages/openapi-ts/test/__snapshots__/2.0/form-data/types.gen.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type Foo = { - status?: number; -}; - -export type PostV1FooData = { - body: { - file: (Blob | File); - info: string; - }; -}; - -export type PostV1FooResponse = (Foo); - -export type PostV1FooError = unknown; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-PascalCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-PascalCase/types.gen.ts index be4c8a2e1..c1d701b49 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-PascalCase/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-PascalCase/types.gen.ts @@ -32,10 +32,13 @@ export const Foo = { False: false } as const; -export type Numbers = 100 | 200 | 300; +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; export const Numbers = { 100: 100, 200: 200, - 300: 300 + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 } as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts index c21d20ee8..73c552bf1 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts @@ -32,10 +32,13 @@ export const Foo = { FALSE: false } as const; -export type Numbers = 100 | 200 | 300; +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; export const Numbers = { 100: 100, 200: 200, - 300: 300 + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 } as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-camelCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-camelCase/types.gen.ts index 4b2158f2e..ae84dd495 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-camelCase/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-camelCase/types.gen.ts @@ -32,10 +32,13 @@ export const Foo = { false: false } as const; -export type Numbers = 100 | 200 | 300; +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; export const Numbers = { 100: 100, 200: 200, - 300: 300 + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 } as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-preserve/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-preserve/types.gen.ts index c58f04869..97179c3c0 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-preserve/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-preserve/types.gen.ts @@ -32,10 +32,13 @@ export const Foo = { false: false } as const; -export type Numbers = 100 | 200 | 300; +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; export const Numbers = { 100: 100, 200: 200, - 300: 300 + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 } as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-snake_case/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-snake_case/types.gen.ts index d0b9682c8..7efaa9a87 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-snake_case/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-javascript-snake_case/types.gen.ts @@ -32,10 +32,13 @@ export const Foo = { false: false } as const; -export type Numbers = 100 | 200 | 300; +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; export const Numbers = { 100: 100, 200: 200, - 300: 300 + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 } as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-PascalCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-PascalCase/types.gen.ts index 47ad2cae0..d093e3221 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-PascalCase/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-PascalCase/types.gen.ts @@ -20,5 +20,8 @@ export type Foo = 'foo' | 'bar' | null | '' | true | false; export enum Numbers { _100 = 100, _200 = 200, - _300 = 300 + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 } \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts index 0cd5e4250..0d87d08f0 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts @@ -20,5 +20,8 @@ export type Foo = 'foo' | 'bar' | null | '' | true | false; export enum Numbers { _100 = 100, _200 = 200, - _300 = 300 + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 } \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-camelCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-camelCase/types.gen.ts index 5791d1d71..889bb2907 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-camelCase/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-camelCase/types.gen.ts @@ -20,5 +20,8 @@ export type Foo = 'foo' | 'bar' | null | '' | true | false; export enum Numbers { _100 = 100, _200 = 200, - _300 = 300 + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 } \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-preserve/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-preserve/types.gen.ts index a2c0bf8f7..ce17b6234 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-preserve/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-preserve/types.gen.ts @@ -20,5 +20,8 @@ export type Foo = 'foo' | 'bar' | null | '' | true | false; export enum Numbers { _100 = 100, _200 = 200, - _300 = 300 + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 } \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-snake_case/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-snake_case/types.gen.ts index bae32c049..8d67be40b 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-snake_case/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values-typescript-snake_case/types.gen.ts @@ -20,5 +20,8 @@ export type Foo = 'foo' | 'bar' | null | '' | true | false; export enum Numbers { _100 = 100, _200 = 200, - _300 = 300 + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 } \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values/types.gen.ts index a038ae83f..6e90f2a49 100644 --- a/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/enum-names-values/types.gen.ts @@ -8,4 +8,4 @@ export type MyFoo2 = 'MyFoo' | 'MyBar'; export type Foo = 'foo' | 'bar' | null | '' | true | false; -export type Numbers = 100 | 200 | 300; \ No newline at end of file +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-PascalCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-PascalCase/types.gen.ts index be4c8a2e1..c1d701b49 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-PascalCase/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-PascalCase/types.gen.ts @@ -32,10 +32,13 @@ export const Foo = { False: false } as const; -export type Numbers = 100 | 200 | 300; +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; export const Numbers = { 100: 100, 200: 200, - 300: 300 + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 } as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts index c21d20ee8..73c552bf1 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-SCREAMING_SNAKE_CASE/types.gen.ts @@ -32,10 +32,13 @@ export const Foo = { FALSE: false } as const; -export type Numbers = 100 | 200 | 300; +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; export const Numbers = { 100: 100, 200: 200, - 300: 300 + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 } as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-camelCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-camelCase/types.gen.ts index 4b2158f2e..ae84dd495 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-camelCase/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-camelCase/types.gen.ts @@ -32,10 +32,13 @@ export const Foo = { false: false } as const; -export type Numbers = 100 | 200 | 300; +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; export const Numbers = { 100: 100, 200: 200, - 300: 300 + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 } as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-preserve/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-preserve/types.gen.ts index c58f04869..97179c3c0 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-preserve/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-preserve/types.gen.ts @@ -32,10 +32,13 @@ export const Foo = { false: false } as const; -export type Numbers = 100 | 200 | 300; +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; export const Numbers = { 100: 100, 200: 200, - 300: 300 + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 } as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-snake_case/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-snake_case/types.gen.ts index d0b9682c8..7efaa9a87 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-snake_case/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-javascript-snake_case/types.gen.ts @@ -32,10 +32,13 @@ export const Foo = { false: false } as const; -export type Numbers = 100 | 200 | 300; +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; export const Numbers = { 100: 100, 200: 200, - 300: 300 + 300: 300, + '-100': -100, + '-200': -200, + '-300': -300 } as const; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-PascalCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-PascalCase/types.gen.ts index 47ad2cae0..d093e3221 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-PascalCase/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-PascalCase/types.gen.ts @@ -20,5 +20,8 @@ export type Foo = 'foo' | 'bar' | null | '' | true | false; export enum Numbers { _100 = 100, _200 = 200, - _300 = 300 + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 } \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts index 0cd5e4250..0d87d08f0 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-SCREAMING_SNAKE_CASE/types.gen.ts @@ -20,5 +20,8 @@ export type Foo = 'foo' | 'bar' | null | '' | true | false; export enum Numbers { _100 = 100, _200 = 200, - _300 = 300 + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 } \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-camelCase/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-camelCase/types.gen.ts index 5791d1d71..889bb2907 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-camelCase/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-camelCase/types.gen.ts @@ -20,5 +20,8 @@ export type Foo = 'foo' | 'bar' | null | '' | true | false; export enum Numbers { _100 = 100, _200 = 200, - _300 = 300 + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 } \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-preserve/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-preserve/types.gen.ts index a2c0bf8f7..ce17b6234 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-preserve/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-preserve/types.gen.ts @@ -20,5 +20,8 @@ export type Foo = 'foo' | 'bar' | null | '' | true | false; export enum Numbers { _100 = 100, _200 = 200, - _300 = 300 + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 } \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-snake_case/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-snake_case/types.gen.ts index bae32c049..8d67be40b 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-snake_case/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values-typescript-snake_case/types.gen.ts @@ -20,5 +20,8 @@ export type Foo = 'foo' | 'bar' | null | '' | true | false; export enum Numbers { _100 = 100, _200 = 200, - _300 = 300 + _300 = 300, + '-100' = -100, + '-200' = -200, + '-300' = -300 } \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values/types.gen.ts index a038ae83f..6e90f2a49 100644 --- a/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values/types.gen.ts +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/enum-names-values/types.gen.ts @@ -8,4 +8,4 @@ export type MyFoo2 = 'MyFoo' | 'MyBar'; export type Foo = 'foo' | 'bar' | null | '' | true | false; -export type Numbers = 100 | 200 | 300; \ No newline at end of file +export type Numbers = 100 | 200 | 300 | -100 | -200 | -300; \ No newline at end of file diff --git a/packages/openapi-ts/test/openapi-ts.config.ts b/packages/openapi-ts/test/openapi-ts.config.ts index c85276052..e23ec4d9f 100644 --- a/packages/openapi-ts/test/openapi-ts.config.ts +++ b/packages/openapi-ts/test/openapi-ts.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ // exclude: '^#/components/schemas/ModelWithCircularReference$', // include: // '^(#/components/schemas/import|#/paths/api/v{api-version}/simple/options)$', - path: './packages/openapi-ts/test/spec/3.0.x/full.json', + path: './packages/openapi-ts/test/spec/2.0.x/full.json', // path: './test/spec/v3-transforms.json', // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', // path: 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', @@ -36,13 +36,13 @@ export default defineConfig({ }, // @ts-ignore { - asClass: true, + // asClass: true, // auth: false, // include... name: '@hey-api/sdk', // operationId: false, // serviceNameBuilder: '^Parameters', - throwOnError: true, + // throwOnError: true, // transformer: '@hey-api/transformers', transformer: true, // validator: 'zod', @@ -68,11 +68,11 @@ export default defineConfig({ }, // @ts-ignore { - name: '@tanstack/react-query', + // name: '@tanstack/react-query', }, // @ts-ignore { - name: 'zod', + // name: 'zod', }, ], // useOptions: false, diff --git a/packages/openapi-ts/test/spec/2.0.x/enum-names-values.json b/packages/openapi-ts/test/spec/2.0.x/enum-names-values.json new file mode 100644 index 000000000..6605425a2 --- /dev/null +++ b/packages/openapi-ts/test/spec/2.0.x/enum-names-values.json @@ -0,0 +1,29 @@ +{ + "swagger": "2.0", + "info": { + "title": "OpenAPI 2.0 enum names values example", + "version": "1" + }, + "definitions": { + "1-10": { + "enum": ["1-10", "11-20"], + "type": "string" + }, + "myFoo": { + "enum": ["myFoo", "myBar"], + "type": "string" + }, + "MyFoo": { + "enum": ["MyFoo", "MyBar"], + "type": "string" + }, + "Foo": { + "enum": ["foo", "bar", "", true, false], + "type": "string" + }, + "Numbers": { + "enum": [100, 200, 300, -100, -200, -300], + "type": "number" + } + } +} diff --git a/packages/openapi-ts/test/spec/2.0/form-data.json b/packages/openapi-ts/test/spec/2.0.x/form-data.json similarity index 81% rename from packages/openapi-ts/test/spec/2.0/form-data.json rename to packages/openapi-ts/test/spec/2.0.x/form-data.json index 639e215bf..8770b7ad3 100644 --- a/packages/openapi-ts/test/spec/2.0/form-data.json +++ b/packages/openapi-ts/test/spec/2.0.x/form-data.json @@ -1,14 +1,9 @@ { - "basePath": "/", - "host": "foo.bar.com", + "swagger": "2.0", "info": { - "description": "", - "title": "", - "version": "v1" + "title": "OpenAPI 2.0 form data example", + "version": "1" }, - "schemes": ["https"], - "swagger": "2.0", - "tags": [], "paths": { "/v1/foo": { "post": { @@ -29,7 +24,7 @@ ], "responses": { "200": { - "description": "", + "description": "OK", "schema": { "$ref": "#/definitions/Foo" } diff --git a/packages/openapi-ts/test/spec/2.0.x/full.json b/packages/openapi-ts/test/spec/2.0.x/full.json new file mode 100644 index 000000000..3990d8226 --- /dev/null +++ b/packages/openapi-ts/test/spec/2.0.x/full.json @@ -0,0 +1,1532 @@ +{ + "swagger": "2.0", + "info": { + "title": "swagger", + "version": "v1.0" + }, + "host": "localhost:3000", + "basePath": "/base", + "schemes": ["http"], + "paths": { + "/api/v{api-version}/no-tag": { + "tags": [], + "get": { + "operationId": "ServiceWithEmptyTag" + } + }, + "/api/v{api-version}/simple": { + "get": { + "tags": ["Simple"], + "operationId": "GetCallWithoutParametersAndResponse" + }, + "put": { + "tags": ["Simple"], + "operationId": "PutCallWithoutParametersAndResponse" + }, + "post": { + "tags": ["Simple"], + "operationId": "PostCallWithoutParametersAndResponse" + }, + "delete": { + "tags": ["Simple"], + "operationId": "DeleteCallWithoutParametersAndResponse" + }, + "options": { + "tags": ["Simple"], + "operationId": "OptionsCallWithoutParametersAndResponse" + }, + "head": { + "tags": ["Simple"], + "operationId": "HeadCallWithoutParametersAndResponse" + }, + "patch": { + "tags": ["Simple"], + "operationId": "PatchCallWithoutParametersAndResponse" + } + }, + "/api/v{api-version}/descriptions/": { + "post": { + "tags": ["Descriptions"], + "operationId": "CallWithDescriptions", + "parameters": [ + { + "description": "Testing multiline comments in string: First line\nSecond line\n\nFourth line", + "name": "parameterWithBreaks", + "in": "query", + "type": "string" + }, + { + "description": "Testing backticks in string: `backticks` and ```multiple backticks``` should work", + "name": "parameterWithBackticks", + "in": "query", + "type": "string" + }, + { + "description": "Testing slashes in string: \\backwards\\\\\\ and /forwards/// should work", + "name": "parameterWithSlashes", + "in": "query", + "type": "string" + }, + { + "description": "Testing expression placeholders in string: ${expression} should work", + "name": "parameterWithExpressionPlaceholders", + "in": "query", + "type": "string" + }, + { + "description": "Testing quotes in string: 'single quote''' and \"double quotes\"\"\" should work", + "name": "parameterWithQuotes", + "in": "query", + "type": "string" + }, + { + "description": "Testing reserved characters in string: /* inline */ and /** inline **/ should work", + "name": "parameterWithReservedCharacters", + "in": "query", + "type": "string" + } + ] + } + }, + "/api/v{api-version}/parameters/{parameterPath}": { + "post": { + "tags": ["Parameters"], + "operationId": "CallWithParameters", + "parameters": [ + { + "description": "This is the parameter that goes into the header", + "name": "parameterHeader", + "in": "header", + "type": "string", + "required": true + }, + { + "description": "This is the parameter that goes into the query params", + "name": "parameterQuery", + "in": "query", + "type": "string", + "required": true + }, + { + "description": "This is the parameter that goes into the form data", + "name": "parameterForm", + "in": "formData", + "type": "string", + "required": true + }, + { + "description": "This is the parameter that is sent as request body", + "name": "parameterBody", + "in": "body", + "type": "string", + "required": true + }, + { + "description": "This is the parameter that goes into the path", + "name": "parameterPath", + "in": "path", + "type": "string", + "required": true + }, + { + "description": "api-version should be required in standalone clients", + "name": "api-version", + "in": "path", + "type": "string", + "required": true + } + ] + } + }, + "/api/v{api-version}/parameters/{parameter.path.1}/{parameter-path-2}/{PARAMETER-PATH-3}": { + "post": { + "tags": ["Parameters"], + "operationId": "CallWithWeirdParameterNames", + "parameters": [ + { + "description": "This is the parameter that goes into the path", + "name": "parameter.path.1", + "in": "path", + "type": "string", + "required": false + }, + { + "description": "This is the parameter that goes into the path", + "name": "parameter-path-2", + "in": "path", + "type": "string", + "required": false + }, + { + "description": "This is the parameter that goes into the path", + "name": "PARAMETER-PATH-3", + "in": "path", + "type": "string", + "required": false + }, + { + "description": "This is the parameter with a reserved keyword", + "name": "default", + "in": "query", + "type": "string", + "required": false + }, + { + "description": "This is the parameter that goes into the request header", + "name": "parameter.header", + "in": "header", + "type": "string", + "required": true + }, + { + "description": "This is the parameter that goes into the request query params", + "name": "parameter-query", + "in": "query", + "type": "string", + "required": true + }, + { + "description": "This is the parameter that goes into the request form data", + "name": "parameter_form", + "in": "formData", + "type": "string", + "required": true + }, + { + "description": "This is the parameter that is sent as request body", + "name": "PARAMETER-BODY", + "in": "body", + "type": "string", + "required": true + }, + { + "description": "api-version should be required in standalone clients", + "name": "api-version", + "in": "path", + "type": "string", + "required": true + } + ] + } + }, + "/api/v{api-version}/defaults": { + "get": { + "tags": ["Defaults"], + "operationId": "CallWithDefaultParameters", + "parameters": [ + { + "description": "This is a simple string with default value", + "name": "parameterString", + "in": "query", + "required": true, + "default": "Hello World!", + "type": "string" + }, + { + "description": "This is a simple number with default value", + "name": "parameterNumber", + "in": "query", + "required": true, + "default": 123, + "type": "number" + }, + { + "description": "This is a simple boolean with default value", + "name": "parameterBoolean", + "in": "query", + "required": true, + "default": true, + "type": "boolean" + }, + { + "description": "This is a simple enum with default value", + "name": "parameterEnum", + "in": "query", + "required": true, + "default": 0, + "enum": ["Success", "Warning", "Error"] + }, + { + "description": "This is a simple model with default value", + "name": "parameterModel", + "in": "query", + "required": true, + "default": { + "prop": "Hello World!" + }, + "$ref": "#/definitions/ModelWithString" + } + ] + }, + "post": { + "tags": ["Defaults"], + "operationId": "CallWithDefaultOptionalParameters", + "parameters": [ + { + "description": "This is a simple string that is optional with default value", + "name": "parameterString", + "in": "query", + "default": "Hello World!", + "type": "string" + }, + { + "description": "This is a simple number that is optional with default value", + "name": "parameterNumber", + "in": "query", + "default": 123, + "type": "number" + }, + { + "description": "This is a simple boolean that is optional with default value", + "name": "parameterBoolean", + "in": "query", + "default": true, + "type": "boolean" + }, + { + "description": "This is a simple enum that is optional with default value", + "name": "parameterEnum", + "in": "query", + "default": 0, + "enum": ["Success", "Warning", "Error"] + } + ] + }, + "put": { + "tags": ["Defaults"], + "operationId": "CallToTestOrderOfParams", + "parameters": [ + { + "description": "This is a optional string with default", + "name": "parameterOptionalStringWithDefault", + "in": "query", + "required": false, + "default": "Hello World!", + "type": "string" + }, + { + "description": "This is a optional string with empty default", + "name": "parameterOptionalStringWithEmptyDefault", + "in": "query", + "required": false, + "default": "", + "type": "string" + }, + { + "description": "This is a optional string with no default", + "name": "parameterOptionalStringWithNoDefault", + "in": "query", + "required": false, + "type": "string" + }, + { + "description": "This is a string with default", + "name": "parameterStringWithDefault", + "in": "query", + "required": true, + "default": "Hello World!", + "type": "string" + }, + { + "description": "This is a string with empty default", + "name": "parameterStringWithEmptyDefault", + "in": "query", + "required": true, + "default": "", + "type": "string" + }, + { + "description": "This is a string with no default", + "name": "parameterStringWithNoDefault", + "in": "query", + "required": true, + "type": "string" + }, + { + "x-nullable": true, + "description": "This is a string that can be null with no default", + "name": "parameterStringNullableWithNoDefault", + "in": "query", + "required": false, + "type": "string" + }, + { + "x-nullable": true, + "description": "This is a string that can be null with default", + "name": "parameterStringNullableWithDefault", + "in": "query", + "required": false, + "type": "string", + "default": null + } + ] + } + }, + "/api/v{api-version}/duplicate": { + "delete": { + "tags": ["Duplicate"], + "operationId": "DuplicateName" + }, + "get": { + "tags": ["Duplicate"], + "operationId": "DuplicateName2" + }, + "post": { + "tags": ["Duplicate"], + "operationId": "DuplicateName3" + }, + "put": { + "tags": ["Duplicate"], + "operationId": "DuplicateName4" + } + }, + "/api/v{api-version}/no-content": { + "get": { + "tags": ["NoContent"], + "operationId": "CallWithNoContentResponse", + "responses": { + "204": { + "description": "Success" + } + } + } + }, + "/api/v{api-version}/multiple-tags/response-and-no-content": { + "get": { + "tags": ["Response", "NoContent"], + "operationId": "CallWithResponseAndNoContentResponse", + "produces": ["application/json"], + "responses": { + "200": { + "description": "Response is a simple number", + "schema": { + "type": "number" + } + }, + "204": { + "description": "Success" + } + } + } + }, + "/api/v{api-version}/multiple-tags/a": { + "get": { + "tags": ["MultipleTags1", "MultipleTags2"], + "operationId": "DummyA", + "responses": { + "204": { + "description": "Success" + } + } + } + }, + "/api/v{api-version}/multiple-tags/b": { + "get": { + "tags": ["MultipleTags1", "MultipleTags2", "MultipleTags3"], + "operationId": "DummyB", + "responses": { + "204": { + "description": "Success" + } + } + } + }, + "/api/v{api-version}/response": { + "get": { + "tags": ["Response"], + "operationId": "CallWithResponse", + "responses": { + "default": { + "description": "Message for default response", + "schema": { + "$ref": "#/definitions/ModelWithString" + } + } + } + }, + "post": { + "tags": ["Response"], + "operationId": "CallWithDuplicateResponses", + "responses": { + "default": { + "description": "Message for default response", + "schema": { + "$ref": "#/definitions/ModelWithString" + } + }, + "201": { + "description": "Message for 201 response", + "schema": { + "$ref": "#/definitions/ModelWithString" + } + }, + "202": { + "description": "Message for 202 response", + "schema": { + "$ref": "#/definitions/ModelWithString" + } + }, + "500": { + "description": "Message for 500 error", + "schema": { + "$ref": "#/definitions/ModelWithStringError" + } + }, + "501": { + "description": "Message for 501 error", + "schema": { + "$ref": "#/definitions/ModelWithStringError" + } + }, + "502": { + "description": "Message for 502 error", + "schema": { + "$ref": "#/definitions/ModelWithStringError" + } + } + } + }, + "put": { + "tags": ["Response"], + "operationId": "CallWithResponses", + "responses": { + "default": { + "description": "Message for default response", + "schema": { + "$ref": "#/definitions/ModelWithString" + } + }, + "200": { + "description": "Message for 200 response", + "schema": { + "type": "object", + "properties": { + "@namespace.string": { + "type": "string", + "readOnly": true + }, + "@namespace.integer": { + "type": "integer", + "readOnly": true + }, + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/ModelWithString" + }, + "readOnly": true + } + } + } + }, + "201": { + "description": "Message for 201 response", + "schema": { + "$ref": "#/definitions/ModelThatExtends" + } + }, + "202": { + "description": "Message for 202 response", + "schema": { + "$ref": "#/definitions/ModelThatExtendsExtends" + } + }, + "500": { + "description": "Message for 500 error", + "schema": { + "$ref": "#/definitions/ModelWithStringError" + } + }, + "501": { + "description": "Message for 501 error", + "schema": { + "$ref": "#/definitions/ModelWithStringError" + } + }, + "502": { + "description": "Message for 502 error", + "schema": { + "$ref": "#/definitions/ModelWithStringError" + } + } + } + } + }, + "/api/v{api-version}/collectionFormat": { + "get": { + "tags": ["CollectionFormat"], + "operationId": "CollectionFormat", + "parameters": [ + { + "description": "This is an array parameter that is sent as csv format (comma-separated values)", + "name": "parameterArrayCSV", + "in": "query", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv" + }, + { + "description": "This is an array parameter that is sent as ssv format (space-separated values)", + "name": "parameterArraySSV", + "in": "query", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "ssv" + }, + { + "description": "This is an array parameter that is sent as tsv format (tab-separated values)", + "name": "parameterArrayTSV", + "in": "query", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "tsv" + }, + { + "description": "This is an array parameter that is sent as pipes format (pipe-separated values)", + "name": "parameterArrayPipes", + "in": "query", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "pipes" + }, + { + "description": "This is an array parameter that is sent as multi format (multiple parameter instances)", + "name": "parameterArrayMulti", + "in": "query", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + ] + } + }, + "/api/v{api-version}/types": { + "get": { + "tags": ["Types"], + "operationId": "Types", + "parameters": [ + { + "description": "This is a number parameter", + "name": "parameterNumber", + "in": "query", + "required": true, + "default": 123, + "type": "number" + }, + { + "description": "This is a string parameter", + "name": "parameterString", + "in": "query", + "required": true, + "default": "default", + "type": "string" + }, + { + "description": "This is a boolean parameter", + "name": "parameterBoolean", + "in": "query", + "required": true, + "default": true, + "type": "boolean" + }, + { + "description": "This is an object parameter", + "name": "parameterObject", + "in": "query", + "required": true, + "default": null, + "type": "object" + }, + { + "description": "This is an array parameter", + "name": "parameterArray", + "in": "query", + "required": true, + "type": "array", + "items": { + "type": "string" + } + }, + { + "description": "This is a dictionary parameter", + "name": "parameterDictionary", + "in": "query", + "required": true, + "type": "object", + "items": { + "type": "string" + } + }, + { + "description": "This is an enum parameter", + "name": "parameterEnum", + "in": "query", + "required": true, + "enum": ["Success", "Warning", "Error"] + }, + { + "description": "This is a number parameter", + "name": "id", + "in": "path", + "type": "integer", + "format": "int32" + } + ], + "responses": { + "200": { + "description": "Response is a simple number", + "schema": { + "type": "number" + } + }, + "201": { + "description": "Response is a simple string", + "schema": { + "type": "string" + } + }, + "202": { + "description": "Response is a simple boolean", + "schema": { + "type": "boolean" + } + }, + "203": { + "description": "Response is a simple object", + "default": null, + "schema": { + "type": "object" + } + } + } + } + }, + "/api/v{api-version}/complex": { + "get": { + "tags": ["Complex"], + "operationId": "ComplexTypes", + "parameters": [ + { + "description": "Parameter containing object", + "name": "parameterObject", + "in": "query", + "required": true, + "type": "object", + "properties": { + "first": { + "type": "object", + "properties": { + "second": { + "type": "object", + "properties": { + "third": { + "type": "string" + } + } + } + } + } + } + }, + { + "description": "Parameter containing reference", + "name": "parameterReference", + "in": "query", + "required": true, + "$ref": "#/definitions/ModelWithString" + } + ], + "responses": { + "200": { + "description": "Successful response", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/ModelWithString" + } + } + }, + "400": { + "description": "400 server error" + }, + "500": { + "description": "500 server error" + } + } + } + }, + "/api/v{api-version}/header": { + "post": { + "tags": ["Header"], + "operationId": "CallWithResultFromHeader", + "responses": { + "200": { + "description": "Successful response", + "headers": { + "operation-location": { + "type": "string" + } + } + }, + "400": { + "description": "400 server error" + }, + "500": { + "description": "500 server error" + } + } + } + }, + "/api/v{api-version}/error": { + "post": { + "tags": ["Error"], + "operationId": "testErrorCode", + "parameters": [ + { + "description": "Status code to return", + "name": "status", + "in": "query", + "type": "string", + "required": true + } + ], + "responses": { + "200": { + "description": "Custom message: Successful response" + }, + "500": { + "description": "Custom message: Internal Server Error" + }, + "501": { + "description": "Custom message: Not Implemented" + }, + "502": { + "description": "Custom message: Bad Gateway" + }, + "503": { + "description": "Custom message: Service Unavailable" + } + } + } + }, + "/api/v{api-version}/non-ascii-æøåÆØÅöôêÊ字符串": { + "post": { + "tags": ["Non-Ascii-æøåÆØÅöôêÊ"], + "operationId": "nonAsciiæøåÆØÅöôêÊ字符串", + "parameters": [ + { + "description": "Dummy input param", + "name": "nonAsciiParamæøåÆØÅöôêÊ", + "in": "query", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Successful response", + "schema": { + "$ref": "#/definitions/NonAsciiStringæøåÆØÅöôêÊ字符串" + } + } + } + } + }, + "/api/v{api-version}/body": { + "post": { + "description": "Body should not be unknown", + "consumes": ["application/json"], + "produces": ["application/json"], + "summary": "Body should not be unknown", + "parameters": [ + { + "description": "Body should not be unknown", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/parameter.ActivityParams" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.PostActivityResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/failure.Failure" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/failure.Failure" + } + } + } + } + } + }, + "definitions": { + "CommentWithBreaks": { + "description": "Testing multiline comments in string: First line\nSecond line\n\nFourth line", + "type": "integer" + }, + "CommentWithBackticks": { + "description": "Testing backticks in string: `backticks` and ```multiple backticks``` should work", + "type": "integer" + }, + "CommentWithBackticksAndQuotes": { + "description": "Testing backticks and quotes in string: `backticks`, 'quotes', \"double quotes\" and ```multiple backticks``` should work", + "type": "integer" + }, + "CommentWithSlashes": { + "description": "Testing slashes in string: \\backwards\\\\\\ and /forwards/// should work", + "type": "integer" + }, + "CommentWithExpressionPlaceholders": { + "description": "Testing expression placeholders in string: ${expression} should work", + "type": "integer" + }, + "CommentWithQuotes": { + "description": "Testing quotes in string: 'single quote''' and \"double quotes\"\"\" should work", + "type": "integer" + }, + "CommentWithReservedCharacters": { + "description": "Testing reserved characters in string: /* inline */ and /** inline **/ should work", + "type": "integer" + }, + "SimpleInteger": { + "description": "This is a simple number", + "type": "integer" + }, + "SimpleBoolean": { + "description": "This is a simple boolean", + "type": "boolean" + }, + "SimpleString": { + "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", + "format": "binary", + "type": "string" + }, + "SimpleReference": { + "description": "This is a simple reference", + "$ref": "#/definitions/ModelWithString" + }, + "SimpleStringWithPattern": { + "description": "This is a simple string", + "type": "string", + "maxLength": 64, + "pattern": "^[a-zA-Z0-9_]*$" + }, + "EnumWithStrings": { + "description": "This is a simple enum with strings", + "enum": [ + "Success", + "Warning", + "Error", + "'Single Quote'", + "\"Double Quotes\"", + "Non-ascii: øæåôöØÆÅÔÖ字符串" + ] + }, + "EnumWithNumbers": { + "description": "This is a simple enum with numbers", + "enum": [ + 1, 2, 3, 1.1, 1.2, 1.3, 100, 200, 300, -100, -200, -300, -1.1, -1.2, + -1.3 + ] + }, + "EnumFromDescription": { + "description": "Success=1,Warning=2,Error=3", + "type": "number" + }, + "EnumWithExtensions": { + "description": "This is a simple enum with numbers", + "enum": [200, 400, 500], + "x-enum-varnames": ["CUSTOM_SUCCESS", "CUSTOM_WARNING", "CUSTOM_ERROR"], + "x-enum-descriptions": [ + "Used when the status of something is successful", + "Used when the status of something has a warning", + "Used when the status of something has an error" + ] + }, + "ArrayWithNumbers": { + "description": "This is a simple array with numbers", + "type": "array", + "items": { + "type": "integer" + } + }, + "ArrayWithBooleans": { + "description": "This is a simple array with booleans", + "type": "array", + "items": { + "type": "boolean" + } + }, + "ArrayWithStrings": { + "description": "This is a simple array with strings", + "type": "array", + "items": { + "type": "string" + } + }, + "ArrayWithReferences": { + "description": "This is a simple array with references", + "type": "array", + "items": { + "$ref": "#/definitions/ModelWithString" + } + }, + "ArrayWithArray": { + "description": "This is a simple array containing an array", + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/ModelWithString" + } + } + }, + "ArrayWithProperties": { + "description": "This is a simple array with properties", + "type": "array", + "items": { + "type": "object", + "properties": { + "foo": { + "type": "string" + }, + "bar": { + "type": "string" + } + } + } + }, + "DictionaryWithString": { + "description": "This is a string dictionary", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "DictionaryWithReference": { + "description": "This is a string reference", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ModelWithString" + } + }, + "DictionaryWithArray": { + "description": "This is a complex dictionary", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/ModelWithString" + } + } + }, + "DictionaryWithDictionary": { + "description": "This is a string dictionary", + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "DictionaryWithProperties": { + "description": "This is a complex dictionary", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "foo": { + "type": "string" + }, + "bar": { + "type": "string" + } + } + } + }, + "Date": { + "description": "This is a type-only model that defines Date as a string", + "type": "string" + }, + "ModelWithInteger": { + "description": "This is a model with one number property", + "type": "object", + "properties": { + "prop": { + "description": "This is a simple number property", + "type": "integer" + } + } + }, + "ModelWithBoolean": { + "description": "This is a model with one boolean property", + "type": "object", + "properties": { + "prop": { + "description": "This is a simple boolean property", + "type": "boolean" + } + } + }, + "ModelWithString": { + "description": "This is a model with one string property", + "type": "object", + "properties": { + "prop": { + "description": "This is a simple string property", + "type": "string" + } + } + }, + "ModelWithStringError": { + "description": "This is a model with one string property", + "type": "object", + "properties": { + "prop": { + "description": "This is a simple string property", + "type": "string" + } + } + }, + "ModelWithNullableString": { + "description": "This is a model with one string property", + "type": "object", + "required": ["nullableRequiredProp"], + "properties": { + "nullableProp": { + "description": "This is a simple string property", + "type": "string", + "x-nullable": true + }, + "nullableRequiredProp": { + "description": "This is a simple string property", + "type": "string", + "x-nullable": true + } + } + }, + "ModelWithEnum": { + "description": "This is a model with one enum", + "type": "object", + "properties": { + "test": { + "description": "This is a simple enum with strings", + "enum": ["Success", "Warning", "Error", "ØÆÅ字符串"] + }, + "statusCode": { + "description": "These are the HTTP error code enums", + "enum": [ + "100", + "200 FOO", + "300 FOO_BAR", + "400 foo-bar", + "500 foo.bar", + "600 foo&bar" + ] + }, + "bool": { + "description": "Simple boolean enum", + "type": "boolean", + "enum": [true] + } + } + }, + "ModelWithEnumFromDescription": { + "description": "This is a model with one enum", + "type": "object", + "properties": { + "test": { + "type": "integer", + "description": "Success=1,Warning=2,Error=3" + } + } + }, + "ModelWithNestedEnums": { + "description": "This is a model with nested enums", + "type": "object", + "properties": { + "dictionaryWithEnum": { + "type": "object", + "additionalProperties": { + "enum": ["Success", "Warning", "Error"] + } + }, + "dictionaryWithEnumFromDescription": { + "type": "object", + "additionalProperties": { + "type": "integer", + "description": "Success=1,Warning=2,Error=3" + } + }, + "arrayWithEnum": { + "type": "array", + "items": { + "enum": ["Success", "Warning", "Error"] + } + }, + "arrayWithDescription": { + "type": "array", + "items": { + "type": "integer", + "description": "Success=1,Warning=2,Error=3" + } + } + } + }, + "ModelWithReference": { + "description": "This is a model with one property containing a reference", + "type": "object", + "properties": { + "prop": { + "$ref": "#/definitions/ModelWithProperties" + } + } + }, + "ModelWithArray": { + "description": "This is a model with one property containing an array", + "type": "object", + "properties": { + "prop": { + "type": "array", + "items": { + "$ref": "#/definitions/ModelWithString" + } + }, + "propWithFile": { + "type": "array", + "items": { + "format": "binary", + "type": "string" + } + }, + "propWithNumber": { + "type": "array", + "items": { + "type": "number" + } + } + } + }, + "ModelWithDictionary": { + "description": "This is a model with one property containing a dictionary", + "type": "object", + "properties": { + "prop": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "ModelWithCircularReference": { + "description": "This is a model with one property containing a circular reference", + "type": "object", + "properties": { + "prop": { + "$ref": "#/definitions/ModelWithCircularReference" + } + } + }, + "ModelWithProperties": { + "description": "This is a model with one nested property", + "type": "object", + "required": ["required", "requiredAndReadOnly"], + "properties": { + "required": { + "type": "string" + }, + "requiredAndReadOnly": { + "type": "string", + "readOnly": true + }, + "string": { + "type": "string" + }, + "number": { + "type": "number" + }, + "boolean": { + "type": "boolean" + }, + "reference": { + "$ref": "#/definitions/ModelWithString" + }, + "property with space": { + "type": "string" + }, + "default": { + "type": "string" + }, + "try": { + "type": "string" + }, + "@namespace.string": { + "type": "string", + "readOnly": true + }, + "@namespace.integer": { + "type": "integer", + "readOnly": true + } + } + }, + "ModelWithNestedProperties": { + "description": "This is a model with one nested property", + "type": "object", + "required": ["first"], + "properties": { + "first": { + "type": "object", + "required": ["second"], + "readOnly": true, + "properties": { + "second": { + "type": "object", + "required": ["third"], + "readOnly": true, + "properties": { + "third": { + "type": "string", + "readOnly": true + } + } + } + } + } + } + }, + "ModelWithDuplicateProperties": { + "description": "This is a model with duplicated properties", + "type": "object", + "properties": { + "prop": { + "$ref": "#/definitions/ModelWithString" + }, + "prop": { + "$ref": "#/definitions/ModelWithString" + }, + "prop": { + "$ref": "#/definitions/ModelWithString" + } + } + }, + "ModelWithOrderedProperties": { + "description": "This is a model with ordered properties", + "type": "object", + "properties": { + "zebra": { + "type": "string" + }, + "apple": { + "type": "string" + }, + "hawaii": { + "type": "string" + } + } + }, + "ModelWithDuplicateImports": { + "description": "This is a model with duplicated imports", + "type": "object", + "properties": { + "propA": { + "$ref": "#/definitions/ModelWithString" + }, + "propB": { + "$ref": "#/definitions/ModelWithString" + }, + "propC": { + "$ref": "#/definitions/ModelWithString" + } + } + }, + "ModelThatExtends": { + "description": "This is a model that extends another model", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ModelWithString" + }, + { + "type": "object", + "properties": { + "propExtendsA": { + "type": "string" + }, + "propExtendsB": { + "$ref": "#/definitions/ModelWithString" + } + } + } + ] + }, + "ModelThatExtendsExtends": { + "description": "This is a model that extends another model", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ModelWithString" + }, + { + "$ref": "#/definitions/ModelThatExtends" + }, + { + "type": "object", + "properties": { + "propExtendsC": { + "type": "string" + }, + "propExtendsD": { + "$ref": "#/definitions/ModelWithString" + } + } + } + ] + }, + "default": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "ModelWithPattern": { + "description": "This is a model that contains a some patterns", + "type": "object", + "required": ["key", "name"], + "properties": { + "key": { + "maxLength": 64, + "pattern": "^[a-zA-Z0-9_]*$", + "type": "string" + }, + "name": { + "maxLength": 255, + "type": "string" + }, + "enabled": { + "type": "boolean", + "readOnly": true + }, + "modified": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "id": { + "type": "string", + "pattern": "^\\d{2}-\\d{3}-\\d{4}$" + }, + "text": { + "type": "string", + "pattern": "^\\w+$" + }, + "patternWithSingleQuotes": { + "type": "string", + "pattern": "^[a-zA-Z0-9']*$" + }, + "patternWithNewline": { + "type": "string", + "pattern": "aaa\nbbb" + }, + "patternWithBacktick": { + "type": "string", + "pattern": "aaa`bbb" + } + } + }, + "parameter.ActivityParams": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "graduate_id": { + "type": "integer" + }, + "organization_id": { + "type": "integer" + }, + "parent_activity": { + "type": "integer" + }, + "post_id": { + "type": "integer" + } + } + }, + "response.PostActivityResponse": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "graduate_id": { + "type": "integer" + }, + "organization_id": { + "type": "integer" + }, + "parent_activity_id": { + "type": "integer" + }, + "post_id": { + "type": "integer" + } + } + }, + "failure.Failure": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "reference_code": { + "type": "string" + } + } + } + } +} diff --git a/packages/openapi-ts/test/spec/2.0.x/schema-unknown.yaml b/packages/openapi-ts/test/spec/2.0.x/schema-unknown.yaml new file mode 100644 index 000000000..8e94324d9 --- /dev/null +++ b/packages/openapi-ts/test/spec/2.0.x/schema-unknown.yaml @@ -0,0 +1,135 @@ +swagger: '2.0' +info: + title: OpenAPI 2.0 schema unknown example + description: https://github.com/hey-api/openapi-ts/issues/1402 + version: '1' +host: api.postmarkapp.com +basePath: / +produces: + - application/json +consumes: + - application/json +definitions: + SendEmailRequest: + properties: + From: + description: The sender email address. Must have a registered and confirmed Sender Signature. + type: string + To: + description: Recipient email address. Multiple addresses are comma seperated. Max 50. + type: string + Cc: + description: Recipient email address. Multiple addresses are comma seperated. Max 50. + type: string + Bcc: + description: Bcc recipient email address. Multiple addresses are comma seperated. Max 50. + type: string + Subject: + description: Email Subject + type: string + Tag: + description: Email tag that allows you to categorize outgoing emails and get detailed statistics. + type: string + HtmlBody: + description: If no TextBody specified HTML email message + type: string + TextBody: + description: If no HtmlBody specified Plain text email message + type: string + ReplyTo: + description: Reply To override email address. Defaults to the Reply To set in the sender signature. + type: string + TrackOpens: + description: Activate open tracking for this email. + type: boolean + TrackLinks: + description: Replace links in content to enable "click tracking" stats. Default is 'null', which uses the server's LinkTracking setting'. + type: string + enum: ['None', 'HtmlAndText', 'HtmlOnly', 'TextOnly'] + Headers: + $ref: '#/definitions/HeaderCollection' + Attachments: + $ref: '#/definitions/AttachmentCollection' + MessageHeader: + description: A single header for an email message. + properties: + Name: + description: The header's name. + type: string + Value: + description: The header's value. + type: string + HeaderCollection: + type: array + items: + $ref: '#/definitions/MessageHeader' + Attachment: + description: An attachment for an email message. + properties: + Name: + type: string + Content: + type: string + ContentType: + type: string + ContentID: + type: string + AttachmentCollection: + type: array + items: + $ref: '#/definitions/Attachment' + SendEmailResponse: + description: The standard response when a postmark message is sent + properties: + To: + type: string + SubmittedAt: + type: string + format: 'date-time' + MessageID: + type: string + ErrorCode: + type: integer + Message: + type: string + StandardPostmarkResponse: + description: 'A Postmark API error.' + properties: + ErrorCode: + type: integer + Message: + type: string +responses: + 422: + description: 'An error was generated due to incorrect use of the API. See the Message associated with this response for more information.' + schema: + $ref: '#/definitions/StandardPostmarkResponse' + 500: + description: 'Indicates an internal server error occurred.' +paths: + #Message Sending API + /email: + post: + operationId: sendEmail + tags: + - Sending API + summary: Send a single email + parameters: + - name: X-Postmark-Server-Token + required: true + description: The token associated with the Server on which this request will operate. + type: string + in: header + - name: body + in: body + schema: + $ref: '#/definitions/SendEmailRequest' + responses: + 200: + description: OK + schema: + $ref: '#/definitions/SendEmailResponse' + 422: + $ref: '#/responses/422' + 500: + $ref: '#/responses/500' diff --git a/packages/openapi-ts/test/spec/3.0.x/enum-names-values.json b/packages/openapi-ts/test/spec/3.0.x/enum-names-values.json index f701f9412..2737b2e37 100644 --- a/packages/openapi-ts/test/spec/3.0.x/enum-names-values.json +++ b/packages/openapi-ts/test/spec/3.0.x/enum-names-values.json @@ -24,7 +24,7 @@ "type": "string" }, "Numbers": { - "enum": [100, 200, 300], + "enum": [100, 200, 300, -100, -200, -300], "type": "number" } } 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 8a2c95f97..241488c6b 100644 --- a/packages/openapi-ts/test/spec/3.0.x/full.json +++ b/packages/openapi-ts/test/spec/3.0.x/full.json @@ -788,7 +788,7 @@ }, "/api/v{api-version}/duplicate": { "delete": { - "tags": ["Duplicate", "Duplicate"], + "tags": ["Duplicate"], "operationId": "DuplicateName" }, "get": { diff --git a/packages/openapi-ts/test/spec/3.1.x/enum-names-values.json b/packages/openapi-ts/test/spec/3.1.x/enum-names-values.json index 2429e1233..8f03b2619 100644 --- a/packages/openapi-ts/test/spec/3.1.x/enum-names-values.json +++ b/packages/openapi-ts/test/spec/3.1.x/enum-names-values.json @@ -23,7 +23,7 @@ "type": ["string", "null"] }, "Numbers": { - "enum": [100, 200, 300], + "enum": [100, 200, 300, -100, -200, -300], "type": "number" } }