diff --git a/src/__tests__/__snapshots__/gen-api-models-oas3.test.ts.snap b/src/__tests__/__snapshots__/gen-api-models-oas3.test.ts.snap index 6a78104b..e04b4dd0 100644 --- a/src/__tests__/__snapshots__/gen-api-models-oas3.test.ts.snap +++ b/src/__tests__/__snapshots__/gen-api-models-oas3.test.ts.snap @@ -170,7 +170,7 @@ exports[`gen-api-models should generate the operator definition 1`] = ` */ // Request type definition - export type TestAuthBearerT = r.IGetApiRequestType<{readonly bearerToken: string}, \\"Authorization\\", never, r.IResponseType<200, undefined>|r.IResponseType<403, undefined>>; + export type TestAuthBearerT = r.IGetApiRequestType<{readonly bearerToken: string,readonly qo?: string,readonly qr: string}, \\"Authorization\\", never, r.IResponseType<200, undefined>|r.IResponseType<403, undefined>>; // Decodes the success response with a custom success type export function testAuthBearerDecoder(type: t.Type) { return r.composeResponseDecoders(r.ioResponseDecoder<200, (typeof type)[\\"_A\\"], (typeof type)[\\"_O\\"]>(200, type), r.constantResponseDecoder(403, undefined)); } @@ -508,3 +508,19 @@ exports[`gen-api-models should support file uploads 1`] = ` // Decodes the success response with the type defined in the specs export const testFileUploadDefaultDecoder = () => testFileUploadDecoder(t.undefined);" `; + +exports[`gen-api-models should support generate serializers 1`] = ` +" + /**************************************************************** + * testSerializer + */ + + // Request type definition + export type TestSerializerT = r.IPostApiRequestType<{readonly qo?: string,readonly paginationRequest?: string}, \\"Content-Type\\", never, r.IResponseType<200, Message>|r.IResponseType<403, Problem>>; + + // Decodes the success response with a custom success type + export function testSerializerDecoder(type: t.Type) { return r.composeResponseDecoders(r.ioResponseDecoder<200, (typeof type)[\\"_A\\"], (typeof type)[\\"_O\\"]>(200, type), r.ioResponseDecoder<403, (typeof Problem)[\\"_A\\"], (typeof Problem)[\\"_O\\"]>(403, Problem)); } + + // Decodes the success response with the type defined in the specs + export const testSerializerDefaultDecoder = () => testSerializerDecoder(Message);" +`; diff --git a/src/__tests__/__snapshots__/gen-api-models.test.ts.snap b/src/__tests__/__snapshots__/gen-api-models.test.ts.snap index 3fa0ca2f..815dc1cd 100644 --- a/src/__tests__/__snapshots__/gen-api-models.test.ts.snap +++ b/src/__tests__/__snapshots__/gen-api-models.test.ts.snap @@ -508,3 +508,19 @@ exports[`gen-api-models should support file uploads 1`] = ` // Decodes the success response with the type defined in the specs export const testFileUploadDefaultDecoder = () => testFileUploadDecoder(t.undefined);" `; + +exports[`gen-api-models should support generate serializers 1`] = ` +" + /**************************************************************** + * testSerializer + */ + + // Request type definition + export type TestSerializerT = r.IPostApiRequestType<{readonly qo?: string,readonly paginationRequest?: string}, \\"Content-Type\\", never, r.IResponseType<200, Message>|r.IResponseType<403, Problem>>; + + // Decodes the success response with a custom success type + export function testSerializerDecoder(type: t.Type) { return r.composeResponseDecoders(r.ioResponseDecoder<200, (typeof type)[\\"_A\\"], (typeof type)[\\"_O\\"]>(200, type), r.ioResponseDecoder<403, (typeof Problem)[\\"_A\\"], (typeof Problem)[\\"_O\\"]>(403, Problem)); } + + // Decodes the success response with the type defined in the specs + export const testSerializerDefaultDecoder = () => testSerializerDecoder(Message);" +`; diff --git a/src/__tests__/api.yaml b/src/__tests__/api.yaml index fec97352..6bcf4553 100644 --- a/src/__tests__/api.yaml +++ b/src/__tests__/api.yaml @@ -45,7 +45,31 @@ paths: responses: "200": description: "File uploaded" + /test-serializers: + post: + operationId: "testSerializer" + parameters: + - name: "qo" + in: "query" + required: false + type: "string" + - $ref: "#/parameters/PaginationRequest" + responses: + "200": + description: "File uploaded" + schema: + $ref: "#/definitions/Message" + "403": + description: "Error string" + schema: + $ref: "#/definitions/Problem" definitions: + Problem: + properties: + title: + type: string + type: + type: string AllOfTest: allOf: - type: object diff --git a/src/__tests__/api_oas3.yaml b/src/__tests__/api_oas3.yaml index 9e3c66d2..3040c5a1 100644 --- a/src/__tests__/api_oas3.yaml +++ b/src/__tests__/api_oas3.yaml @@ -42,6 +42,29 @@ paths: responses: "200": description: File uploaded + /test-serializers: + post: + operationId: "testSerializer" + parameters: + - name: "qo" + in: "query" + required: false + schema: + type: "string" + - $ref: "#/components/parameters/PaginationRequest" + responses: + "200": + description: "File uploaded" + content: + application/json: + schema: + $ref: "#/components/schemas/Message" + "403": + description: "Error string" + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" servers: - url: https://localhost/api/v1 components: @@ -79,6 +102,12 @@ components: write: Grants write access admin: Grants access to admin operations schemas: + Problem: + properties: + title: + type: string + type: + type: string AllOfTest: allOf: - type: object diff --git a/src/__tests__/gen-api-models-oas3.test.ts b/src/__tests__/gen-api-models-oas3.test.ts index 146aa8b5..8b0fac8f 100644 --- a/src/__tests__/gen-api-models-oas3.test.ts +++ b/src/__tests__/gen-api-models-oas3.test.ts @@ -273,7 +273,26 @@ describe("gen-api-models", () => { "post", operation.operationId, operation, - spec.parameters, + spec.components.parameters, + spec.components.securitySchemes, + [], + {}, + "undefined", + "undefined", + true + ); + + expect(code.e1).toMatchSnapshot(); + }); + + it("should support generate serializers", async () => { + const operation = spec.paths["/test-serializers"].post; + + const code = await renderOperation( + "post", + operation.operationId, + operation, + spec.components.parameters, spec.components.securitySchemes, [], {}, diff --git a/src/__tests__/gen-api-models.test.ts b/src/__tests__/gen-api-models.test.ts index 059131e2..4d7edeb3 100644 --- a/src/__tests__/gen-api-models.test.ts +++ b/src/__tests__/gen-api-models.test.ts @@ -284,4 +284,24 @@ describe("gen-api-models", () => { expect(code.e1).toMatchSnapshot(); }); + + it("should support generate serializers", async () => { + const operation = spec.paths["/test-serializers"].post; + + const code = await renderOperation( + "post", + operation.operationId, + operation, + spec.parameters, + spec.securityDefinitions, + [], + {}, + "undefined", + "undefined", + true + ); + + expect(code.e1).toMatchSnapshot(); + }); + }); diff --git a/src/gen-api-models.ts b/src/gen-api-models.ts index 1946278c..c888b942 100644 --- a/src/gen-api-models.ts +++ b/src/gen-api-models.ts @@ -62,13 +62,25 @@ function typeFromRef( s: string ): ITuple2<"definition" | "parameter" | "other", string> | undefined { const parts = s.split("/"); + + if (!parts) { + return undefined; + } + + // If it's an OAS3, remove the "components" part. + if (parts[1] === "components") { + parts.splice(1,1); + } + if (parts && parts.length === 3) { const refType: "definition" | "parameter" | "other" = parts[1] === "definitions" ? "definition" - : parts[1] === "parameters" - ? "parameter" - : "other"; + : parts[1] === "schemas" + ? "definition" + : parts[1] === "parameters" + ? "parameter" + : "other"; return Tuple2(refType, parts[2]); } return undefined; @@ -120,11 +132,12 @@ export function renderOperation( OpenAPIV2.InBodyParameterObject | OpenAPIV3.ParameterObject >; parameters.forEach(param => { - if (param.name && (param as any).type) { + if (param.name && getParameterType(param)) { // The parameter description is inline + // and the parameter type is not undefined. const isRequired = param.required === true; params[`${param.name}${isRequired ? "" : "?"}`] = specTypeToTs( - (param as any).type + getParameterType(param)! ); return; } @@ -148,11 +161,17 @@ export function renderOperation( console.warn(`Unrecognized ref type [${refInParam}]`); return; } + // if the reference type is "definition" + // e2 contains a schema object + // otherwise it is the schema name const paramType: string | undefined = refType === "definition" ? parsedRef.e2 - : specParameters - ? specTypeToTs((specParameters as any)[parsedRef.e2].type) + // check that specParameters contain a valid declaration too! + : specParameters && getParameterType((specParameters as any)[parsedRef.e2]) + ? specTypeToTs( + getParameterType((specParameters as any)[parsedRef.e2])! + ) : undefined; if (paramType === undefined) { @@ -217,7 +236,19 @@ export function renderOperation( const responses = Object.keys(operation.responses as object).map( responseStatus => { const response = operation.responses![responseStatus]; - const typeRef = response.schema ? response.schema.$ref : undefined; + const media_type = "application/json" + const typeRef = + // get schema from Swagger... + response.schema + ? response.schema.$ref + // ... or try with OAS3 + : response.content + ? response.content[media_type] + && response.content[media_type].schema + ? response.content[media_type].schema.$ref + : undefined + // Not OAS2 or missing media-type in response.content + : undefined; const parsedRef = typeRef ? typeFromRef(typeRef) : undefined; if (parsedRef !== undefined) { importedTypes.add(parsedRef.e2); @@ -273,6 +304,20 @@ export function renderOperation( return Tuple2(code, importedTypes); } +function getParameterType( + parameter: + any | undefined +): string | undefined { + if (!parameter) { + return undefined; + } + return parameter.type + ? parameter.type + : parameter.schema + ? parameter.schema.type + : undefined; +} + function getAuthHeaders( securityDefinitions: | OpenAPIV2.SecurityDefinitionsObject