Skip to content

Commit

Permalink
Refactor and creation of getResponseBody (#921)
Browse files Browse the repository at this point in the history
* fix: use of custom responseBody oas

* chore: update

* chore: fix
  • Loading branch information
stijnvanhulle authored Apr 6, 2024
1 parent 717d243 commit 95c37c6
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 66 deletions.
5 changes: 5 additions & 0 deletions .changeset/thirty-cobras-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kubb/swagger": patch
---

Incorrect content type for response(refactor)
18 changes: 1 addition & 17 deletions examples/vue-query-v5/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/**"]
Expand Down
6 changes: 4 additions & 2 deletions packages/swagger-ts/src/components/OperationSchema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ts.TypeNode> = {
response: factory.createTypeReferenceNode(factory.createIdentifier(schemas.response.name), undefined),
const properties: Record<string, ts.TypeNode> = {}

if (schemas.response) {
properties['response'] = factory.createTypeReferenceNode(factory.createIdentifier(schemas.response.name), undefined)
}

if (schemas.request) {
Expand Down
156 changes: 109 additions & 47 deletions packages/swagger/src/OperationGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any, any, any>> = T extends OperationGenerator<infer Options, any, any> ? Options : never
Expand Down Expand Up @@ -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)

Expand All @@ -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 {
Expand All @@ -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 {
Expand Down

0 comments on commit 95c37c6

Please sign in to comment.