diff --git a/.changeset/new-timers-tie.md b/.changeset/new-timers-tie.md new file mode 100644 index 000000000..1a47983c4 --- /dev/null +++ b/.changeset/new-timers-tie.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +fix: experimental parser transforms anyOf date and null 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 d872b26d9..158353828 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 @@ -37,6 +37,26 @@ export const getSchemaType = ({ } }; +const parseSchemaJsDoc = ({ + irSchema, + schema, +}: { + irSchema: IRSchemaObject; + schema: SchemaObject; +}) => { + if (schema.deprecated !== undefined) { + irSchema.deprecated = schema.deprecated; + } + + if (schema.description) { + irSchema.description = schema.description; + } + + if (schema.title) { + irSchema.title = schema.title; + } +}; + const parseSchemaMeta = ({ irSchema, schema, @@ -89,10 +109,6 @@ const parseSchemaMeta = ({ } else if (schema.writeOnly) { irSchema.accessScope = 'write'; } - - if (schema.title) { - irSchema.title = schema.title; - } }; const parseArray = ({ @@ -238,22 +254,6 @@ const parseString = ({ return irSchema; }; -const parseSchemaJsDoc = ({ - irSchema, - schema, -}: { - irSchema: IRSchemaObject; - schema: SchemaObject; -}) => { - if (schema.deprecated !== undefined) { - irSchema.deprecated = schema.deprecated; - } - - if (schema.description) { - irSchema.description = schema.description; - } -}; - const initIrSchema = ({ schema }: { schema: SchemaObject }): IRSchemaObject => { const irSchema: IRSchemaObject = {}; @@ -636,17 +636,19 @@ const parseNullableType = ({ }): IRSchemaObject => { if (!irSchema) { irSchema = initIrSchema({ schema }); - - parseSchemaMeta({ - irSchema, - schema, - }); } + const typeIrSchema: IRSchemaObject = {}; + + parseSchemaMeta({ + irSchema: typeIrSchema, + schema, + }); + const schemaItems: Array = [ parseOneType({ context, - irSchema: {}, + irSchema: typeIrSchema, schema, }), { 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 805b7b0f9..897f301e5 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 @@ -43,6 +43,26 @@ export const getSchemaTypes = ({ return []; }; +const parseSchemaJsDoc = ({ + irSchema, + schema, +}: { + irSchema: IRSchemaObject; + schema: SchemaObject; +}) => { + if (schema.deprecated !== undefined) { + irSchema.deprecated = schema.deprecated; + } + + if (schema.description) { + irSchema.description = schema.description; + } + + if (schema.title) { + irSchema.title = schema.title; + } +}; + const parseSchemaMeta = ({ irSchema, schema, @@ -119,10 +139,6 @@ const parseSchemaMeta = ({ } else if (schema.writeOnly) { irSchema.accessScope = 'write'; } - - if (schema.title) { - irSchema.title = schema.title; - } }; const parseArray = ({ @@ -291,22 +307,6 @@ const parseString = ({ return irSchema; }; -const parseSchemaJsDoc = ({ - irSchema, - schema, -}: { - irSchema: IRSchemaObject; - schema: SchemaObject; -}) => { - if (schema.deprecated !== undefined) { - irSchema.deprecated = schema.deprecated; - } - - if (schema.description) { - irSchema.description = schema.description; - } -}; - const initIrSchema = ({ schema }: { schema: SchemaObject }): IRSchemaObject => { const irSchema: IRSchemaObject = {}; @@ -742,26 +742,32 @@ const parseManyTypes = ({ }): IRSchemaObject => { if (!irSchema) { irSchema = initIrSchema({ schema }); - - parseSchemaMeta({ - irSchema, - schema, - }); } + const typeIrSchema: IRSchemaObject = {}; + + parseSchemaMeta({ + irSchema: typeIrSchema, + schema, + }); + const schemaItems: Array = []; for (const type of schema.type) { - schemaItems.push( - parseOneType({ - context, - irSchema: {}, - schema: { - ...schema, - type, - }, - }), - ); + if (type === 'null') { + schemaItems.push({ type: 'null' }); + } else { + schemaItems.push( + parseOneType({ + context, + irSchema: typeIrSchema, + schema: { + ...schema, + type, + }, + }), + ); + } } irSchema = addItemsToSchema({ diff --git a/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts index d03276cc6..a9c786ff5 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts @@ -314,7 +314,7 @@ const processSchemaType = ({ }); } - const nodes: Array = []; + let arrayNodes: Array = []; if ( schema.items.length === 2 && schema.items.find((item) => item.type === 'null' || item.type === 'void') @@ -323,33 +323,37 @@ const processSchemaType = ({ for (const item of schema.items) { const nodes = processSchemaType({ context, - dataExpression: 'item', + dataExpression: dataExpression || 'item', schema: item, }); if (nodes.length) { - const identifierItem = compiler.identifier({ text: 'item' }); - // processed means the item was transformed - nodes.push( - compiler.ifStatement({ - expression: identifierItem, - thenStatement: compiler.block({ - statements: - nodes.length === 1 - ? ts.isStatement(nodes[0]) - ? [] - : [ - compiler.returnStatement({ - expression: nodes[0], - }), - ] - : ensureStatements(nodes), + if (dataExpression) { + arrayNodes = arrayNodes.concat(nodes); + } else { + const identifierItem = compiler.identifier({ text: 'item' }); + // processed means the item was transformed + arrayNodes.push( + compiler.ifStatement({ + expression: identifierItem, + thenStatement: compiler.block({ + statements: + nodes.length === 1 + ? ts.isStatement(nodes[0]) + ? [] + : [ + compiler.returnStatement({ + expression: nodes[0], + }), + ] + : ensureStatements(nodes), + }), }), - }), - compiler.returnStatement({ expression: identifierItem }), - ); + compiler.returnStatement({ expression: identifierItem }), + ); + } } } - return nodes; + return arrayNodes; } console.warn( diff --git a/packages/openapi-ts/test/3.0.x.test.ts b/packages/openapi-ts/test/3.0.x.test.ts index a8d5a65ea..26112feba 100644 --- a/packages/openapi-ts/test/3.0.x.test.ts +++ b/packages/openapi-ts/test/3.0.x.test.ts @@ -201,13 +201,21 @@ describe(`OpenAPI ${VERSION}`, () => { }), description: 'handles non-exploded array query parameters', }, + { + config: createConfig({ + input: 'transformers-any-of-null.json', + output: 'transformers-any-of-null', + plugins: ['@hey-api/transformers'], + }), + description: 'transforms nullable date property', + }, { config: createConfig({ input: 'transformers-array.json', output: 'transformers-array', plugins: ['@hey-api/transformers'], }), - description: 'correctly transforms an array', + description: 'transforms an array', }, { config: createConfig({ diff --git a/packages/openapi-ts/test/3.1.x.test.ts b/packages/openapi-ts/test/3.1.x.test.ts index 28e935637..119c49397 100644 --- a/packages/openapi-ts/test/3.1.x.test.ts +++ b/packages/openapi-ts/test/3.1.x.test.ts @@ -262,13 +262,21 @@ describe(`OpenAPI ${VERSION}`, () => { description: 'does not set oneOf composition ref model properties as required', }, + { + config: createConfig({ + input: 'transformers-any-of-null.json', + output: 'transformers-any-of-null', + plugins: ['@hey-api/transformers'], + }), + description: 'transforms nullable date property', + }, { config: createConfig({ input: 'transformers-array.json', output: 'transformers-array', plugins: ['@hey-api/transformers'], }), - description: 'correctly transforms an array', + description: 'transforms an array', }, { config: createConfig({ diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/transformers-any-of-null/index.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/transformers-any-of-null/index.ts new file mode 100644 index 000000000..d602ae3ac --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/transformers-any-of-null/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './transformers.gen'; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/transformers-any-of-null/transformers.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/transformers-any-of-null/transformers.gen.ts new file mode 100644 index 000000000..996bb0a98 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/transformers-any-of-null/transformers.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { GetFooResponse } from './types.gen'; + +const fooSchemaResponseTransformer = (data: any) => { + if (data.foo) { + data.foo = new Date(data.foo); + } + if (data.bar) { + data.bar = new Date(data.bar); + } + return data; +}; + +export const getFooResponseTransformer = async (data: any): Promise => { + data = data.map((item: any) => { + return fooSchemaResponseTransformer(item); + }); + return data; +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.0.x/transformers-any-of-null/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.0.x/transformers-any-of-null/types.gen.ts new file mode 100644 index 000000000..4ce217d1f --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/3.0.x/transformers-any-of-null/types.gen.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Foo = { + foo?: string; + bar?: string | null; +}; + +export type GetFooData = { + body?: never; + url: '/foo'; +}; + +export type GetFooResponses = { + /** + * OK + */ + 200: Array; +}; + +export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/transformers-any-of-null/index.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/transformers-any-of-null/index.ts new file mode 100644 index 000000000..d602ae3ac --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/transformers-any-of-null/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './transformers.gen'; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/transformers-any-of-null/transformers.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/transformers-any-of-null/transformers.gen.ts new file mode 100644 index 000000000..67b61d10f --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/transformers-any-of-null/transformers.gen.ts @@ -0,0 +1,23 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { GetFooResponse } from './types.gen'; + +const fooSchemaResponseTransformer = (data: any) => { + if (data.foo) { + data.foo = new Date(data.foo); + } + if (data.bar) { + data.bar = new Date(data.bar); + } + if (data.baz) { + data.baz = new Date(data.baz); + } + return data; +}; + +export const getFooResponseTransformer = async (data: any): Promise => { + data = data.map((item: any) => { + return fooSchemaResponseTransformer(item); + }); + return data; +}; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/3.1.x/transformers-any-of-null/types.gen.ts b/packages/openapi-ts/test/__snapshots__/3.1.x/transformers-any-of-null/types.gen.ts new file mode 100644 index 000000000..789ca39c7 --- /dev/null +++ b/packages/openapi-ts/test/__snapshots__/3.1.x/transformers-any-of-null/types.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Foo = { + foo?: string; + bar?: string | null; + baz?: string | null; +}; + +export type GetFooData = { + body?: never; + url: '/foo'; +}; + +export type GetFooResponses = { + /** + * OK + */ + 200: Array; +}; + +export type GetFooResponse = GetFooResponses[keyof GetFooResponses]; \ No newline at end of file diff --git a/packages/openapi-ts/test/sample.cjs b/packages/openapi-ts/test/sample.cjs index 7ffae690e..ba2677911 100644 --- a/packages/openapi-ts/test/sample.cjs +++ b/packages/openapi-ts/test/sample.cjs @@ -5,8 +5,8 @@ const main = async () => { const config = { client: { // bundle: true, - // name: '@hey-api/client-axios', - name: '@hey-api/client-fetch', + name: '@hey-api/client-axios', + // name: '@hey-api/client-fetch', }, // debug: true, experimentalParser: true, @@ -14,7 +14,7 @@ const main = async () => { exclude: '^#/components/schemas/ModelWithCircularReference$', // include: // '^(#/components/schemas/import|#/paths/api/v{api-version}/simple/options)$', - path: './test/spec/3.1.x/transformers-array.json', + path: './test/spec/3.1.x/transformers-any-of-null.json', // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', }, // name: 'foo', @@ -42,17 +42,20 @@ const main = async () => { { // enums: 'typescript', // enums: 'typescript+namespace', - enums: 'javascript', - exportInlineEnums: true, - name: '@hey-api/typescript', + // enums: 'javascript', + // exportInlineEnums: true, + // name: '@hey-api/typescript', // style: 'PascalCase', // tree: true, }, { - name: '@tanstack/react-query', + // name: '@tanstack/react-query', // name: 'fastify', // name: 'zod', }, + { + // name: 'zod', + }, ], // useOptions: false, }; diff --git a/packages/openapi-ts/test/spec/3.0.x/transformers-any-of-null.json b/packages/openapi-ts/test/spec/3.0.x/transformers-any-of-null.json new file mode 100644 index 000000000..255e12436 --- /dev/null +++ b/packages/openapi-ts/test/spec/3.0.x/transformers-any-of-null.json @@ -0,0 +1,50 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "OpenAPI 3.0.3 transformers any of null example", + "version": "1" + }, + "paths": { + "/foo": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Foo" + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Foo": { + "type": "object", + "properties": { + "foo": { + "type": "string", + "format": "date-time" + }, + "bar": { + "anyOf": [ + { + "type": "string", + "nullable": true, + "format": "date-time" + } + ] + } + } + } + } + } +} diff --git a/packages/openapi-ts/test/spec/3.1.x/transformers-any-of-null.json b/packages/openapi-ts/test/spec/3.1.x/transformers-any-of-null.json new file mode 100644 index 000000000..ed14b139b --- /dev/null +++ b/packages/openapi-ts/test/spec/3.1.x/transformers-any-of-null.json @@ -0,0 +1,60 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "OpenAPI 3.1.1 transformers any of null example", + "version": "1" + }, + "paths": { + "/foo": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Foo" + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Foo": { + "type": "object", + "properties": { + "foo": { + "type": "string", + "format": "date-time" + }, + "bar": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, + "baz": { + "anyOf": [ + { + "type": ["string", "null"], + "format": "date-time" + } + ] + } + } + } + } + } +}