fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: apply transformer when response uses anyOf with $ref and null

Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com>

+243 -6
+1 -1
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/transformers-any-of-null/index.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 2 3 - export type { ClientOptions, Foo, GetFooData, GetFooResponse, GetFooResponses } from './types.gen'; 3 + export type { ClientOptions, Foo, GetFooData, GetFooResponse, GetFooResponses, GetNullablePollData, GetNullablePollResponse, GetNullablePollResponses, GetPollData, GetPollResponse, GetPollResponses, Poll } from './types.gen';
+18 -1
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/transformers-any-of-null/transformers.gen.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 2 3 - import type { GetFooResponse } from './types.gen'; 3 + import type { GetFooResponse, GetNullablePollResponse, GetPollResponse } from './types.gen'; 4 4 5 5 const fooSchemaResponseTransformer = (data: any) => { 6 6 if (data.foo) { ··· 19 19 data = data.map((item: any) => fooSchemaResponseTransformer(item)); 20 20 return data; 21 21 }; 22 + 23 + const pollSchemaResponseTransformer = (data: any) => { 24 + data.createdAt = new Date(data.createdAt); 25 + return data; 26 + }; 27 + 28 + export const getPollResponseTransformer = async (data: any): Promise<GetPollResponse> => { 29 + data = pollSchemaResponseTransformer(data); 30 + return data; 31 + }; 32 + 33 + export const getNullablePollResponseTransformer = async (data: any): Promise<GetNullablePollResponse> => { 34 + if (data) { 35 + data = pollSchemaResponseTransformer(data); 36 + } 37 + return data; 38 + };
+37
packages/openapi-ts-tests/main/test/__snapshots__/3.0.x/transformers-any-of-null/types.gen.ts
··· 4 4 baseUrl: `${string}://${string}` | (string & {}); 5 5 }; 6 6 7 + export type Poll = { 8 + id: number; 9 + createdAt: Date; 10 + }; 11 + 7 12 export type Foo = { 8 13 foo?: Date; 9 14 bar?: Date | null; ··· 25 30 }; 26 31 27 32 export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; 33 + 34 + export type GetPollData = { 35 + body?: never; 36 + path?: never; 37 + query?: never; 38 + url: '/polls'; 39 + }; 40 + 41 + export type GetPollResponses = { 42 + /** 43 + * OK 44 + */ 45 + 200: Poll; 46 + }; 47 + 48 + export type GetPollResponse = GetPollResponses[keyof GetPollResponses]; 49 + 50 + export type GetNullablePollData = { 51 + body?: never; 52 + path?: never; 53 + query?: never; 54 + url: '/polls/nullable'; 55 + }; 56 + 57 + export type GetNullablePollResponses = { 58 + /** 59 + * OK 60 + */ 61 + 200: Poll | null; 62 + }; 63 + 64 + export type GetNullablePollResponse = GetNullablePollResponses[keyof GetNullablePollResponses];
+1 -1
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transformers-any-of-null/index.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 2 3 - export type { ClientOptions, Foo, GetFooData, GetFooResponse, GetFooResponses, NestedDateObject, NestedDateObjectData, NestedDateObjectResponse, NestedDateObjectResponses } from './types.gen'; 3 + export type { ClientOptions, Foo, GetFooData, GetFooResponse, GetFooResponses, GetNullablePollData, GetNullablePollResponse, GetNullablePollResponses, GetPollData, GetPollResponse, GetPollResponses, NestedDateObject, NestedDateObjectData, NestedDateObjectResponse, NestedDateObjectResponses, Poll } from './types.gen';
+18 -1
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transformers-any-of-null/transformers.gen.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 2 3 - import type { GetFooResponse, NestedDateObjectResponse } from './types.gen'; 3 + import type { GetFooResponse, GetNullablePollResponse, GetPollResponse, NestedDateObjectResponse } from './types.gen'; 4 4 5 5 const fooSchemaResponseTransformer = (data: any) => { 6 6 if (data.foo) { ··· 20 20 21 21 export const getFooResponseTransformer = async (data: any): Promise<GetFooResponse> => { 22 22 data = data.map((item: any) => fooSchemaResponseTransformer(item)); 23 + return data; 24 + }; 25 + 26 + const pollSchemaResponseTransformer = (data: any) => { 27 + data.createdAt = new Date(data.createdAt); 28 + return data; 29 + }; 30 + 31 + export const getPollResponseTransformer = async (data: any): Promise<GetPollResponse> => { 32 + data = pollSchemaResponseTransformer(data); 33 + return data; 34 + }; 35 + 36 + export const getNullablePollResponseTransformer = async (data: any): Promise<GetNullablePollResponse> => { 37 + if (data) { 38 + data = pollSchemaResponseTransformer(data); 39 + } 23 40 return data; 24 41 }; 25 42
+37
packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/transformers-any-of-null/types.gen.ts
··· 4 4 baseUrl: `${string}://${string}` | (string & {}); 5 5 }; 6 6 7 + export type Poll = { 8 + id: number; 9 + createdAt: Date; 10 + }; 11 + 7 12 /** 8 13 * Object with a nested date structure 9 14 */ ··· 35 40 }; 36 41 37 42 export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; 43 + 44 + export type GetPollData = { 45 + body?: never; 46 + path?: never; 47 + query?: never; 48 + url: '/polls'; 49 + }; 50 + 51 + export type GetPollResponses = { 52 + /** 53 + * OK 54 + */ 55 + 200: Poll; 56 + }; 57 + 58 + export type GetPollResponse = GetPollResponses[keyof GetPollResponses]; 59 + 60 + export type GetNullablePollData = { 61 + body?: never; 62 + path?: never; 63 + query?: never; 64 + url: '/polls/nullable'; 65 + }; 66 + 67 + export type GetNullablePollResponses = { 68 + /** 69 + * OK 70 + */ 71 + 200: Poll | null; 72 + }; 73 + 74 + export type GetNullablePollResponse = GetNullablePollResponses[keyof GetNullablePollResponses]; 38 75 39 76 export type NestedDateObjectData = { 40 77 body?: never;
+25 -2
packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts
··· 283 283 const { response } = operationResponsesMap(operation); 284 284 if (!response) return; 285 285 286 - if (response.items && response.items.length > 1 && response.logicalOperator !== 'and') { 286 + const isNullableUnion = 287 + response.items?.length === 2 && 288 + response.items.some((item) => item.type === 'null' || item.type === 'void'); 289 + 290 + if ( 291 + response.items && 292 + response.items.length > 1 && 293 + response.logicalOperator !== 'and' && 294 + !isNullableUnion 295 + ) { 287 296 if (plugin.context.config.logs.level === 'debug') { 288 297 console.warn( 289 298 `❗️ Transformers warning: route ${createOperationKey(operation)} has ${response.items.length} non-void success responses. This is currently not handled and we will not generate a response transformer. Please open an issue if you'd like this feature https://github.com/hey-api/openapi-ts/issues`, ··· 306 315 schema: response, 307 316 }); 308 317 if (!nodes.length) return; 318 + 319 + // For nullable union responses (e.g. anyOf: [SomeSchema, null]), wrap the 320 + // transformation in a null guard so that null data is returned as-is. 321 + // We require nodes.length >= 2 because we need at least one transformation 322 + // statement AND a return statement (empty .do() would fail validation). 323 + let finalNodes = nodes; 324 + if (isNullableUnion && nodes.length >= 2) { 325 + const lastNode = nodes[nodes.length - 1]!; 326 + if (isNodeReturnStatement(lastNode as Expr)) { 327 + const transformNodes = nodes.slice(0, -1) as Array<Expr>; 328 + finalNodes = [$.if($(dataVariableName)).do(...transformNodes), lastNode]; 329 + } 330 + } 331 + 309 332 const symbol = plugin.symbol( 310 333 applyNaming(operation.id, { 311 334 case: 'camelCase', ··· 328 351 .async() 329 352 .param(dataVariableName, (p) => p.type('any')) 330 353 .returns($.type('Promise').generic(symbolResponse)) 331 - .do(...nodes), 354 + .do(...finalNodes), 332 355 ); 333 356 plugin.node(value); 334 357 },
+52
specs/3.0.x/transformers-any-of-null.json
··· 23 23 } 24 24 } 25 25 } 26 + }, 27 + "/polls": { 28 + "get": { 29 + "operationId": "getPoll", 30 + "responses": { 31 + "200": { 32 + "description": "OK", 33 + "content": { 34 + "application/json": { 35 + "schema": { 36 + "$ref": "#/components/schemas/Poll" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + }, 44 + "/polls/nullable": { 45 + "get": { 46 + "operationId": "getNullablePoll", 47 + "responses": { 48 + "200": { 49 + "description": "OK", 50 + "content": { 51 + "application/json": { 52 + "schema": { 53 + "anyOf": [ 54 + { 55 + "$ref": "#/components/schemas/Poll" 56 + } 57 + ], 58 + "nullable": true 59 + } 60 + } 61 + } 62 + } 63 + } 64 + } 26 65 } 27 66 }, 28 67 "components": { 29 68 "schemas": { 69 + "Poll": { 70 + "type": "object", 71 + "properties": { 72 + "id": { 73 + "type": "integer" 74 + }, 75 + "createdAt": { 76 + "type": "string", 77 + "format": "date-time" 78 + } 79 + }, 80 + "required": ["id", "createdAt"] 81 + }, 30 82 "Foo": { 31 83 "type": "object", 32 84 "properties": {
+54
specs/3.1.x/transformers-any-of-null.json
··· 24 24 } 25 25 } 26 26 }, 27 + "/polls": { 28 + "get": { 29 + "operationId": "getPoll", 30 + "responses": { 31 + "200": { 32 + "description": "OK", 33 + "content": { 34 + "application/json": { 35 + "schema": { 36 + "$ref": "#/components/schemas/Poll" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + }, 44 + "/polls/nullable": { 45 + "get": { 46 + "operationId": "getNullablePoll", 47 + "responses": { 48 + "200": { 49 + "description": "OK", 50 + "content": { 51 + "application/json": { 52 + "schema": { 53 + "anyOf": [ 54 + { 55 + "$ref": "#/components/schemas/Poll" 56 + }, 57 + { 58 + "type": "null" 59 + } 60 + ] 61 + } 62 + } 63 + } 64 + } 65 + } 66 + } 67 + }, 27 68 "/api/nested-date-object": { 28 69 "get": { 29 70 "operationId": "nestedDateObject", ··· 44 85 }, 45 86 "components": { 46 87 "schemas": { 88 + "Poll": { 89 + "type": "object", 90 + "properties": { 91 + "id": { 92 + "type": "integer" 93 + }, 94 + "createdAt": { 95 + "type": "string", 96 + "format": "date-time" 97 + } 98 + }, 99 + "required": ["id", "createdAt"] 100 + }, 47 101 "NestedDateObject": { 48 102 "description": "Object with a nested date structure", 49 103 "type": "object",