diff --git a/.changeset/thirty-cobras-push.md b/.changeset/thirty-cobras-push.md new file mode 100644 index 000000000..7345393cb --- /dev/null +++ b/.changeset/thirty-cobras-push.md @@ -0,0 +1,5 @@ +--- +"@kubb/swagger": patch +--- + +Incorrect content type for response(refactor) diff --git a/examples/vue-query-v5/tsconfig.json b/examples/vue-query-v5/tsconfig.json index 1748f22af..f38032761 100644 --- a/examples/vue-query-v5/tsconfig.json +++ b/examples/vue-query-v5/tsconfig.json @@ -16,23 +16,7 @@ "allowJs": true, "allowImportingTsExtensions": true, "noEmit": true, - "paths": { - "@kubb/cli": ["../../packages/cli/src/index.ts"], - "@kubb/eslint-config": ["../../packages/config/eslint-config/src/index.ts"], - "@kubb/tsup-config": ["../../packages/config/tsup-config/src/index.ts"], - "@kubb/ts-config": ["../../packages/config/ts-config/src/index.ts"], - "@kubb/core": ["../../packages/core/src/index.ts"], - "@kubb/swagger": ["../../packages/swagger/src/index.ts"], - "@kubb/swagger-client": ["../../packages/swagger-client/src/index.ts"], - "@kubb/swagger-client/client": ["../../packages/swagger-client/client.ts"], - "@kubb/swagger-client/ts-client": ["../../packages/swagger-client/client.ts"], - "@kubb/swagger-faker": ["../../packages/swagger-faker/src/index.ts"], - "@kubb/swagger-tanstack-query": ["../../packages/swagger-tanstack-query/src/index.ts"], - "@kubb/swagger-ts": ["../../packages/swagger-ts/src/index.ts"], - "@kubb/swagger-zod": ["../../packages/swagger-zod/src/index.ts"], - "@kubb/swagger-zodios": ["../../packages/swagger-zodios/src/index.ts"], - "@kubb/parser": ["../../packages/parser/src/index.ts"] - } + "paths": {} }, "include": ["./src/**/*", "vite.config.ts", "shims-vue.d.ts"], "exclude": ["**/node_modules", "**/types/**", "**/mocks/**"] diff --git a/packages/swagger-ts/src/components/OperationSchema.tsx b/packages/swagger-ts/src/components/OperationSchema.tsx index 9c0a38be9..69d31512d 100644 --- a/packages/swagger-ts/src/components/OperationSchema.tsx +++ b/packages/swagger-ts/src/components/OperationSchema.tsx @@ -20,8 +20,10 @@ import type { FileMeta, PluginOptions } from '../types.ts' type Props = {} function printCombinedSchema(name: string, operation: Operation, schemas: OperationSchemas): string { - const properties: Record = { - response: factory.createTypeReferenceNode(factory.createIdentifier(schemas.response.name), undefined), + const properties: Record = {} + + if (schemas.response) { + properties['response'] = factory.createTypeReferenceNode(factory.createIdentifier(schemas.response.name), undefined) } if (schemas.request) { diff --git a/packages/swagger/src/OperationGenerator.ts b/packages/swagger/src/OperationGenerator.ts index 3283f61c4..03ba7f027 100644 --- a/packages/swagger/src/OperationGenerator.ts +++ b/packages/swagger/src/OperationGenerator.ts @@ -2,14 +2,14 @@ import { Generator } from '@kubb/core' import transformers from '@kubb/core/transformers' -import { findSchemaDefinition } from 'oas/utils' +import { findSchemaDefinition, matchesMimeType } from 'oas/utils' import { isReference } from './utils/isReference.ts' import type { KubbFile, PluginFactoryOptions, PluginManager } from '@kubb/core' import type { Plugin } from '@kubb/core' import type { HttpMethods as HttpMethod, MediaTypeObject, RequestBodyObject } from 'oas/types' -import type { Oas, OasTypes, OpenAPIV3, Operation } from './oas/index.ts' +import type { Oas, OasTypes, OpenAPIV3, OpenAPIV3_1, Operation } from './oas/index.ts' import type { ContentType, Exclude, Include, OperationSchemas, OperationsByMethod, Override } from './types.ts' export type GetOperationGeneratorOptions> = T extends OperationGenerator ? Options : never @@ -120,16 +120,11 @@ export abstract class OperationGenerator< } #getParametersSchema(operation: Operation, inKey: 'path' | 'query' | 'header'): OasTypes.SchemaObject | null { - const contentType = this.context.contentType || operation.getContentType() + const mediaType = this.context.contentType || operation.getContentType() const params = operation .getParameters() - .map((item) => { - const param = item as unknown as OpenAPIV3.ReferenceObject & OasTypes.ParameterObject - if (isReference(param)) { - return findSchemaDefinition(param.$ref, operation.api) as OasTypes.ParameterObject - } - - return param + .map((schema) => { + return this.#dereference(schema, { withRef: true }) }) .filter((v) => v.in === inKey) @@ -139,7 +134,7 @@ export abstract class OperationGenerator< return params.reduce( (schema, pathParameters) => { - const property = pathParameters.content?.[contentType]?.schema ?? (pathParameters.schema as OasTypes.SchemaObject) + const property = pathParameters.content?.[mediaType]?.schema ?? (pathParameters.schema as OasTypes.SchemaObject) const required = [...(schema.required || ([] as any)), pathParameters.required ? pathParameters.name : undefined].filter(Boolean) return { @@ -161,61 +156,128 @@ export abstract class OperationGenerator< ) } - #getResponseSchema(operation: Operation, statusCode: string | number): OasTypes.SchemaObject { - const contentType = this.context.contentType || operation.getContentType() + /** + * Oas does not have a getResponseBody(mediaType/contentType) + * TODO open PR in Oas + */ + #getResponseBodyFactory( + responseBody: boolean | OasTypes.ResponseObject, + ): (mediaType?: string) => OasTypes.MediaTypeObject | false | [string, OasTypes.MediaTypeObject, ...string[]] { + function hasResponseBody(res = responseBody): res is OasTypes.ResponseObject { + return !!res + } - const schema = operation.schema.responses?.[statusCode] as OpenAPIV3.ReferenceObject | OpenAPIV3.ResponseObject | undefined - if (isReference(schema)) { - const responseSchema = findSchemaDefinition(schema?.$ref, operation.api) as OasTypes.ResponseObject - const contentTypeSchema = responseSchema.content?.[contentType]?.schema as OasTypes.SchemaObject + return (mediaType) => { + if (!hasResponseBody(responseBody)) { + return false + } + + if (isReference(responseBody)) { + // If the request body is still a `$ref` pointer we should return false because this library + // assumes that you've run dereferencing beforehand. + return false + } + + if (!responseBody.content) { + return false + } + + if (mediaType) { + if (!(mediaType in responseBody.content)) { + return false + } + + return responseBody.content[mediaType]! + } + + // Since no media type was supplied we need to find either the first JSON-like media type that + // we've got, or the first available of anything else if no JSON-like media types are present. + let availableMediaType: string | undefined = undefined + const mediaTypes = Object.keys(responseBody.content) + mediaTypes.forEach((mt: string) => { + if (!availableMediaType && matchesMimeType.json(mt)) { + availableMediaType = mt + } + }) + + if (!availableMediaType) { + mediaTypes.forEach((mt: string) => { + if (!availableMediaType) { + availableMediaType = mt + } + }) + } + + if (availableMediaType) { + return [availableMediaType, responseBody.content[availableMediaType]!, ...(responseBody.description ? [responseBody.description] : [])] + } - if (isReference(contentTypeSchema)) { + return false + } + } + + #dereference(schema?: unknown, { withRef = false }: { withRef?: boolean } = {}) { + if (isReference(schema)) { + if (withRef) { return { - ...findSchemaDefinition(contentTypeSchema?.$ref, operation.api), - $ref: contentTypeSchema.$ref, - } as OasTypes.SchemaObject + ...findSchemaDefinition(schema?.$ref, this.context.oas.api), + $ref: schema.$ref, + } } + return findSchemaDefinition(schema?.$ref, this.context.oas.api) + } + + return schema + } - return contentTypeSchema + #getResponseSchema(operation: Operation, statusCode: string | number): OasTypes.SchemaObject { + if (operation.schema.responses) { + Object.keys(operation.schema.responses).forEach((key) => { + operation.schema.responses![key] = this.#dereference(operation.schema.responses![key]) + }) } - // check if contentType of content x exists, sometimes requestBody can have contentType x and responses 200 y. - const responseJSONSchema = schema?.content?.[contentType] - ? (schema?.content?.[contentType]?.schema as OasTypes.SchemaObject) - : (operation.getResponseAsJSONSchema(statusCode)?.at(0)?.schema as OasTypes.SchemaObject) + const getResponseBody = this.#getResponseBodyFactory(operation.getResponseByStatusCode(statusCode)) - if (isReference(responseJSONSchema)) { - return { - ...findSchemaDefinition(responseJSONSchema?.$ref, operation.api), - $ref: responseJSONSchema.$ref, - } as OasTypes.SchemaObject + const mediaType = this.context.contentType + const responseBody = getResponseBody(mediaType) + + if (responseBody === false) { + // return empty object because response will always be defined(request does not need a body) + return {} } - return responseJSONSchema + const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema + + if (!schema) { + // return empty object because response will always be defined(request does not need a body) + + return {} + } + + return this.#dereference(schema, { withRef: true }) } - #getRequestSchema(operation: Operation): OasTypes.SchemaObject | null { - if (!operation.hasRequestBody()) { - return null + #getRequestSchema(operation: Operation): OasTypes.SchemaObject | undefined { + const mediaType = this.context.contentType + + if (operation.schema.requestBody) { + operation.schema.requestBody = this.#dereference(operation.schema.requestBody) } - const contentType = this.context.contentType || operation.getContentType() - const requestBody = operation.getRequestBody() as MediaTypeObject - const requestBodyContentType = operation.getRequestBody(contentType) as MediaTypeObject - const schema = (requestBody?.schema || requestBodyContentType?.schema) as OasTypes.SchemaObject + const requestBody = operation.getRequestBody(mediaType) - if (!schema) { - return null + if (requestBody === false) { + return undefined } - if (isReference(schema)) { - return { - ...findSchemaDefinition(schema?.$ref, operation.api), - $ref: schema.$ref, - } as OasTypes.SchemaObject + const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema + + if (!schema) { + return undefined } - return schema + return this.#dereference(schema, { withRef: true }) } getSchemas(operation: Operation, forStatusCode?: string | number): OperationSchemas {