From e040c59e66993ef610e16f71ab878afe0de44ec4 Mon Sep 17 00:00:00 2001 From: Lubos Date: Sun, 23 Jun 2024 16:59:02 +0200 Subject: [PATCH 1/5] fix: infinite loop in transforms --- .../src/openApi/common/parser/type.ts | 2 +- .../src/openApi/v3/parser/getModel.ts | 4 +- .../openapi-ts/src/utils/write/transforms.ts | 210 +++++++++--------- packages/openapi-ts/src/utils/write/types.ts | 71 ++++-- packages/openapi-ts/test/sample.cjs | 8 +- 5 files changed, 157 insertions(+), 138 deletions(-) diff --git a/packages/openapi-ts/src/openApi/common/parser/type.ts b/packages/openapi-ts/src/openApi/common/parser/type.ts index 69948ca5e..df250f8dd 100644 --- a/packages/openapi-ts/src/openApi/common/parser/type.ts +++ b/packages/openapi-ts/src/openApi/common/parser/type.ts @@ -144,7 +144,7 @@ export const getType = ({ result.type = encodedType; result.base = encodedType; if (type.startsWith('#')) { - result.$refs = [...result.$refs, type]; + result.$refs = [...result.$refs, decodeURIComponent(type)]; } result.imports = [...result.imports, encodedType]; return result; diff --git a/packages/openapi-ts/src/openApi/v3/parser/getModel.ts b/packages/openapi-ts/src/openApi/v3/parser/getModel.ts index 32b24b69c..3afd830fc 100644 --- a/packages/openapi-ts/src/openApi/v3/parser/getModel.ts +++ b/packages/openapi-ts/src/openApi/v3/parser/getModel.ts @@ -82,7 +82,7 @@ export const getModel = ({ if (definition.$ref) { const definitionRef = getType({ type: definition.$ref }); - model.$refs = [...model.$refs, definition.$ref]; + model.$refs = [...model.$refs, decodeURIComponent(definition.$ref)]; model.base = definitionRef.base; model.export = 'reference'; model.imports = [...model.imports, ...definitionRef.imports]; @@ -147,7 +147,7 @@ export const getModel = ({ if (definition.items.$ref) { const arrayItems = getType({ type: definition.items.$ref }); - model.$refs = [...model.$refs, definition.items.$ref]; + model.$refs = [...model.$refs, decodeURIComponent(definition.items.$ref)]; model.base = arrayItems.base; model.export = 'array'; model.imports = [...model.imports, ...arrayItems.imports]; diff --git a/packages/openapi-ts/src/utils/write/transforms.ts b/packages/openapi-ts/src/utils/write/transforms.ts index 08d0cd76c..28555c249 100644 --- a/packages/openapi-ts/src/utils/write/transforms.ts +++ b/packages/openapi-ts/src/utils/write/transforms.ts @@ -3,8 +3,6 @@ import ts from 'typescript'; import { compiler } from '../../compiler'; import type { Model } from '../../openApi'; import type { Client } from '../../types/client'; -import { getConfig } from '../config'; -import { processModel } from './types'; type OnNode = (node: ts.Node) => void; @@ -13,133 +11,127 @@ export const generateTransform = ( model: Model, onNode: OnNode, ) => { - const config = getConfig(); - if (config.types.dates === 'types+transform') { - if (model.meta?.hasTransformer) { - // Transform already created (maybe the model is used in other models) so we just bail here - return; + // Ignore if transforms are disabled or the transform has been already created + if (!model.meta || model.meta.hasTransformer) { + return; + } + + const generateForProperty = (rootPath: string[], property: Model) => { + const localPath = [...rootPath, property.name]; + + if ( + property.type === 'string' && + property.export !== 'array' && + (property.format === 'date-time' || property.format === 'date') + ) { + return [ + compiler.transform.dateTransformMutation({ + path: localPath, + }), + ]; + } else { + // otherwise we recurse in case it's an object/array, and if it's not that will just bail with [] + return generateForModel(localPath, property); + } + }; + + const generateForArray = ( + localPath: string[], + localModel: Model, + refModels: Model[], + ) => { + if (localModel.export !== 'array') { + throw new Error( + 'generateForArray should only be called with array models', + ); } - const generateForProperty = (rootPath: string[], property: Model) => { - const localPath = [...rootPath, property.name]; + if (refModels.length === 1) { + const refModel = refModels[0]; - if ( - property.type === 'string' && - property.export !== 'array' && - (property.format === 'date-time' || property.format === 'date') - ) { + if (client.types[refModel.meta!.name].hasTransformer) { return [ - compiler.transform.dateTransformMutation({ + compiler.transform.arrayTransformMutation({ path: localPath, + transformer: refModel.meta!.name, }), ]; } else { - // otherwise we recurse in case it's an object/array, and if it's not that will just bail with [] - return generateForModel(localPath, property); - } - }; - - function generateForArray( - localPath: string[], - localModel: Model, - refModels: Model[], - ) { - if (localModel.export !== 'array') { - throw new Error( - 'generateForArray should only be called with array models', - ); - } - - if (refModels.length === 1) { - const refModel = refModels[0]; - - if (client.types[refModel.meta!.name].hasTransformer) { - return [ - compiler.transform.arrayTransformMutation({ - path: localPath, - transformer: refModel.meta!.name, - }), - ]; - } else { - // We do not currently support union types for transforms since discriminating them is a challenge - return []; - } + // We do not currently support union types for transforms since discriminating them is a challenge + return []; } + } - if (localModel.format === 'date' || localModel.format === 'date-time') { - return [ - compiler.transform.mapArray({ - path: localPath, - transformExpression: compiler.transform.newDate({ - parameterName: 'item', - }), + if (localModel.format === 'date' || localModel.format === 'date-time') { + return [ + compiler.transform.mapArray({ + path: localPath, + transformExpression: compiler.transform.newDate({ + parameterName: 'item', }), - ]; - } + }), + ]; + } + + // Not transform for this type + return []; + }; - // Not transform for this type + const generateForReference = (localPath: string[], refModels: Model[]) => { + if ( + refModels.length !== 1 || + client.types[refModels[0].meta!.name].hasTransformer !== true + ) { return []; } - function generateForReference(localPath: string[], refModels: Model[]) { - if ( - refModels.length !== 1 || - client.types[refModels[0].meta!.name].hasTransformer !== true - ) { - return []; + return compiler.transform.transformItem({ + path: localPath, + transformer: refModels[0].meta!.name, + }); + }; + + const generateForModel = ( + localPath: string[], + model: Model, + ): ts.Statement[] => { + const refModels = model.$refs.map((ref) => { + const refModel = client.models.find((m) => m.meta?.$ref === ref); + if (!refModel) { + throw new Error( + `Model ${ref} could not be found when building ref transform`, + ); } + return refModel; + }); + + switch (model.export) { + case 'reference': + return generateForReference(localPath, refModels); + case 'interface': + return model.properties.flatMap((property) => + generateForProperty(localPath, property), + ); + case 'array': + return generateForArray(localPath, model, refModels); - return compiler.transform.transformItem({ - path: localPath, - transformer: refModels[0].meta!.name, - }); + default: + // Unsupported + return []; } + }; - function generateForModel( - localPath: string[], - localModel: Model, - ): ts.Statement[] { - // We pre-transform refs (if any) so that they can be referenced in this transform - const refModels = localModel.$refs.map((ref) => { - const refModel = client.models.find((m) => m.meta!.$ref === ref); - if (!refModel) { - throw new Error( - `Model ${ref} could not be founded when building ref transform`, - ); - } - - // We have to jump the gun a bit here and get this pre-processed so any transformers can be consumed - processModel(client, refModel, onNode); - - return refModel; - }); - - switch (localModel.export) { - case 'reference': - return generateForReference(localPath, refModels); - case 'interface': - return localModel.properties.flatMap((property) => - generateForProperty(localPath, property), - ); - case 'array': - return generateForArray(localPath, localModel, refModels); - - default: - // Unsupported - return []; - } - } + const statements = generateForModel(['data'], model); + if (!statements.length) { + return; + } - const transformStatements = generateForModel(['data'], model); - if (transformStatements.length > 0) { - const transformFunction = compiler.transform.transformMutationFunction({ - modelName: model.meta!.name, - statements: transformStatements, - }); + const transformFunction = compiler.transform.transformMutationFunction({ + modelName: model.meta.name, + statements, + }); - client.types[model.meta!.name].hasTransformer = true; + client.types[model.meta.name].hasTransformer = true; - onNode(transformFunction); - } - } + onNode(transformFunction); }; diff --git a/packages/openapi-ts/src/utils/write/types.ts b/packages/openapi-ts/src/utils/write/types.ts index 8bf30fc32..813b3b837 100644 --- a/packages/openapi-ts/src/utils/write/types.ts +++ b/packages/openapi-ts/src/utils/write/types.ts @@ -23,7 +23,11 @@ import { import { generateTransform } from './transforms'; import { toType, uniqueTypeName } from './type'; -type OnNode = (node: Node) => void; +interface TypesProps { + client: Client; + model: Model; + onNode: (node: Node) => void; +} const serviceExportedNamespace = () => '$OpenApiTs'; @@ -56,9 +60,8 @@ const generateEnum = ({ ...uniqueTypeNameArgs }: Omit[0], 'name'> & Pick[0], 'client' | 'nameTransformer'> & - Pick & { - onNode: OnNode; - }) => { + Pick & + Pick) => { // generate types only for top-level models if (!meta) { return; @@ -89,9 +92,9 @@ const generateType = ({ ...uniqueTypeNameArgs }: Omit[0], 'name'> & Pick[0], 'client' | 'nameTransformer'> & - Pick & { + Pick & + Pick & { onCreated?: (name: string) => void; - onNode: OnNode; }) => { // generate types only for top-level models if (!meta) { @@ -111,12 +114,14 @@ const generateType = ({ } }; -const processComposition = (client: Client, model: Model, onNode: OnNode) => { - processType(client, model, onNode); - model.enums.forEach((enumerator) => processEnum(client, enumerator, onNode)); +const processComposition = (props: TypesProps) => { + processType(props); + props.model.enums.forEach((enumerator) => + processEnum({ ...props, model: enumerator }), + ); }; -const processEnum = (client: Client, model: Model, onNode: OnNode) => { +const processEnum = ({ client, model, onNode }: TypesProps) => { const config = getConfig(); const properties: Record = {}; @@ -174,7 +179,7 @@ const processEnum = (client: Client, model: Model, onNode: OnNode) => { }); }; -const processType = (client: Client, model: Model, onNode: OnNode) => { +const processType = ({ client, model, onNode }: TypesProps) => { generateType({ client, comment: [ @@ -185,21 +190,19 @@ const processType = (client: Client, model: Model, onNode: OnNode) => { onNode, type: toType(model), }); - - generateTransform(client, model, onNode); }; -export const processModel = (client: Client, model: Model, onNode: OnNode) => { - switch (model.export) { +const processModel = (props: TypesProps) => { + switch (props.model.export) { case 'all-of': case 'any-of': case 'one-of': case 'interface': - return processComposition(client, model, onNode); + return processComposition(props); case 'enum': - return processEnum(client, model, onNode); + return processEnum(props); default: - return processType(client, model, onNode); + return processType(props); } }; @@ -215,7 +218,10 @@ type PathMap = { type PathsMap = Record; -const processServiceTypes = (client: Client, onNode: OnNode) => { +const processServiceTypes = ({ + client, + onNode, +}: Pick) => { const pathsMap: PathsMap = {}; const config = getConfig(); @@ -540,15 +546,34 @@ export const processTypes = async ({ client: Client; files: Record; }): Promise => { + const config = getConfig(); + for (const model of client.models) { - processModel(client, model, (node) => { - files.types?.add(node); + processModel({ + client, + model, + onNode: (node) => { + files.types?.add(node); + }, }); } if (files.services && client.services.length) { - processServiceTypes(client, (node) => { - files.types?.add(node); + processServiceTypes({ + client, + onNode: (node) => { + files.types?.add(node); + }, }); } + + if (config.types.dates === 'types+transform') { + for (const model of client.models) { + if (model.export !== 'enum') { + generateTransform(client, model, (node) => { + files.types?.add(node); + }); + } + } + } }; diff --git a/packages/openapi-ts/test/sample.cjs b/packages/openapi-ts/test/sample.cjs index b01ad0731..4909d5476 100644 --- a/packages/openapi-ts/test/sample.cjs +++ b/packages/openapi-ts/test/sample.cjs @@ -5,6 +5,7 @@ const main = async () => { const config = { client: '@hey-api/client-fetch', input: './test/spec/v3.json', + // input: './test/spec/v3-transforms.json', // input: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', output: { path: './test/generated/sample/', @@ -13,14 +14,15 @@ const main = async () => { export: false, }, services: { - asClass: true, + // asClass: true, export: false, // name: '^Parameters', }, types: { + dates: 'types+transform', enums: 'typescript', - include: - '^ModelWithPrefixItemsConstantSizeArray|ModelWithAnyOfConstantSizeArray', + // include: + // '^ModelWithPrefixItemsConstantSizeArray|ModelWithAnyOfConstantSizeArray', // name: 'PascalCase', }, // useOptions: false, From 3b079ad05847f9623e576fbd72ed23844b91af66 Mon Sep 17 00:00:00 2001 From: Lubos Date: Mon, 24 Jun 2024 04:37:52 +0200 Subject: [PATCH 2/5] refactor: rewrite transformers logic --- packages/openapi-ts/src/compiler/convert.ts | 10 + packages/openapi-ts/src/compiler/function.ts | 19 + packages/openapi-ts/src/compiler/index.ts | 17 +- packages/openapi-ts/src/compiler/module.ts | 4 +- packages/openapi-ts/src/compiler/return.ts | 10 +- packages/openapi-ts/src/compiler/transform.ts | 105 ++---- packages/openapi-ts/src/compiler/types.ts | 2 +- .../src/openApi/common/interfaces/client.ts | 4 - packages/openapi-ts/src/utils/postprocess.ts | 83 ++--- packages/openapi-ts/src/utils/write/client.ts | 23 ++ .../openapi-ts/src/utils/write/services.ts | 116 +++--- .../src/utils/write/transformers.ts | 345 ++++++++++++++++++ .../openapi-ts/src/utils/write/transforms.ts | 137 ------- packages/openapi-ts/src/utils/write/type.ts | 45 ++- packages/openapi-ts/src/utils/write/types.ts | 111 ++---- .../v3_angular_transform/services.gen.ts.snap | 18 +- .../v3_angular_transform/types.gen.ts.snap | 96 +++-- .../v3_axios_transform/services.gen.ts.snap | 18 +- .../v3_axios_transform/types.gen.ts.snap | 96 +++-- .../v3_client_transform/services.gen.ts.snap | 18 +- .../v3_client_transform/types.gen.ts.snap | 96 +++-- .../services.gen.ts.snap | 12 +- .../types.gen.ts.snap | 98 +++-- .../services.gen.ts.snap | 12 +- .../types.gen.ts.snap | 98 +++-- .../v3_node_transform/services.gen.ts.snap | 18 +- .../v3_node_transform/types.gen.ts.snap | 96 +++-- .../v3_transform/services.gen.ts.snap | 18 +- .../generated/v3_transform/types.gen.ts.snap | 96 +++-- .../v3_xhr_transform/services.gen.ts.snap | 18 +- .../v3_xhr_transform/types.gen.ts.snap | 96 +++-- packages/openapi-ts/test/sample.cjs | 6 +- .../openapi-ts/test/spec/v3-transforms.json | 15 + 33 files changed, 1249 insertions(+), 707 deletions(-) create mode 100644 packages/openapi-ts/src/compiler/convert.ts create mode 100644 packages/openapi-ts/src/compiler/function.ts create mode 100644 packages/openapi-ts/src/utils/write/transformers.ts delete mode 100644 packages/openapi-ts/src/utils/write/transforms.ts diff --git a/packages/openapi-ts/src/compiler/convert.ts b/packages/openapi-ts/src/compiler/convert.ts new file mode 100644 index 000000000..4afd40bf8 --- /dev/null +++ b/packages/openapi-ts/src/compiler/convert.ts @@ -0,0 +1,10 @@ +import ts from 'typescript'; + +export const convertExpressionToStatement = ({ + expression, +}: { + expression: ts.Expression; +}) => { + const statement = ts.factory.createExpressionStatement(expression); + return statement; +}; diff --git a/packages/openapi-ts/src/compiler/function.ts b/packages/openapi-ts/src/compiler/function.ts new file mode 100644 index 000000000..f22a6dfa3 --- /dev/null +++ b/packages/openapi-ts/src/compiler/function.ts @@ -0,0 +1,19 @@ +import ts from 'typescript'; + +export const createCallExpression = ({ + parameters, + functionName, +}: { + parameters: Array; + functionName: string; +}) => { + const functionIdentifier = ts.factory.createIdentifier(functionName); + + const callExpression = ts.factory.createCallExpression( + functionIdentifier, + undefined, + parameters.map((parameter) => ts.factory.createIdentifier(parameter)), + ); + + return callExpression; +}; diff --git a/packages/openapi-ts/src/compiler/index.ts b/packages/openapi-ts/src/compiler/index.ts index 409a43cc7..f73a9a052 100644 --- a/packages/openapi-ts/src/compiler/index.ts +++ b/packages/openapi-ts/src/compiler/index.ts @@ -4,6 +4,8 @@ import path from 'node:path'; import ts from 'typescript'; import * as classes from './classes'; +import * as convert from './convert'; +import * as functions from './function'; import * as module from './module'; import * as _return from './return'; import * as transform from './transform'; @@ -78,6 +80,13 @@ export class TypeScriptFile { rmSync(this._path, options); } + /** + * Removes last node form the stack. Works as undo. + */ + public removeNode() { + this._items = this._items.slice(0, this._items.length - 1); + } + private _setName(fileName: string) { if (fileName.includes('index')) { return fileName; @@ -124,16 +133,23 @@ export const compiler = { create: classes.createClassDeclaration, method: classes.createMethodDeclaration, }, + convert: { + expressionToStatement: convert.convertExpressionToStatement, + }, export: { all: module.createExportAllDeclaration, const: module.createExportConstVariable, named: module.createNamedExportDeclarations, }, + function: { + call: functions.createCallExpression, + }, import: { named: module.createNamedImportDeclarations, }, return: { functionCall: _return.createReturnFunctionCall, + statement: _return.createReturnStatement, }, transform: { alias: transform.createAlias, @@ -143,7 +159,6 @@ export const compiler = { newDate: transform.createDateTransformerExpression, responseArrayTransform: transform.createResponseArrayTransform, transformItem: transform.createFunctionTransformMutation, - transformMutationFunction: transform.createTransformMutationFunction, }, typedef: { alias: typedef.createTypeAliasDeclaration, diff --git a/packages/openapi-ts/src/compiler/module.ts b/packages/openapi-ts/src/compiler/module.ts index d5ecdefc3..18f7bc703 100644 --- a/packages/openapi-ts/src/compiler/module.ts +++ b/packages/openapi-ts/src/compiler/module.ts @@ -69,11 +69,13 @@ export const createExportConstVariable = ({ constAssertion = false, expression, name, + typeName, }: { comment?: Comments; constAssertion?: boolean; expression: ts.Expression; name: string; + typeName?: string; }): ts.VariableStatement => { const initializer = constAssertion ? ts.factory.createAsExpression( @@ -84,7 +86,7 @@ export const createExportConstVariable = ({ const declaration = ts.factory.createVariableDeclaration( ts.factory.createIdentifier(name), undefined, - undefined, + typeName ? ts.factory.createTypeReferenceNode(typeName) : undefined, initializer, ); const statement = ts.factory.createVariableStatement( diff --git a/packages/openapi-ts/src/compiler/return.ts b/packages/openapi-ts/src/compiler/return.ts index b82338eb0..27eac8e0d 100644 --- a/packages/openapi-ts/src/compiler/return.ts +++ b/packages/openapi-ts/src/compiler/return.ts @@ -28,8 +28,12 @@ export const createReturnFunctionCall = ({ ) .filter(isType), ); - - const statement = ts.factory.createReturnStatement(expression); - + const statement = createReturnStatement({ expression }); return statement; }; + +export const createReturnStatement = ({ + expression, +}: { + expression?: ts.Expression; +}) => ts.factory.createReturnStatement(expression); diff --git a/packages/openapi-ts/src/compiler/transform.ts b/packages/openapi-ts/src/compiler/transform.ts index 5dcdc1b75..4bad086db 100644 --- a/packages/openapi-ts/src/compiler/transform.ts +++ b/packages/openapi-ts/src/compiler/transform.ts @@ -1,5 +1,8 @@ import ts from 'typescript'; +import { convertExpressionToStatement } from './convert'; +import { createReturnStatement } from './return'; + const getSafeAccessExpression = (path: string[]) => path .slice(1) @@ -36,8 +39,8 @@ export const createDateTransformMutation = ({ const statement = ts.factory.createIfStatement( safeAccessExpression, ts.factory.createBlock([ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( + convertExpressionToStatement({ + expression: ts.factory.createBinaryExpression( accessExpression, ts.SyntaxKind.EqualsToken, ts.factory.createNewExpression( @@ -46,7 +49,7 @@ export const createDateTransformMutation = ({ [accessExpression], ), ), - ), + }), ]), ); @@ -55,29 +58,31 @@ export const createDateTransformMutation = ({ export const createFunctionTransformMutation = ({ path, - transformer, + transformerName, }: { path: string[]; - transformer: string; + transformerName: string; }) => { const safeAccessExpression = getSafeAccessExpression(path); const accessExpression = getAccessExpression(path); + const thenStatement = ts.factory.createBlock( + [ + convertExpressionToStatement({ + expression: ts.factory.createCallExpression( + ts.factory.createIdentifier(transformerName), + undefined, + [accessExpression], + ), + }), + ], + true, + ); + const statement = [ ts.factory.createIfStatement( safeAccessExpression, - ts.factory.createBlock( - [ - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - ts.factory.createIdentifier(transformer), - undefined, - [accessExpression], - ), - ), - ], - true, - ), + thenStatement, undefined, ), ]; @@ -87,10 +92,10 @@ export const createFunctionTransformMutation = ({ export const createArrayTransformMutation = ({ path, - transformer, + transformerName, }: { path: string[]; - transformer: string; + transformerName: string; }): ts.Statement => { const safeAccessExpression = getSafeAccessExpression(path); const accessExpression = getAccessExpression(path); @@ -106,17 +111,17 @@ export const createArrayTransformMutation = ({ ), ts.factory.createBlock( [ - ts.factory.createExpressionStatement( - ts.factory.createCallChain( + convertExpressionToStatement({ + expression: ts.factory.createCallChain( ts.factory.createPropertyAccessExpression( accessExpression, ts.factory.createIdentifier('forEach'), ), undefined, undefined, - [ts.factory.createIdentifier(transformer)], + [ts.factory.createIdentifier(transformerName)], ), - ), + }), ], true, ), @@ -157,8 +162,8 @@ export const createArrayMapTransform = ({ ), ts.factory.createBlock( [ - ts.factory.createExpressionStatement( - ts.factory.createBinaryExpression( + convertExpressionToStatement({ + expression: ts.factory.createBinaryExpression( accessExpression, ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createCallChain( @@ -189,7 +194,7 @@ export const createArrayMapTransform = ({ ], ), ), - ), + }), ], true, ), @@ -199,44 +204,6 @@ export const createArrayMapTransform = ({ return statement; }; -export const createTransformMutationFunction = ({ - modelName, - statements, -}: { - modelName: string; - statements: ts.Statement[]; -}) => { - const transformFunction = ts.factory.createFunctionDeclaration( - [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], - undefined, - ts.factory.createIdentifier(modelName), - undefined, - [ - ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createIdentifier('data'), - undefined, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), - undefined, - ), - ], - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier(modelName), - undefined, - ), - ts.factory.createBlock( - [ - ...statements, - ts.factory.createReturnStatement(ts.factory.createIdentifier('data')), - ], - true, - ), - ); - - return transformFunction; -}; - export const createAlias = ({ existingName, name, @@ -294,8 +261,8 @@ export const createResponseArrayTransform = ({ ), ts.factory.createBlock( [ - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( + convertExpressionToStatement({ + expression: ts.factory.createCallExpression( ts.factory.createPropertyAccessExpression( ts.factory.createIdentifier('data'), ts.factory.createIdentifier('forEach'), @@ -303,13 +270,15 @@ export const createResponseArrayTransform = ({ undefined, [ts.factory.createIdentifier(transform)], ), - ), + }), ], true, ), undefined, ), - ts.factory.createReturnStatement(ts.factory.createIdentifier('data')), + createReturnStatement({ + expression: ts.factory.createIdentifier('data'), + }), ], true, ), diff --git a/packages/openapi-ts/src/compiler/types.ts b/packages/openapi-ts/src/compiler/types.ts index 86d13a0c1..3f7ed0a18 100644 --- a/packages/openapi-ts/src/compiler/types.ts +++ b/packages/openapi-ts/src/compiler/types.ts @@ -11,7 +11,7 @@ export type FunctionParameter = { isReadOnly?: boolean; isRequired?: boolean; name: string; - type: any | ts.TypeNode; + type?: any | ts.TypeNode; }; /** diff --git a/packages/openapi-ts/src/openApi/common/interfaces/client.ts b/packages/openapi-ts/src/openApi/common/interfaces/client.ts index 799aa8cc7..6f30f58cb 100644 --- a/packages/openapi-ts/src/openApi/common/interfaces/client.ts +++ b/packages/openapi-ts/src/openApi/common/interfaces/client.ts @@ -110,10 +110,6 @@ export interface ModelMeta { * Name passed to the initial `getModel()` call. */ name: string; - /** - * If transformers are enabled and this type is detected as needing one, this will be set - */ - hasTransformer?: boolean; } export interface Model extends Schema { diff --git a/packages/openapi-ts/src/utils/postprocess.ts b/packages/openapi-ts/src/utils/postprocess.ts index b2826821c..040b25cf5 100644 --- a/packages/openapi-ts/src/utils/postprocess.ts +++ b/packages/openapi-ts/src/utils/postprocess.ts @@ -1,4 +1,4 @@ -import type { Enum, Model, Operation, Service } from '../openApi'; +import type { Model, Operation, Service } from '../openApi'; import type { Client } from '../types/client'; import { sort } from './sort'; import { unique } from './unique'; @@ -16,72 +16,35 @@ export function postProcessClient(client: Client): Client { }; } -/** - * Post processes the model. - * This will clean up any double imports or enum values. - * @param model - */ -export function postProcessModel(model: Model): Model { - return { - ...model, - enum: postProcessModelEnum(model), - enums: postProcessModelEnums(model), - imports: postProcessModelImports(model), - }; -} - -/** - * Set unique enum values for the model - * @param model - */ -export function postProcessModelEnum(model: Model): Enum[] { - return model.enum.filter( - (property, index, arr) => - arr.findIndex((item) => item.value === property.value) === index, - ); -} +const postProcessModel = (model: Model): Model => ({ + ...model, + $refs: model.$refs.filter((value, index, arr) => unique(value, index, arr)), + enum: model.enum.filter( + (value, index, arr) => + arr.findIndex((item) => item.value === value.value) === index, + ), + enums: model.enums.filter( + (value, index, arr) => + arr.findIndex((item) => item.name === value.name) === index, + ), + imports: model.imports + .filter( + (value, index, arr) => unique(value, index, arr) && value !== model.name, + ) + .sort(sort), +}); -/** - * Set unique enum values for the model - * @param model The model that is post-processed - */ -export function postProcessModelEnums(model: Model): Model[] { - return model.enums.filter( - (property, index, arr) => - arr.findIndex((item) => item.name === property.name) === index, - ); -} - -/** - * Set unique imports, sorted by name - * @param model The model that is post-processed - */ -export function postProcessModelImports(model: Model): string[] { - return model.imports - .filter(unique) - .sort(sort) - .filter((name) => model.name !== name); -} - -export function postProcessService(service: Service): Service { +const postProcessService = (service: Service): Service => { const clone = { ...service }; clone.operations = postProcessServiceOperations(clone); clone.operations.forEach((operation) => { clone.imports.push(...operation.imports); }); - clone.imports = postProcessServiceImports(clone); + clone.imports = clone.imports.filter(unique).sort(sort); return clone; -} +}; -/** - * Set unique imports, sorted by name - * @param service - */ -export function postProcessServiceImports(service: Service): string[] { - return service.imports.filter(unique).sort(sort); -} - -export function postProcessServiceOperations(service: Service): Operation[] { +const postProcessServiceOperations = (service: Service): Operation[] => { const names = new Map(); return service.operations.map((operation) => { @@ -104,4 +67,4 @@ export function postProcessServiceOperations(service: Service): Operation[] { return clone; }); -} +}; diff --git a/packages/openapi-ts/src/utils/write/client.ts b/packages/openapi-ts/src/utils/write/client.ts index 47293e371..1a7ca5143 100644 --- a/packages/openapi-ts/src/utils/write/client.ts +++ b/packages/openapi-ts/src/utils/write/client.ts @@ -11,6 +11,7 @@ import { writeCore } from './core'; import { processIndex } from './index'; import { processSchemas } from './schemas'; import { processServices } from './services'; +import { processResponseTransformers } from './transformers'; import { processTypes } from './types'; /** @@ -69,8 +70,30 @@ export const writeClient = async ( }); } + // schemas await processSchemas({ file: files.schemas, openApi }); + + // types await processTypes({ client, files }); + + // transformers + if ( + files.services && + client.services.length && + config.types.dates === 'types+transform' + ) { + await processResponseTransformers({ + client, + onNode: (node) => { + files.types?.add(node); + }, + onRemoveNode: () => { + files.types?.removeNode(); + }, + }); + } + + // services await processServices({ client, files }); // deprecated files diff --git a/packages/openapi-ts/src/utils/write/services.ts b/packages/openapi-ts/src/utils/write/services.ts index c3824c678..2c669bbfd 100644 --- a/packages/openapi-ts/src/utils/write/services.ts +++ b/packages/openapi-ts/src/utils/write/services.ts @@ -20,7 +20,7 @@ import { getConfig, isStandaloneClient } from '../config'; import { escapeComment, escapeName } from '../escape'; import { transformServiceName } from '../transform'; import { unique } from '../unique'; -import { uniqueTypeName } from './type'; +import { setUniqueTypeName } from './type'; type OnNode = (node: Node) => void; type OnImport = (name: string) => void; @@ -28,8 +28,8 @@ type OnImport = (name: string) => void; const generateImport = ({ meta, onImport, - ...uniqueTypeNameArgs -}: Pick[0], 'client' | 'nameTransformer'> & + ...setUniqueTypeNameArgs +}: Pick[0], 'client' | 'nameTransformer'> & Pick & { onImport: OnImport; }) => { @@ -38,18 +38,25 @@ const generateImport = ({ return; } - const { name } = uniqueTypeName({ meta, ...uniqueTypeNameArgs }); + const { name } = setUniqueTypeName({ meta, ...setUniqueTypeNameArgs }); if (name) { onImport(name); } }; +export const modelResponseTransformerTypeName = (name: string) => + `${name}ModelResponseTransformer`; + export const operationDataTypeName = (name: string) => `${camelcase(name, { pascalCase: true })}Data`; export const operationErrorTypeName = (name: string) => `${camelcase(name, { pascalCase: true })}Error`; +// operation response type ends with "Response", it's enough to append "Transformer" +export const operationResponseTransformerTypeName = (name: string) => + `${name}Transformer`; + export const operationResponseTypeName = (name: string) => `${camelcase(name, { pascalCase: true })}Response`; @@ -59,7 +66,7 @@ const toOperationParamType = ( ): FunctionParameter[] => { const config = getConfig(); - const { name: importedType } = uniqueTypeName({ + const { name: importedType } = setUniqueTypeName({ client, meta: { // TODO: this should be exact ref to operation for consistency, @@ -129,7 +136,7 @@ const toOperationReturnType = (client: Client, operation: Operation) => { // can't remove this logic without removing request/name config // as it complicates things if (operation.results.length) { - const { name: importedType } = uniqueTypeName({ + const { name: importedType } = setUniqueTypeName({ client, meta: { // TODO: this should be exact ref to operation for consistency, @@ -203,13 +210,24 @@ const toOperationComment = (operation: Operation): Comments => { const toRequestOptions = ( client: Client, operation: Operation, + onImport: OnImport, onClientImport: OnImport | undefined, - responseType: string, ) => { const config = getConfig(); - const hasResponseTransformer = client.types[responseType]?.['hasTransformer']; - const responseTransformerName = responseType; + const operationName = operationResponseTypeName(operation.name); + const { name: responseTransformerName } = setUniqueTypeName({ + client, + meta: { + $ref: `transformers/${operationName}`, + name: operationName, + }, + nameTransformer: operationResponseTransformerTypeName, + }); + + if (responseTransformerName) { + onImport(responseTransformerName); + } if (isStandaloneClient(config)) { let obj: ObjectValue[] = [ @@ -247,11 +265,14 @@ const toRequestOptions = ( }, ]; - if (hasResponseTransformer) { - obj.push({ - key: 'responseTransformer', - value: responseTransformerName, - }); + if (responseTransformerName) { + obj = [ + ...obj, + { + key: 'responseTransformer', + value: responseTransformerName, + }, + ]; } return compiler.types.object({ @@ -327,8 +348,8 @@ const toRequestOptions = ( obj.responseHeader = operation.responseHeader; } - if (hasResponseTransformer) { - obj.responseTransformer = `${responseType}`; + if (responseTransformerName) { + obj.responseTransformer = responseTransformerName; } if (operation.errors.length) { @@ -357,32 +378,15 @@ const toRequestOptions = ( const toOperationStatements = ( client: Client, operation: Operation, + onImport: OnImport, onClientImport?: OnImport, ) => { const config = getConfig(); - const responseType = operation.results.length - ? uniqueTypeName({ - client, - meta: { - // TODO: this should be exact ref to operation for consistency, - // but name should work too as operation ID is unique - $ref: operation.name, - name: operation.name, - }, - nameTransformer: operationResponseTypeName, - }).name - : 'void'; - - const options = toRequestOptions( - client, - operation, - onClientImport, - responseType, - ); + const options = toRequestOptions(client, operation, onImport, onClientImport); if (isStandaloneClient(config)) { - const errorType = uniqueTypeName({ + const errorType = setUniqueTypeName({ client, meta: { // TODO: this should be exact ref to operation for consistency, @@ -392,7 +396,18 @@ const toOperationStatements = ( }, nameTransformer: operationErrorTypeName, }).name; - + const responseType = operation.results.length + ? setUniqueTypeName({ + client, + meta: { + // TODO: this should be exact ref to operation for consistency, + // but name should work too as operation ID is unique + $ref: operation.name, + name: operation.name, + }, + nameTransformer: operationResponseTypeName, + }).name + : 'void'; return [ compiler.return.functionCall({ args: [options], @@ -498,7 +513,12 @@ export const processService = ( returnType: isStandalone ? undefined : toOperationReturnType(client, operation), - statements: toOperationStatements(client, operation, onClientImport), + statements: toOperationStatements( + client, + operation, + onImport, + onClientImport, + ), }); const statement = compiler.export.const({ comment: toOperationComment(operation), @@ -522,7 +542,12 @@ export const processService = ( returnType: isStandalone ? undefined : toOperationReturnType(client, operation), - statements: toOperationStatements(client, operation, onClientImport), + statements: toOperationStatements( + client, + operation, + onImport, + onClientImport, + ), }); return node; }); @@ -653,14 +678,11 @@ export const processServices = async ({ // Import all models required by the services. if (files.types && !files.types.isEmpty()) { - const importedTypes = imports.filter(unique).map((name) => { - const hasTransformer = client.types[name]?.hasTransformer === true; - - return { - asType: !hasTransformer, - name, - }; - }); + const importedTypes = imports.filter(unique).map((name) => ({ + // this detection could be done safer, but it shouldn't cause any issues + asType: !name.endsWith('Transformer'), + name, + })); files.services?.addImport(importedTypes, `./${files.types.getName(false)}`); } }; diff --git a/packages/openapi-ts/src/utils/write/transformers.ts b/packages/openapi-ts/src/utils/write/transformers.ts new file mode 100644 index 000000000..de8c57c53 --- /dev/null +++ b/packages/openapi-ts/src/utils/write/transformers.ts @@ -0,0 +1,345 @@ +import ts from 'typescript'; + +import { compiler } from '../../compiler'; +import type { ModelMeta } from '../../openApi/common/interfaces/client'; +import { getSuccessResponses } from '../../openApi/common/parser/operation'; +import { getConfig } from '../config'; +import { + modelResponseTransformerTypeName, + operationResponseTransformerTypeName, + operationResponseTypeName, +} from './services'; +import { unsetUniqueTypeName } from './type'; +import { generateType, type TypesProps } from './types'; + +interface ModelProps extends TypesProps { + path: Array; + meta?: ModelMeta; +} + +const dataVariableName = 'data'; + +const getRefModels = ({ + client, + model, +}: Pick) => { + const refModels = model.$refs.map((ref) => { + const refModel = client.models.find((model) => model.meta?.$ref === ref); + if (!refModel) { + throw new Error( + `Ref ${ref} could not be found. Transformers cannot be generated without having access to all refs.`, + ); + } + return refModel; + }); + return refModels; +}; + +const ensureModelResponseTransformerExists = ({ + client, + model, + onNode, + onRemoveNode, +}: Omit) => { + const modelName = model.meta!.name; + + const { name } = generateType({ + client, + meta: { + $ref: `transformers/${modelName}`, + name: modelName, + }, + nameTransformer: modelResponseTransformerTypeName, + onCreated: (name) => { + const statements = processModel({ + client, + meta: { + $ref: `transformers/${modelName}`, + name, + }, + model, + onNode, + onRemoveNode, + path: [dataVariableName], + }); + generateResponseTransformer({ + client, + name, + onNode, + onRemoveNode, + statements, + }); + }, + onNode, + type: `(${dataVariableName}: any) => ${modelName}`, + }); + + const result = { + created: Boolean(client.types[name]), + name, + }; + return result; +}; + +const processArray = ({ + client, + model, + onNode, + onRemoveNode, + path, +}: ModelProps) => { + const refModels = getRefModels({ client, model }); + + if (refModels.length === 1) { + const { created, name: nameModelResponseTransformer } = + ensureModelResponseTransformerExists({ + client, + model: refModels[0], + onNode, + onRemoveNode, + }); + + if (!created) { + return []; + } + + return [ + compiler.transform.arrayTransformMutation({ + path, + transformerName: nameModelResponseTransformer, + }), + ]; + } + + if (model.format === 'date' || model.format === 'date-time') { + return [ + compiler.transform.mapArray({ + path, + transformExpression: compiler.transform.newDate({ + parameterName: 'item', + }), + }), + ]; + } + + // Not transform for this type + return []; +}; + +const processProperty = ({ + client, + model, + onNode, + onRemoveNode, + path, + meta, +}: ModelProps) => { + const pathProperty = [...path, model.name]; + + if ( + model.type === 'string' && + model.export !== 'array' && + (model.format === 'date-time' || model.format === 'date') + ) { + return [compiler.transform.dateTransformMutation({ path: pathProperty })]; + } + + // otherwise we recurse in case it's an object/array, and if it's not that will just bail with [] + return processModel({ + client, + meta, + model, + onNode, + onRemoveNode, + path: pathProperty, + }); +}; + +const processModel = ({ + client, + model, + meta, + onNode, + onRemoveNode, + path, +}: ModelProps): ts.Statement[] => { + switch (model.export) { + case 'array': { + return processArray({ + client, + meta, + model, + onNode, + onRemoveNode, + path, + }); + } + case 'interface': { + const statements = model.properties.flatMap((property) => + processProperty({ + client, + meta, + model: property, + onNode, + onRemoveNode, + path, + }), + ); + + return statements; + } + case 'reference': { + if (model.$refs.length !== 1) { + return []; + } + const refModels = getRefModels({ client, model }); + + const { created, name: nameModelResponseTransformer } = + ensureModelResponseTransformerExists({ + client, + model: refModels[0], + onNode, + onRemoveNode, + }); + + if (!created) { + return []; + } + + return model.in === 'response' + ? [ + compiler.convert.expressionToStatement({ + expression: compiler.function.call({ + functionName: nameModelResponseTransformer, + parameters: [dataVariableName], + }), + }), + ] + : compiler.transform.transformItem({ + path, + transformerName: nameModelResponseTransformer, + }); + } + default: + // Unsupported + return []; + } +}; + +const generateResponseTransformer = ({ + client, + name, + onNode, + onRemoveNode, + statements, +}: Pick & { + name: string; + statements: Array; +}) => { + const result = { + created: false, + name, + }; + + if (!statements.length) { + // clean up created type for response transformer if it turns out + // the transformer was never generated + unsetUniqueTypeName({ + client, + name, + }); + onRemoveNode?.(); + return result; + } + + const expression = compiler.types.function({ + multiLine: true, + parameters: [ + { + name: dataVariableName, + }, + ], + statements: [ + ...statements, + compiler.return.statement({ + expression: ts.factory.createIdentifier(dataVariableName), + }), + ], + }); + const statement = compiler.export.const({ + expression, + name, + typeName: name, + }); + onNode(statement); + + return { + created: true, + name, + }; +}; + +export const processResponseTransformers = async ({ + client, + onNode, + onRemoveNode, +}: Pick) => { + const config = getConfig(); + + for (const service of client.services) { + for (const operation of service.operations) { + const hasRes = operation.results.length; + + if (!hasRes) { + continue; + } + + const responses = getSuccessResponses(operation.results); + + if (!responses.length) { + continue; + } + + if (responses.length > 1) { + if (config.debug) { + console.warn( + `⚠️ Transformers warning: route ${operation.method} ${operation.path} has ${responses.length} 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`, + ); + } + continue; + } + + const name = operationResponseTypeName(operation.name); + generateType({ + client, + meta: { + $ref: `transformers/${name}`, + name, + }, + nameTransformer: operationResponseTransformerTypeName, + onCreated: (nameCreated) => { + const statements = processModel({ + client, + meta: { + $ref: `transformers/${name}`, + name, + }, + model: operation.results[0], + onNode, + onRemoveNode, + path: [dataVariableName], + }); + generateResponseTransformer({ + client, + name: nameCreated, + onNode, + onRemoveNode, + statements, + }); + }, + onNode, + type: `(${dataVariableName}: any) => ${name}`, + }); + } + } +}; diff --git a/packages/openapi-ts/src/utils/write/transforms.ts b/packages/openapi-ts/src/utils/write/transforms.ts deleted file mode 100644 index 28555c249..000000000 --- a/packages/openapi-ts/src/utils/write/transforms.ts +++ /dev/null @@ -1,137 +0,0 @@ -import ts from 'typescript'; - -import { compiler } from '../../compiler'; -import type { Model } from '../../openApi'; -import type { Client } from '../../types/client'; - -type OnNode = (node: ts.Node) => void; - -export const generateTransform = ( - client: Client, - model: Model, - onNode: OnNode, -) => { - // Ignore if transforms are disabled or the transform has been already created - if (!model.meta || model.meta.hasTransformer) { - return; - } - - const generateForProperty = (rootPath: string[], property: Model) => { - const localPath = [...rootPath, property.name]; - - if ( - property.type === 'string' && - property.export !== 'array' && - (property.format === 'date-time' || property.format === 'date') - ) { - return [ - compiler.transform.dateTransformMutation({ - path: localPath, - }), - ]; - } else { - // otherwise we recurse in case it's an object/array, and if it's not that will just bail with [] - return generateForModel(localPath, property); - } - }; - - const generateForArray = ( - localPath: string[], - localModel: Model, - refModels: Model[], - ) => { - if (localModel.export !== 'array') { - throw new Error( - 'generateForArray should only be called with array models', - ); - } - - if (refModels.length === 1) { - const refModel = refModels[0]; - - if (client.types[refModel.meta!.name].hasTransformer) { - return [ - compiler.transform.arrayTransformMutation({ - path: localPath, - transformer: refModel.meta!.name, - }), - ]; - } else { - // We do not currently support union types for transforms since discriminating them is a challenge - return []; - } - } - - if (localModel.format === 'date' || localModel.format === 'date-time') { - return [ - compiler.transform.mapArray({ - path: localPath, - transformExpression: compiler.transform.newDate({ - parameterName: 'item', - }), - }), - ]; - } - - // Not transform for this type - return []; - }; - - const generateForReference = (localPath: string[], refModels: Model[]) => { - if ( - refModels.length !== 1 || - client.types[refModels[0].meta!.name].hasTransformer !== true - ) { - return []; - } - - return compiler.transform.transformItem({ - path: localPath, - transformer: refModels[0].meta!.name, - }); - }; - - const generateForModel = ( - localPath: string[], - model: Model, - ): ts.Statement[] => { - const refModels = model.$refs.map((ref) => { - const refModel = client.models.find((m) => m.meta?.$ref === ref); - if (!refModel) { - throw new Error( - `Model ${ref} could not be found when building ref transform`, - ); - } - return refModel; - }); - - switch (model.export) { - case 'reference': - return generateForReference(localPath, refModels); - case 'interface': - return model.properties.flatMap((property) => - generateForProperty(localPath, property), - ); - case 'array': - return generateForArray(localPath, model, refModels); - - default: - // Unsupported - return []; - } - }; - - const statements = generateForModel(['data'], model); - if (!statements.length) { - return; - } - - const transformFunction = compiler.transform.transformMutationFunction({ - modelName: model.meta.name, - statements, - }); - - client.types[model.meta.name].hasTransformer = true; - - onNode(transformFunction); -}; diff --git a/packages/openapi-ts/src/utils/write/type.ts b/packages/openapi-ts/src/utils/write/type.ts index 5b94d125a..c00816073 100644 --- a/packages/openapi-ts/src/utils/write/type.ts +++ b/packages/openapi-ts/src/utils/write/type.ts @@ -155,7 +155,7 @@ export const toType = (model: Model): TypeNode => { } }; -interface UniqueTypeNameResult { +interface SetUniqueTypeNameResult { /** * Did this function add a new property to the `client.types` object? */ @@ -176,9 +176,9 @@ interface UniqueTypeNameResult { * value. In different contexts, a different strategy might be used. For * example, slashes `/` are invalid in TypeScript identifiers, but okay in * a JavaScript object key name. - * @returns {UniqueTypeNameResult} + * @returns {SetUniqueTypeNameResult} */ -export const uniqueTypeName = ({ +export const setUniqueTypeName = ({ client, count = 1, create = false, @@ -189,8 +189,8 @@ export const uniqueTypeName = ({ count?: number; create?: boolean; nameTransformer?: (value: string) => string; -}): UniqueTypeNameResult => { - let result: UniqueTypeNameResult = { +}): SetUniqueTypeNameResult => { + let result: SetUniqueTypeNameResult = { created: false, name: '', }; @@ -216,7 +216,7 @@ export const uniqueTypeName = ({ name, }; } else { - result = uniqueTypeName({ + result = setUniqueTypeName({ client, count: count + 1, create, @@ -226,3 +226,36 @@ export const uniqueTypeName = ({ } return result; }; + +interface UnsetUniqueTypeNameResult { + /** + * Did this function delete a property from the `client.types` object? + */ + deleted: boolean; + /** + * Unique name removed from the `client.types` object. + */ + name: string; +} + +export const unsetUniqueTypeName = ({ + client, + name, +}: { + client: Client; + name: string; +}): UnsetUniqueTypeNameResult => { + let result: UnsetUniqueTypeNameResult = { + deleted: false, + name: '', + }; + if (!client.types[name]) { + return result; + } + delete client.types[name]; + result = { + deleted: true, + name, + }; + return result; +}; diff --git a/packages/openapi-ts/src/utils/write/types.ts b/packages/openapi-ts/src/utils/write/types.ts index 813b3b837..41e559c57 100644 --- a/packages/openapi-ts/src/utils/write/types.ts +++ b/packages/openapi-ts/src/utils/write/types.ts @@ -20,13 +20,13 @@ import { operationErrorTypeName, operationResponseTypeName, } from './services'; -import { generateTransform } from './transforms'; -import { toType, uniqueTypeName } from './type'; +import { setUniqueTypeName, toType } from './type'; -interface TypesProps { +export interface TypesProps { client: Client; model: Model; onNode: (node: Node) => void; + onRemoveNode?: VoidFunction; } const serviceExportedNamespace = () => '$OpenApiTs'; @@ -57,9 +57,9 @@ const generateEnum = ({ meta, obj, onNode, - ...uniqueTypeNameArgs + ...setUniqueTypeNameArgs }: Omit[0], 'name'> & - Pick[0], 'client' | 'nameTransformer'> & + Pick[0], 'client' | 'nameTransformer'> & Pick & Pick) => { // generate types only for top-level models @@ -67,10 +67,10 @@ const generateEnum = ({ return; } - const { created, name } = uniqueTypeName({ + const { created, name } = setUniqueTypeName({ create: true, meta, - ...uniqueTypeNameArgs, + ...setUniqueTypeNameArgs, }); if (created) { const node = compiler.types.enum({ @@ -83,35 +83,40 @@ const generateEnum = ({ } }; -const generateType = ({ +export const generateType = ({ comment, meta, onCreated, onNode, type, - ...uniqueTypeNameArgs + ...setUniqueTypeNameArgs }: Omit[0], 'name'> & - Pick[0], 'client' | 'nameTransformer'> & + Pick[0], 'client' | 'nameTransformer'> & Pick & Pick & { onCreated?: (name: string) => void; }) => { // generate types only for top-level models if (!meta) { - return; + return { + created: false, + name: '', + }; } - const { created, name } = uniqueTypeName({ + const result = setUniqueTypeName({ create: true, meta, - ...uniqueTypeNameArgs, + ...setUniqueTypeNameArgs, }); + const { created, name } = result; if (created) { const node = compiler.typedef.alias({ comment, name, type }); onNode(node); onCreated?.(name); } + return result; }; const processComposition = (props: TypesProps) => { @@ -228,14 +233,14 @@ const processServiceTypes = ({ const isStandalone = isStandaloneClient(config); - client.services.forEach((service) => { - service.operations.forEach((operation) => { + for (const service of client.services) { + for (const operation of service.operations) { const hasReq = operation.parameters.length; const hasRes = operation.results.length; const hasErr = operation.errors.length; if (!hasReq && !hasRes && !hasErr) { - return; + continue; } if (!pathsMap[operation.path]) { @@ -342,7 +347,7 @@ const processServiceTypes = ({ } if (Array.isArray(methodMap.res)) { - return; + continue; } operation.results.forEach((result) => { @@ -370,41 +375,6 @@ const processServiceTypes = ({ }), }); - if (config.types.dates === 'types+transform') { - if (responses.length === 1) { - const response = responses[0]; - - if (client.types[response.type]?.hasTransformer) { - const name = operationResponseTypeName(operation.name); - - if (response.export === 'array') { - const arrayTransformer = - compiler.transform.responseArrayTransform({ - name, - transform: response.type, - }); - onNode(arrayTransformer); - } else { - const transformAlias = compiler.transform.alias({ - existingName: response.type, - name, - }); - - onNode(transformAlias); - } - - client.types[name].hasTransformer = true; - } - } else if (responses.length > 1) { - console.log( - '❗ Route', - operation.method, - operation.path, - 'has more than 1 success response and will not currently have a transform generated. If you have a use case for this please open an issue https://github.com/hey-api/openapi-ts/issues', - ); - } - } - if (isStandaloneClient(config)) { const errorResults = getErrorResponses(operation.errors); // create type export for operation error @@ -443,15 +413,15 @@ const processServiceTypes = ({ } if (Array.isArray(methodMap.res)) { - return; + continue; } operation.errors.forEach((error) => { methodMap.res![error.code] = error; }); } - }); - }); + } + } const properties = Object.entries(pathsMap).map(([path, pathMap]) => { const pathParameters = Object.entries(pathMap) @@ -462,7 +432,7 @@ const processServiceTypes = ({ if (methodMap.req) { const operationName = methodMap.$ref!; - const { name: base } = uniqueTypeName({ + const { name: base } = setUniqueTypeName({ client, meta: { // TODO: this should be exact ref to operation for consistency, @@ -546,34 +516,15 @@ export const processTypes = async ({ client: Client; files: Record; }): Promise => { - const config = getConfig(); + const onNode: TypesProps['onNode'] = (node) => { + files.types?.add(node); + }; for (const model of client.models) { - processModel({ - client, - model, - onNode: (node) => { - files.types?.add(node); - }, - }); + processModel({ client, model, onNode }); } if (files.services && client.services.length) { - processServiceTypes({ - client, - onNode: (node) => { - files.types?.add(node); - }, - }); - } - - if (config.types.dates === 'types+transform') { - for (const model of client.models) { - if (model.export !== 'enum') { - generateTransform(client, model, (node) => { - files.types?.add(node); - }); - } - } + processServiceTypes({ client, onNode }); } }; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/services.gen.ts.snap index 1a7f70831..17a6d508a 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/services.gen.ts.snap @@ -5,7 +5,7 @@ import { HttpClient } from '@angular/common/http'; import type { Observable } from 'rxjs'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import { ModelWithDatesResponse, ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse } from './types.gen'; +import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse, ParentModelWithDatesResponseTransformer, ModelWithDatesResponseTransformer, ModelWithDatesArrayResponseTransformer } from './types.gen'; @Injectable({ providedIn: 'root' @@ -13,6 +13,18 @@ import { ModelWithDatesResponse, ModelWithDatesArrayResponse, type ArrayOfDatesR export class DefaultService { constructor(public readonly http: HttpClient) { } + /** + * @returns ParentModelWithDates Success + * @throws ApiError + */ + public parentModelWithDates(): Observable { + return __request(OpenAPI, this.http, { + method: 'POST', + url: '/api/model-with-dates', + responseTransformer: ParentModelWithDatesResponseTransformer + }); + } + /** * @returns ModelWithDates Success * @throws ApiError @@ -21,7 +33,7 @@ export class DefaultService { return __request(OpenAPI, this.http, { method: 'PUT', url: '/api/model-with-dates', - responseTransformer: ModelWithDatesResponse + responseTransformer: ModelWithDatesResponseTransformer }); } @@ -33,7 +45,7 @@ export class DefaultService { return __request(OpenAPI, this.http, { method: 'PUT', url: '/api/model-with-dates-array', - responseTransformer: ModelWithDatesArrayResponse + responseTransformer: ModelWithDatesArrayResponseTransformer }); } diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/types.gen.ts.snap index 0e3adfd30..dbd786b11 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/types.gen.ts.snap @@ -20,16 +20,6 @@ export type ModelWithDates = { readonly expires?: Date; }; -export function ModelWithDates(data: any): ModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (data?.expires) { - data.expires = new Date(data.expires); - } - return data; -} - /** * This is a model that contains a some dates and arrays */ @@ -44,35 +34,12 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export function ParentModelWithDates(data: any): ParentModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (Array.isArray(data?.items)) { - data.items.forEach(ModelWithDates); - } - if (data?.item) { - ModelWithDates(data.item); - } - if (Array.isArray(data?.dates)) { - data.dates = data.dates.map(item => new Date(item)); - } - return data; -} +export type ParentModelWithDatesResponse = ParentModelWithDates; export type ModelWithDatesResponse = ModelWithDates; -export const ModelWithDatesResponse = ModelWithDates; - export type ModelWithDatesArrayResponse = Array; -export const ModelWithDatesArrayResponse = (data: any) => { - if (Array.isArray(data)) { - data.forEach(ModelWithDates); - } - return data; -}; - export type ArrayOfDatesResponse = Array<(Date)>; export type DateResponse = Date; @@ -81,6 +48,14 @@ export type MultipleResponsesResponse = Array | Array ParentModelWithDatesResponse; + +export type ParentModelWithDatesModelResponseTransformer = (data: any) => ParentModelWithDates; + +export type ModelWithDatesModelResponseTransformer = (data: any) => ModelWithDates; + +export const ModelWithDatesModelResponseTransformer: ModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (data?.expires) { + data.expires = new Date(data.expires); + } + return data; +}; + +export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (Array.isArray(data?.items)) { + data.items.forEach(ModelWithDatesModelResponseTransformer); + } + if (data?.item) { + ModelWithDatesModelResponseTransformer(data.item); + } + if (Array.isArray(data?.dates)) { + data.dates = data.dates.map(item => new Date(item)); + } + return data; +}; + +export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { + ParentModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesResponseTransformer = (data: any) => ModelWithDatesResponse; + +export const ModelWithDatesResponseTransformer: ModelWithDatesResponseTransformer = data => { + ModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesArrayResponseTransformer = (data: any) => ModelWithDatesArrayResponse; + +export const ModelWithDatesArrayResponseTransformer: ModelWithDatesArrayResponseTransformer = data => { + if (Array.isArray(data)) { + data.forEach(ModelWithDatesModelResponseTransformer); + } + return data; }; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/services.gen.ts.snap index 00782ecb9..7a30623b5 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/services.gen.ts.snap @@ -3,9 +3,21 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import { ModelWithDatesResponse, ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse } from './types.gen'; +import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse, ParentModelWithDatesResponseTransformer, ModelWithDatesResponseTransformer, ModelWithDatesArrayResponseTransformer } from './types.gen'; export class DefaultService { + /** + * @returns ParentModelWithDates Success + * @throws ApiError + */ + public static parentModelWithDates(): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/model-with-dates', + responseTransformer: ParentModelWithDatesResponseTransformer + }); + } + /** * @returns ModelWithDates Success * @throws ApiError @@ -14,7 +26,7 @@ export class DefaultService { return __request(OpenAPI, { method: 'PUT', url: '/api/model-with-dates', - responseTransformer: ModelWithDatesResponse + responseTransformer: ModelWithDatesResponseTransformer }); } @@ -26,7 +38,7 @@ export class DefaultService { return __request(OpenAPI, { method: 'PUT', url: '/api/model-with-dates-array', - responseTransformer: ModelWithDatesArrayResponse + responseTransformer: ModelWithDatesArrayResponseTransformer }); } diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/types.gen.ts.snap index 0e3adfd30..dbd786b11 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/types.gen.ts.snap @@ -20,16 +20,6 @@ export type ModelWithDates = { readonly expires?: Date; }; -export function ModelWithDates(data: any): ModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (data?.expires) { - data.expires = new Date(data.expires); - } - return data; -} - /** * This is a model that contains a some dates and arrays */ @@ -44,35 +34,12 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export function ParentModelWithDates(data: any): ParentModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (Array.isArray(data?.items)) { - data.items.forEach(ModelWithDates); - } - if (data?.item) { - ModelWithDates(data.item); - } - if (Array.isArray(data?.dates)) { - data.dates = data.dates.map(item => new Date(item)); - } - return data; -} +export type ParentModelWithDatesResponse = ParentModelWithDates; export type ModelWithDatesResponse = ModelWithDates; -export const ModelWithDatesResponse = ModelWithDates; - export type ModelWithDatesArrayResponse = Array; -export const ModelWithDatesArrayResponse = (data: any) => { - if (Array.isArray(data)) { - data.forEach(ModelWithDates); - } - return data; -}; - export type ArrayOfDatesResponse = Array<(Date)>; export type DateResponse = Date; @@ -81,6 +48,14 @@ export type MultipleResponsesResponse = Array | Array ParentModelWithDatesResponse; + +export type ParentModelWithDatesModelResponseTransformer = (data: any) => ParentModelWithDates; + +export type ModelWithDatesModelResponseTransformer = (data: any) => ModelWithDates; + +export const ModelWithDatesModelResponseTransformer: ModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (data?.expires) { + data.expires = new Date(data.expires); + } + return data; +}; + +export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (Array.isArray(data?.items)) { + data.items.forEach(ModelWithDatesModelResponseTransformer); + } + if (data?.item) { + ModelWithDatesModelResponseTransformer(data.item); + } + if (Array.isArray(data?.dates)) { + data.dates = data.dates.map(item => new Date(item)); + } + return data; +}; + +export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { + ParentModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesResponseTransformer = (data: any) => ModelWithDatesResponse; + +export const ModelWithDatesResponseTransformer: ModelWithDatesResponseTransformer = data => { + ModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesArrayResponseTransformer = (data: any) => ModelWithDatesArrayResponse; + +export const ModelWithDatesArrayResponseTransformer: ModelWithDatesArrayResponseTransformer = data => { + if (Array.isArray(data)) { + data.forEach(ModelWithDatesModelResponseTransformer); + } + return data; }; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/services.gen.ts.snap index d3322c02f..7d449f2b0 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/services.gen.ts.snap @@ -2,11 +2,23 @@ import type { CancelablePromise } from './core/CancelablePromise'; import type { BaseHttpRequest } from './core/BaseHttpRequest'; -import { ModelWithDatesResponse, ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse } from './types.gen'; +import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse, ParentModelWithDatesResponseTransformer, ModelWithDatesResponseTransformer, ModelWithDatesArrayResponseTransformer } from './types.gen'; export class DefaultService { constructor(public readonly httpRequest: BaseHttpRequest) { } + /** + * @returns ParentModelWithDates Success + * @throws ApiError + */ + public parentModelWithDates(): CancelablePromise { + return this.httpRequest.request({ + method: 'POST', + url: '/api/model-with-dates', + responseTransformer: ParentModelWithDatesResponseTransformer + }); + } + /** * @returns ModelWithDates Success * @throws ApiError @@ -15,7 +27,7 @@ export class DefaultService { return this.httpRequest.request({ method: 'PUT', url: '/api/model-with-dates', - responseTransformer: ModelWithDatesResponse + responseTransformer: ModelWithDatesResponseTransformer }); } @@ -27,7 +39,7 @@ export class DefaultService { return this.httpRequest.request({ method: 'PUT', url: '/api/model-with-dates-array', - responseTransformer: ModelWithDatesArrayResponse + responseTransformer: ModelWithDatesArrayResponseTransformer }); } diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/types.gen.ts.snap index 0e3adfd30..dbd786b11 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/types.gen.ts.snap @@ -20,16 +20,6 @@ export type ModelWithDates = { readonly expires?: Date; }; -export function ModelWithDates(data: any): ModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (data?.expires) { - data.expires = new Date(data.expires); - } - return data; -} - /** * This is a model that contains a some dates and arrays */ @@ -44,35 +34,12 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export function ParentModelWithDates(data: any): ParentModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (Array.isArray(data?.items)) { - data.items.forEach(ModelWithDates); - } - if (data?.item) { - ModelWithDates(data.item); - } - if (Array.isArray(data?.dates)) { - data.dates = data.dates.map(item => new Date(item)); - } - return data; -} +export type ParentModelWithDatesResponse = ParentModelWithDates; export type ModelWithDatesResponse = ModelWithDates; -export const ModelWithDatesResponse = ModelWithDates; - export type ModelWithDatesArrayResponse = Array; -export const ModelWithDatesArrayResponse = (data: any) => { - if (Array.isArray(data)) { - data.forEach(ModelWithDates); - } - return data; -}; - export type ArrayOfDatesResponse = Array<(Date)>; export type DateResponse = Date; @@ -81,6 +48,14 @@ export type MultipleResponsesResponse = Array | Array ParentModelWithDatesResponse; + +export type ParentModelWithDatesModelResponseTransformer = (data: any) => ParentModelWithDates; + +export type ModelWithDatesModelResponseTransformer = (data: any) => ModelWithDates; + +export const ModelWithDatesModelResponseTransformer: ModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (data?.expires) { + data.expires = new Date(data.expires); + } + return data; +}; + +export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (Array.isArray(data?.items)) { + data.items.forEach(ModelWithDatesModelResponseTransformer); + } + if (data?.item) { + ModelWithDatesModelResponseTransformer(data.item); + } + if (Array.isArray(data?.dates)) { + data.dates = data.dates.map(item => new Date(item)); + } + return data; +}; + +export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { + ParentModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesResponseTransformer = (data: any) => ModelWithDatesResponse; + +export const ModelWithDatesResponseTransformer: ModelWithDatesResponseTransformer = data => { + ModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesArrayResponseTransformer = (data: any) => ModelWithDatesArrayResponse; + +export const ModelWithDatesArrayResponseTransformer: ModelWithDatesArrayResponseTransformer = data => { + if (Array.isArray(data)) { + data.forEach(ModelWithDatesModelResponseTransformer); + } + return data; }; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/services.gen.ts.snap index 133ddaf4e..b559568d6 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/services.gen.ts.snap @@ -1,18 +1,24 @@ // This file is auto-generated by @hey-api/openapi-ts import { client, type Options } from '@hey-api/client-axios'; -import { type ModelWithDatesError, ModelWithDatesResponse, type ModelWithDatesArrayError, ModelWithDatesArrayResponse, type ArrayOfDatesError, type ArrayOfDatesResponse, type DateError, type DateResponse, type MultipleResponsesError, type MultipleResponsesResponse } from './types.gen'; +import { type ParentModelWithDatesError, type ParentModelWithDatesResponse, type ModelWithDatesError, type ModelWithDatesResponse, type ModelWithDatesArrayError, type ModelWithDatesArrayResponse, type ArrayOfDatesError, type ArrayOfDatesResponse, type DateError, type DateResponse, type MultipleResponsesError, type MultipleResponsesResponse, ParentModelWithDatesResponseTransformer, ModelWithDatesResponseTransformer, ModelWithDatesArrayResponseTransformer } from './types.gen'; + +export const parentModelWithDates = (options?: Options) => { return (options?.client ?? client).post({ + ...options, + url: '/api/model-with-dates', + responseTransformer: ParentModelWithDatesResponseTransformer +}); }; export const modelWithDates = (options?: Options) => { return (options?.client ?? client).put({ ...options, url: '/api/model-with-dates', - responseTransformer: ModelWithDatesResponse + responseTransformer: ModelWithDatesResponseTransformer }); }; export const modelWithDatesArray = (options?: Options) => { return (options?.client ?? client).put({ ...options, url: '/api/model-with-dates-array', - responseTransformer: ModelWithDatesArrayResponse + responseTransformer: ModelWithDatesArrayResponseTransformer }); }; export const arrayOfDates = (options?: Options) => { return (options?.client ?? client).put({ diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/types.gen.ts.snap index e936a61ff..b1e66047f 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/types.gen.ts.snap @@ -20,16 +20,6 @@ export type ModelWithDates = { readonly expires?: Date; }; -export function ModelWithDates(data: any): ModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (data?.expires) { - data.expires = new Date(data.expires); - } - return data; -} - /** * This is a model that contains a some dates and arrays */ @@ -44,37 +34,16 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export function ParentModelWithDates(data: any): ParentModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (Array.isArray(data?.items)) { - data.items.forEach(ModelWithDates); - } - if (data?.item) { - ModelWithDates(data.item); - } - if (Array.isArray(data?.dates)) { - data.dates = data.dates.map(item => new Date(item)); - } - return data; -} +export type ParentModelWithDatesResponse = ParentModelWithDates; -export type ModelWithDatesResponse = ModelWithDates; +export type ParentModelWithDatesError = unknown; -export const ModelWithDatesResponse = ModelWithDates; +export type ModelWithDatesResponse = ModelWithDates; export type ModelWithDatesError = unknown; export type ModelWithDatesArrayResponse = Array; -export const ModelWithDatesArrayResponse = (data: any) => { - if (Array.isArray(data)) { - data.forEach(ModelWithDates); - } - return data; -}; - export type ModelWithDatesArrayError = unknown; export type ArrayOfDatesResponse = Array<(Date)>; @@ -91,6 +60,14 @@ export type MultipleResponsesError = unknown; export type $OpenApiTs = { '/api/model-with-dates': { + post: { + res: { + /** + * Success + */ + '200': ParentModelWithDates; + }; + }; put: { res: { /** @@ -144,4 +121,57 @@ export type $OpenApiTs = { }; }; }; +}; + +export type ParentModelWithDatesResponseTransformer = (data: any) => ParentModelWithDatesResponse; + +export type ParentModelWithDatesModelResponseTransformer = (data: any) => ParentModelWithDates; + +export type ModelWithDatesModelResponseTransformer = (data: any) => ModelWithDates; + +export const ModelWithDatesModelResponseTransformer: ModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (data?.expires) { + data.expires = new Date(data.expires); + } + return data; +}; + +export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (Array.isArray(data?.items)) { + data.items.forEach(ModelWithDatesModelResponseTransformer); + } + if (data?.item) { + ModelWithDatesModelResponseTransformer(data.item); + } + if (Array.isArray(data?.dates)) { + data.dates = data.dates.map(item => new Date(item)); + } + return data; +}; + +export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { + ParentModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesResponseTransformer = (data: any) => ModelWithDatesResponse; + +export const ModelWithDatesResponseTransformer: ModelWithDatesResponseTransformer = data => { + ModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesArrayResponseTransformer = (data: any) => ModelWithDatesArrayResponse; + +export const ModelWithDatesArrayResponseTransformer: ModelWithDatesArrayResponseTransformer = data => { + if (Array.isArray(data)) { + data.forEach(ModelWithDatesModelResponseTransformer); + } + return data; }; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/services.gen.ts.snap index 96266f9c9..44410306c 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/services.gen.ts.snap @@ -1,18 +1,24 @@ // This file is auto-generated by @hey-api/openapi-ts import { client, type Options } from '@hey-api/client-fetch'; -import { type ModelWithDatesError, ModelWithDatesResponse, type ModelWithDatesArrayError, ModelWithDatesArrayResponse, type ArrayOfDatesError, type ArrayOfDatesResponse, type DateError, type DateResponse, type MultipleResponsesError, type MultipleResponsesResponse } from './types.gen'; +import { type ParentModelWithDatesError, type ParentModelWithDatesResponse, type ModelWithDatesError, type ModelWithDatesResponse, type ModelWithDatesArrayError, type ModelWithDatesArrayResponse, type ArrayOfDatesError, type ArrayOfDatesResponse, type DateError, type DateResponse, type MultipleResponsesError, type MultipleResponsesResponse, ParentModelWithDatesResponseTransformer, ModelWithDatesResponseTransformer, ModelWithDatesArrayResponseTransformer } from './types.gen'; + +export const parentModelWithDates = (options?: Options) => { return (options?.client ?? client).post({ + ...options, + url: '/api/model-with-dates', + responseTransformer: ParentModelWithDatesResponseTransformer +}); }; export const modelWithDates = (options?: Options) => { return (options?.client ?? client).put({ ...options, url: '/api/model-with-dates', - responseTransformer: ModelWithDatesResponse + responseTransformer: ModelWithDatesResponseTransformer }); }; export const modelWithDatesArray = (options?: Options) => { return (options?.client ?? client).put({ ...options, url: '/api/model-with-dates-array', - responseTransformer: ModelWithDatesArrayResponse + responseTransformer: ModelWithDatesArrayResponseTransformer }); }; export const arrayOfDates = (options?: Options) => { return (options?.client ?? client).put({ diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/types.gen.ts.snap index e936a61ff..b1e66047f 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/types.gen.ts.snap @@ -20,16 +20,6 @@ export type ModelWithDates = { readonly expires?: Date; }; -export function ModelWithDates(data: any): ModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (data?.expires) { - data.expires = new Date(data.expires); - } - return data; -} - /** * This is a model that contains a some dates and arrays */ @@ -44,37 +34,16 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export function ParentModelWithDates(data: any): ParentModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (Array.isArray(data?.items)) { - data.items.forEach(ModelWithDates); - } - if (data?.item) { - ModelWithDates(data.item); - } - if (Array.isArray(data?.dates)) { - data.dates = data.dates.map(item => new Date(item)); - } - return data; -} +export type ParentModelWithDatesResponse = ParentModelWithDates; -export type ModelWithDatesResponse = ModelWithDates; +export type ParentModelWithDatesError = unknown; -export const ModelWithDatesResponse = ModelWithDates; +export type ModelWithDatesResponse = ModelWithDates; export type ModelWithDatesError = unknown; export type ModelWithDatesArrayResponse = Array; -export const ModelWithDatesArrayResponse = (data: any) => { - if (Array.isArray(data)) { - data.forEach(ModelWithDates); - } - return data; -}; - export type ModelWithDatesArrayError = unknown; export type ArrayOfDatesResponse = Array<(Date)>; @@ -91,6 +60,14 @@ export type MultipleResponsesError = unknown; export type $OpenApiTs = { '/api/model-with-dates': { + post: { + res: { + /** + * Success + */ + '200': ParentModelWithDates; + }; + }; put: { res: { /** @@ -144,4 +121,57 @@ export type $OpenApiTs = { }; }; }; +}; + +export type ParentModelWithDatesResponseTransformer = (data: any) => ParentModelWithDatesResponse; + +export type ParentModelWithDatesModelResponseTransformer = (data: any) => ParentModelWithDates; + +export type ModelWithDatesModelResponseTransformer = (data: any) => ModelWithDates; + +export const ModelWithDatesModelResponseTransformer: ModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (data?.expires) { + data.expires = new Date(data.expires); + } + return data; +}; + +export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (Array.isArray(data?.items)) { + data.items.forEach(ModelWithDatesModelResponseTransformer); + } + if (data?.item) { + ModelWithDatesModelResponseTransformer(data.item); + } + if (Array.isArray(data?.dates)) { + data.dates = data.dates.map(item => new Date(item)); + } + return data; +}; + +export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { + ParentModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesResponseTransformer = (data: any) => ModelWithDatesResponse; + +export const ModelWithDatesResponseTransformer: ModelWithDatesResponseTransformer = data => { + ModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesArrayResponseTransformer = (data: any) => ModelWithDatesArrayResponse; + +export const ModelWithDatesArrayResponseTransformer: ModelWithDatesArrayResponseTransformer = data => { + if (Array.isArray(data)) { + data.forEach(ModelWithDatesModelResponseTransformer); + } + return data; }; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/services.gen.ts.snap index 00782ecb9..7a30623b5 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/services.gen.ts.snap @@ -3,9 +3,21 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import { ModelWithDatesResponse, ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse } from './types.gen'; +import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse, ParentModelWithDatesResponseTransformer, ModelWithDatesResponseTransformer, ModelWithDatesArrayResponseTransformer } from './types.gen'; export class DefaultService { + /** + * @returns ParentModelWithDates Success + * @throws ApiError + */ + public static parentModelWithDates(): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/model-with-dates', + responseTransformer: ParentModelWithDatesResponseTransformer + }); + } + /** * @returns ModelWithDates Success * @throws ApiError @@ -14,7 +26,7 @@ export class DefaultService { return __request(OpenAPI, { method: 'PUT', url: '/api/model-with-dates', - responseTransformer: ModelWithDatesResponse + responseTransformer: ModelWithDatesResponseTransformer }); } @@ -26,7 +38,7 @@ export class DefaultService { return __request(OpenAPI, { method: 'PUT', url: '/api/model-with-dates-array', - responseTransformer: ModelWithDatesArrayResponse + responseTransformer: ModelWithDatesArrayResponseTransformer }); } diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/types.gen.ts.snap index 0e3adfd30..dbd786b11 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/types.gen.ts.snap @@ -20,16 +20,6 @@ export type ModelWithDates = { readonly expires?: Date; }; -export function ModelWithDates(data: any): ModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (data?.expires) { - data.expires = new Date(data.expires); - } - return data; -} - /** * This is a model that contains a some dates and arrays */ @@ -44,35 +34,12 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export function ParentModelWithDates(data: any): ParentModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (Array.isArray(data?.items)) { - data.items.forEach(ModelWithDates); - } - if (data?.item) { - ModelWithDates(data.item); - } - if (Array.isArray(data?.dates)) { - data.dates = data.dates.map(item => new Date(item)); - } - return data; -} +export type ParentModelWithDatesResponse = ParentModelWithDates; export type ModelWithDatesResponse = ModelWithDates; -export const ModelWithDatesResponse = ModelWithDates; - export type ModelWithDatesArrayResponse = Array; -export const ModelWithDatesArrayResponse = (data: any) => { - if (Array.isArray(data)) { - data.forEach(ModelWithDates); - } - return data; -}; - export type ArrayOfDatesResponse = Array<(Date)>; export type DateResponse = Date; @@ -81,6 +48,14 @@ export type MultipleResponsesResponse = Array | Array ParentModelWithDatesResponse; + +export type ParentModelWithDatesModelResponseTransformer = (data: any) => ParentModelWithDates; + +export type ModelWithDatesModelResponseTransformer = (data: any) => ModelWithDates; + +export const ModelWithDatesModelResponseTransformer: ModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (data?.expires) { + data.expires = new Date(data.expires); + } + return data; +}; + +export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (Array.isArray(data?.items)) { + data.items.forEach(ModelWithDatesModelResponseTransformer); + } + if (data?.item) { + ModelWithDatesModelResponseTransformer(data.item); + } + if (Array.isArray(data?.dates)) { + data.dates = data.dates.map(item => new Date(item)); + } + return data; +}; + +export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { + ParentModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesResponseTransformer = (data: any) => ModelWithDatesResponse; + +export const ModelWithDatesResponseTransformer: ModelWithDatesResponseTransformer = data => { + ModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesArrayResponseTransformer = (data: any) => ModelWithDatesArrayResponse; + +export const ModelWithDatesArrayResponseTransformer: ModelWithDatesArrayResponseTransformer = data => { + if (Array.isArray(data)) { + data.forEach(ModelWithDatesModelResponseTransformer); + } + return data; }; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/services.gen.ts.snap index 00782ecb9..7a30623b5 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/services.gen.ts.snap @@ -3,9 +3,21 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import { ModelWithDatesResponse, ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse } from './types.gen'; +import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse, ParentModelWithDatesResponseTransformer, ModelWithDatesResponseTransformer, ModelWithDatesArrayResponseTransformer } from './types.gen'; export class DefaultService { + /** + * @returns ParentModelWithDates Success + * @throws ApiError + */ + public static parentModelWithDates(): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/model-with-dates', + responseTransformer: ParentModelWithDatesResponseTransformer + }); + } + /** * @returns ModelWithDates Success * @throws ApiError @@ -14,7 +26,7 @@ export class DefaultService { return __request(OpenAPI, { method: 'PUT', url: '/api/model-with-dates', - responseTransformer: ModelWithDatesResponse + responseTransformer: ModelWithDatesResponseTransformer }); } @@ -26,7 +38,7 @@ export class DefaultService { return __request(OpenAPI, { method: 'PUT', url: '/api/model-with-dates-array', - responseTransformer: ModelWithDatesArrayResponse + responseTransformer: ModelWithDatesArrayResponseTransformer }); } diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/types.gen.ts.snap index 0e3adfd30..dbd786b11 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/types.gen.ts.snap @@ -20,16 +20,6 @@ export type ModelWithDates = { readonly expires?: Date; }; -export function ModelWithDates(data: any): ModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (data?.expires) { - data.expires = new Date(data.expires); - } - return data; -} - /** * This is a model that contains a some dates and arrays */ @@ -44,35 +34,12 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export function ParentModelWithDates(data: any): ParentModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (Array.isArray(data?.items)) { - data.items.forEach(ModelWithDates); - } - if (data?.item) { - ModelWithDates(data.item); - } - if (Array.isArray(data?.dates)) { - data.dates = data.dates.map(item => new Date(item)); - } - return data; -} +export type ParentModelWithDatesResponse = ParentModelWithDates; export type ModelWithDatesResponse = ModelWithDates; -export const ModelWithDatesResponse = ModelWithDates; - export type ModelWithDatesArrayResponse = Array; -export const ModelWithDatesArrayResponse = (data: any) => { - if (Array.isArray(data)) { - data.forEach(ModelWithDates); - } - return data; -}; - export type ArrayOfDatesResponse = Array<(Date)>; export type DateResponse = Date; @@ -81,6 +48,14 @@ export type MultipleResponsesResponse = Array | Array ParentModelWithDatesResponse; + +export type ParentModelWithDatesModelResponseTransformer = (data: any) => ParentModelWithDates; + +export type ModelWithDatesModelResponseTransformer = (data: any) => ModelWithDates; + +export const ModelWithDatesModelResponseTransformer: ModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (data?.expires) { + data.expires = new Date(data.expires); + } + return data; +}; + +export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (Array.isArray(data?.items)) { + data.items.forEach(ModelWithDatesModelResponseTransformer); + } + if (data?.item) { + ModelWithDatesModelResponseTransformer(data.item); + } + if (Array.isArray(data?.dates)) { + data.dates = data.dates.map(item => new Date(item)); + } + return data; +}; + +export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { + ParentModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesResponseTransformer = (data: any) => ModelWithDatesResponse; + +export const ModelWithDatesResponseTransformer: ModelWithDatesResponseTransformer = data => { + ModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesArrayResponseTransformer = (data: any) => ModelWithDatesArrayResponse; + +export const ModelWithDatesArrayResponseTransformer: ModelWithDatesArrayResponseTransformer = data => { + if (Array.isArray(data)) { + data.forEach(ModelWithDatesModelResponseTransformer); + } + return data; }; \ No newline at end of file diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/services.gen.ts.snap index 00782ecb9..7a30623b5 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/services.gen.ts.snap @@ -3,9 +3,21 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import { ModelWithDatesResponse, ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse } from './types.gen'; +import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type ModelWithDatesArrayResponse, type ArrayOfDatesResponse, type DateResponse, type MultipleResponsesResponse, ParentModelWithDatesResponseTransformer, ModelWithDatesResponseTransformer, ModelWithDatesArrayResponseTransformer } from './types.gen'; export class DefaultService { + /** + * @returns ParentModelWithDates Success + * @throws ApiError + */ + public static parentModelWithDates(): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/model-with-dates', + responseTransformer: ParentModelWithDatesResponseTransformer + }); + } + /** * @returns ModelWithDates Success * @throws ApiError @@ -14,7 +26,7 @@ export class DefaultService { return __request(OpenAPI, { method: 'PUT', url: '/api/model-with-dates', - responseTransformer: ModelWithDatesResponse + responseTransformer: ModelWithDatesResponseTransformer }); } @@ -26,7 +38,7 @@ export class DefaultService { return __request(OpenAPI, { method: 'PUT', url: '/api/model-with-dates-array', - responseTransformer: ModelWithDatesArrayResponse + responseTransformer: ModelWithDatesArrayResponseTransformer }); } diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/types.gen.ts.snap index 0e3adfd30..dbd786b11 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/types.gen.ts.snap @@ -20,16 +20,6 @@ export type ModelWithDates = { readonly expires?: Date; }; -export function ModelWithDates(data: any): ModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (data?.expires) { - data.expires = new Date(data.expires); - } - return data; -} - /** * This is a model that contains a some dates and arrays */ @@ -44,35 +34,12 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export function ParentModelWithDates(data: any): ParentModelWithDates { - if (data?.modified) { - data.modified = new Date(data.modified); - } - if (Array.isArray(data?.items)) { - data.items.forEach(ModelWithDates); - } - if (data?.item) { - ModelWithDates(data.item); - } - if (Array.isArray(data?.dates)) { - data.dates = data.dates.map(item => new Date(item)); - } - return data; -} +export type ParentModelWithDatesResponse = ParentModelWithDates; export type ModelWithDatesResponse = ModelWithDates; -export const ModelWithDatesResponse = ModelWithDates; - export type ModelWithDatesArrayResponse = Array; -export const ModelWithDatesArrayResponse = (data: any) => { - if (Array.isArray(data)) { - data.forEach(ModelWithDates); - } - return data; -}; - export type ArrayOfDatesResponse = Array<(Date)>; export type DateResponse = Date; @@ -81,6 +48,14 @@ export type MultipleResponsesResponse = Array | Array ParentModelWithDatesResponse; + +export type ParentModelWithDatesModelResponseTransformer = (data: any) => ParentModelWithDates; + +export type ModelWithDatesModelResponseTransformer = (data: any) => ModelWithDates; + +export const ModelWithDatesModelResponseTransformer: ModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (data?.expires) { + data.expires = new Date(data.expires); + } + return data; +}; + +export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesModelResponseTransformer = data => { + if (data?.modified) { + data.modified = new Date(data.modified); + } + if (Array.isArray(data?.items)) { + data.items.forEach(ModelWithDatesModelResponseTransformer); + } + if (data?.item) { + ModelWithDatesModelResponseTransformer(data.item); + } + if (Array.isArray(data?.dates)) { + data.dates = data.dates.map(item => new Date(item)); + } + return data; +}; + +export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { + ParentModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesResponseTransformer = (data: any) => ModelWithDatesResponse; + +export const ModelWithDatesResponseTransformer: ModelWithDatesResponseTransformer = data => { + ModelWithDatesModelResponseTransformer(data); + return data; +}; + +export type ModelWithDatesArrayResponseTransformer = (data: any) => ModelWithDatesArrayResponse; + +export const ModelWithDatesArrayResponseTransformer: ModelWithDatesArrayResponseTransformer = data => { + if (Array.isArray(data)) { + data.forEach(ModelWithDatesModelResponseTransformer); + } + return data; }; \ No newline at end of file diff --git a/packages/openapi-ts/test/sample.cjs b/packages/openapi-ts/test/sample.cjs index 4909d5476..9137043bb 100644 --- a/packages/openapi-ts/test/sample.cjs +++ b/packages/openapi-ts/test/sample.cjs @@ -4,8 +4,8 @@ const main = async () => { /** @type {import('../src/node/index').UserConfig} */ const config = { client: '@hey-api/client-fetch', - input: './test/spec/v3.json', - // input: './test/spec/v3-transforms.json', + // input: './test/spec/v3.json', + input: './test/spec/v3-transforms.json', // input: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', output: { path: './test/generated/sample/', @@ -15,7 +15,7 @@ const main = async () => { }, services: { // asClass: true, - export: false, + // export: false, // name: '^Parameters', }, types: { diff --git a/packages/openapi-ts/test/spec/v3-transforms.json b/packages/openapi-ts/test/spec/v3-transforms.json index c1ded0ba8..33496c54b 100644 --- a/packages/openapi-ts/test/spec/v3-transforms.json +++ b/packages/openapi-ts/test/spec/v3-transforms.json @@ -11,6 +11,21 @@ ], "paths": { "/api/model-with-dates": { + "post": { + "operationId": "parentModelWithDates", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json; type=collection": { + "schema": { + "$ref": "#/components/schemas/ParentModelWithDates" + } + } + } + } + } + }, "put": { "operationId": "modelWithDates", "responses": { From 756d53f59c93abf47244f5d147a38e56477fe5a2 Mon Sep 17 00:00:00 2001 From: Lubos Date: Mon, 24 Jun 2024 05:28:48 +0200 Subject: [PATCH 3/5] fix: handle void responses in transformers --- packages/openapi-ts/src/compiler/index.ts | 5 + packages/openapi-ts/src/compiler/transform.ts | 95 +++++---- .../src/utils/write/transformers.ts | 187 ++++++++---------- packages/openapi-ts/src/utils/write/types.ts | 2 +- .../v3_angular_transform/services.gen.ts.snap | 1 + .../v3_angular_transform/types.gen.ts.snap | 10 +- .../v3_axios_transform/services.gen.ts.snap | 1 + .../v3_axios_transform/types.gen.ts.snap | 10 +- .../v3_client_transform/services.gen.ts.snap | 1 + .../v3_client_transform/types.gen.ts.snap | 10 +- .../types.gen.ts.snap | 10 +- .../types.gen.ts.snap | 10 +- .../v3_node_transform/services.gen.ts.snap | 1 + .../v3_node_transform/types.gen.ts.snap | 10 +- .../v3_transform/services.gen.ts.snap | 1 + .../generated/v3_transform/types.gen.ts.snap | 10 +- .../v3_xhr_transform/services.gen.ts.snap | 1 + .../v3_xhr_transform/types.gen.ts.snap | 10 +- packages/openapi-ts/test/sample.cjs | 1 + .../openapi-ts/test/spec/v3-transforms.json | 3 + 20 files changed, 217 insertions(+), 162 deletions(-) diff --git a/packages/openapi-ts/src/compiler/index.ts b/packages/openapi-ts/src/compiler/index.ts index f73a9a052..771aa503d 100644 --- a/packages/openapi-ts/src/compiler/index.ts +++ b/packages/openapi-ts/src/compiler/index.ts @@ -147,6 +147,11 @@ export const compiler = { import: { named: module.createNamedImportDeclarations, }, + logic: { + access: transform.createAccessExpression, + if: transform.createIfStatement, + safeAccess: transform.createSafeAccessExpression, + }, return: { functionCall: _return.createReturnFunctionCall, statement: _return.createReturnStatement, diff --git a/packages/openapi-ts/src/compiler/transform.ts b/packages/openapi-ts/src/compiler/transform.ts index 4bad086db..010f01705 100644 --- a/packages/openapi-ts/src/compiler/transform.ts +++ b/packages/openapi-ts/src/compiler/transform.ts @@ -3,7 +3,7 @@ import ts from 'typescript'; import { convertExpressionToStatement } from './convert'; import { createReturnStatement } from './return'; -const getSafeAccessExpression = (path: string[]) => +export const createSafeAccessExpression = (path: string[]) => path .slice(1) .reduce( @@ -16,7 +16,7 @@ const getSafeAccessExpression = (path: string[]) => ts.factory.createIdentifier(path[0]), ); -const getAccessExpression = (path: string[]) => +export const createAccessExpression = (path: string[]) => path .slice(1) .reduce( @@ -28,30 +28,42 @@ const getAccessExpression = (path: string[]) => ts.factory.createIdentifier(path[0]), ); +export const createIfStatement = ({ + expression, + thenStatement, + elseStatement, +}: { + expression: ts.Expression; + thenStatement: ts.Statement; + elseStatement?: ts.Statement; +}) => ts.factory.createIfStatement(expression, thenStatement, elseStatement); + export const createDateTransformMutation = ({ path, }: { path: string[]; }): ts.Statement => { - const safeAccessExpression = getSafeAccessExpression(path); - const accessExpression = getAccessExpression(path); + const safeAccessExpression = createSafeAccessExpression(path); + const accessExpression = createAccessExpression(path); - const statement = ts.factory.createIfStatement( - safeAccessExpression, - ts.factory.createBlock([ - convertExpressionToStatement({ - expression: ts.factory.createBinaryExpression( - accessExpression, - ts.SyntaxKind.EqualsToken, - ts.factory.createNewExpression( - ts.factory.createIdentifier('Date'), - undefined, - [accessExpression], - ), + const thenStatement = ts.factory.createBlock([ + convertExpressionToStatement({ + expression: ts.factory.createBinaryExpression( + accessExpression, + ts.SyntaxKind.EqualsToken, + ts.factory.createNewExpression( + ts.factory.createIdentifier('Date'), + undefined, + [accessExpression], ), - }), - ]), - ); + ), + }), + ]); + + const statement = createIfStatement({ + expression: safeAccessExpression, + thenStatement, + }); return statement; }; @@ -63,8 +75,8 @@ export const createFunctionTransformMutation = ({ path: string[]; transformerName: string; }) => { - const safeAccessExpression = getSafeAccessExpression(path); - const accessExpression = getAccessExpression(path); + const safeAccessExpression = createSafeAccessExpression(path); + const accessExpression = createAccessExpression(path); const thenStatement = ts.factory.createBlock( [ @@ -80,11 +92,10 @@ export const createFunctionTransformMutation = ({ ); const statement = [ - ts.factory.createIfStatement( - safeAccessExpression, + createIfStatement({ + expression: safeAccessExpression, thenStatement, - undefined, - ), + }), ]; return statement; @@ -97,11 +108,11 @@ export const createArrayTransformMutation = ({ path: string[]; transformerName: string; }): ts.Statement => { - const safeAccessExpression = getSafeAccessExpression(path); - const accessExpression = getAccessExpression(path); + const safeAccessExpression = createSafeAccessExpression(path); + const accessExpression = createAccessExpression(path); - const statement = ts.factory.createIfStatement( - ts.factory.createCallExpression( + const statement = createIfStatement({ + expression: ts.factory.createCallExpression( ts.factory.createPropertyAccessExpression( ts.factory.createIdentifier('Array'), ts.factory.createIdentifier('isArray'), @@ -109,7 +120,7 @@ export const createArrayTransformMutation = ({ undefined, [safeAccessExpression], ), - ts.factory.createBlock( + thenStatement: ts.factory.createBlock( [ convertExpressionToStatement({ expression: ts.factory.createCallChain( @@ -125,7 +136,7 @@ export const createArrayTransformMutation = ({ ], true, ), - ); + }); return statement; }; @@ -148,11 +159,11 @@ export const createArrayMapTransform = ({ path: string[]; transformExpression: ts.Expression; }) => { - const safeAccessExpression = getSafeAccessExpression(path); - const accessExpression = getAccessExpression(path); + const safeAccessExpression = createSafeAccessExpression(path); + const accessExpression = createAccessExpression(path); - const statement = ts.factory.createIfStatement( - ts.factory.createCallExpression( + const statement = createIfStatement({ + expression: ts.factory.createCallExpression( ts.factory.createPropertyAccessExpression( ts.factory.createIdentifier('Array'), ts.factory.createIdentifier('isArray'), @@ -160,7 +171,7 @@ export const createArrayMapTransform = ({ undefined, [safeAccessExpression], ), - ts.factory.createBlock( + thenStatement: ts.factory.createBlock( [ convertExpressionToStatement({ expression: ts.factory.createBinaryExpression( @@ -198,8 +209,7 @@ export const createArrayMapTransform = ({ ], true, ), - undefined, - ); + }); return statement; }; @@ -250,8 +260,8 @@ export const createResponseArrayTransform = ({ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createBlock( [ - ts.factory.createIfStatement( - ts.factory.createCallExpression( + createIfStatement({ + expression: ts.factory.createCallExpression( ts.factory.createPropertyAccessExpression( ts.factory.createIdentifier('Array'), ts.factory.createIdentifier('isArray'), @@ -259,7 +269,7 @@ export const createResponseArrayTransform = ({ undefined, [ts.factory.createIdentifier('data')], ), - ts.factory.createBlock( + thenStatement: ts.factory.createBlock( [ convertExpressionToStatement({ expression: ts.factory.createCallExpression( @@ -274,8 +284,7 @@ export const createResponseArrayTransform = ({ ], true, ), - undefined, - ), + }), createReturnStatement({ expression: ts.factory.createIdentifier('data'), }), diff --git a/packages/openapi-ts/src/utils/write/transformers.ts b/packages/openapi-ts/src/utils/write/transformers.ts index de8c57c53..4b0c3a516 100644 --- a/packages/openapi-ts/src/utils/write/transformers.ts +++ b/packages/openapi-ts/src/utils/write/transformers.ts @@ -1,7 +1,10 @@ import ts from 'typescript'; import { compiler } from '../../compiler'; -import type { ModelMeta } from '../../openApi/common/interfaces/client'; +import type { + ModelMeta, + OperationResponse, +} from '../../openApi/common/interfaces/client'; import { getSuccessResponses } from '../../openApi/common/parser/operation'; import { getConfig } from '../config'; import { @@ -19,6 +22,11 @@ interface ModelProps extends TypesProps { const dataVariableName = 'data'; +const isVoidResponse = (response: OperationResponse) => + response.base === 'unknown' && + response.export === 'generic' && + response.type === 'unknown'; + const getRefModels = ({ client, model, @@ -35,16 +43,13 @@ const getRefModels = ({ return refModels; }; -const ensureModelResponseTransformerExists = ({ - client, - model, - onNode, - onRemoveNode, -}: Omit) => { - const modelName = model.meta!.name; +const ensureModelResponseTransformerExists = ( + props: Omit, +) => { + const modelName = props.model.meta!.name; const { name } = generateType({ - client, + ...props, meta: { $ref: `transformers/${modelName}`, name: modelName, @@ -52,52 +57,36 @@ const ensureModelResponseTransformerExists = ({ nameTransformer: modelResponseTransformerTypeName, onCreated: (name) => { const statements = processModel({ - client, + ...props, meta: { $ref: `transformers/${modelName}`, name, }, - model, - onNode, - onRemoveNode, path: [dataVariableName], }); generateResponseTransformer({ - client, + ...props, name, - onNode, - onRemoveNode, statements, }); }, - onNode, type: `(${dataVariableName}: any) => ${modelName}`, }); const result = { - created: Boolean(client.types[name]), + created: Boolean(props.client.types[name]), name, }; return result; }; -const processArray = ({ - client, - model, - onNode, - onRemoveNode, - path, -}: ModelProps) => { - const refModels = getRefModels({ client, model }); +const processArray = (props: ModelProps) => { + const { model } = props; + const refModels = getRefModels(props); if (refModels.length === 1) { const { created, name: nameModelResponseTransformer } = - ensureModelResponseTransformerExists({ - client, - model: refModels[0], - onNode, - onRemoveNode, - }); + ensureModelResponseTransformerExists({ ...props, model: refModels[0] }); if (!created) { return []; @@ -105,7 +94,7 @@ const processArray = ({ return [ compiler.transform.arrayTransformMutation({ - path, + path: props.path, transformerName: nameModelResponseTransformer, }), ]; @@ -114,7 +103,7 @@ const processArray = ({ if (model.format === 'date' || model.format === 'date-time') { return [ compiler.transform.mapArray({ - path, + path: props.path, transformExpression: compiler.transform.newDate({ parameterName: 'item', }), @@ -126,81 +115,44 @@ const processArray = ({ return []; }; -const processProperty = ({ - client, - model, - onNode, - onRemoveNode, - path, - meta, -}: ModelProps) => { - const pathProperty = [...path, model.name]; +const processProperty = (props: ModelProps) => { + const { model } = props; + const path = [...props.path, model.name]; if ( model.type === 'string' && model.export !== 'array' && (model.format === 'date-time' || model.format === 'date') ) { - return [compiler.transform.dateTransformMutation({ path: pathProperty })]; + return [compiler.transform.dateTransformMutation({ path })]; } // otherwise we recurse in case it's an object/array, and if it's not that will just bail with [] return processModel({ - client, - meta, + ...props, model, - onNode, - onRemoveNode, - path: pathProperty, + path, }); }; -const processModel = ({ - client, - model, - meta, - onNode, - onRemoveNode, - path, -}: ModelProps): ts.Statement[] => { +const processModel = (props: ModelProps): ts.Statement[] => { + const { model } = props; + switch (model.export) { - case 'array': { - return processArray({ - client, - meta, - model, - onNode, - onRemoveNode, - path, - }); - } - case 'interface': { - const statements = model.properties.flatMap((property) => - processProperty({ - client, - meta, - model: property, - onNode, - onRemoveNode, - path, - }), + case 'array': + return processArray(props); + case 'interface': + return model.properties.flatMap((property) => + processProperty({ ...props, model: property }), ); - - return statements; - } case 'reference': { if (model.$refs.length !== 1) { return []; } - const refModels = getRefModels({ client, model }); + const refModels = getRefModels(props); const { created, name: nameModelResponseTransformer } = - ensureModelResponseTransformerExists({ - client, - model: refModels[0], - onNode, - onRemoveNode, - }); + ensureModelResponseTransformerExists({ ...props, model: refModels[0] }); if (!created) { return []; @@ -216,12 +168,12 @@ const processModel = ({ }), ] : compiler.transform.transformItem({ - path, + path: props.path, transformerName: nameModelResponseTransformer, }); } + // unsupported default: - // Unsupported return []; } }; @@ -296,11 +248,15 @@ export const processResponseTransformers = async ({ const responses = getSuccessResponses(operation.results); - if (!responses.length) { + const nonVoidResponses = responses.filter( + (response) => !isVoidResponse(response), + ); + + if (!nonVoidResponses.length) { continue; } - if (responses.length > 1) { + if (nonVoidResponses.length > 1) { if (config.debug) { console.warn( `⚠️ Transformers warning: route ${operation.method} ${operation.path} has ${responses.length} 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`, @@ -318,17 +274,44 @@ export const processResponseTransformers = async ({ }, nameTransformer: operationResponseTransformerTypeName, onCreated: (nameCreated) => { - const statements = processModel({ - client, - meta: { - $ref: `transformers/${name}`, - name, - }, - model: operation.results[0], - onNode, - onRemoveNode, - path: [dataVariableName], - }); + const statements = + responses.length > 1 + ? responses.flatMap((response) => { + const statements = processModel({ + client, + meta: { + $ref: `transformers/${name}`, + name, + }, + model: response, + onNode, + onRemoveNode, + path: [dataVariableName], + }); + + // assume unprocessed responses are void + if (!statements.length) { + return []; + } + + return [ + compiler.logic.if({ + expression: compiler.logic.safeAccess(['data']), + thenStatement: ts.factory.createBlock(statements), + }), + ]; + }) + : processModel({ + client, + meta: { + $ref: `transformers/${name}`, + name, + }, + model: operation.results[0], + onNode, + onRemoveNode, + path: [dataVariableName], + }); generateResponseTransformer({ client, name: nameCreated, diff --git a/packages/openapi-ts/src/utils/write/types.ts b/packages/openapi-ts/src/utils/write/types.ts index 41e559c57..c6e1e1196 100644 --- a/packages/openapi-ts/src/utils/write/types.ts +++ b/packages/openapi-ts/src/utils/write/types.ts @@ -31,7 +31,7 @@ export interface TypesProps { const serviceExportedNamespace = () => '$OpenApiTs'; -const emptyModel: Model = { +export const emptyModel: Model = { $refs: [], base: '', description: null, diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/services.gen.ts.snap index 17a6d508a..272083d22 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/services.gen.ts.snap @@ -15,6 +15,7 @@ export class DefaultService { /** * @returns ParentModelWithDates Success + * @returns unknown Success * @throws ApiError */ public parentModelWithDates(): Observable { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/types.gen.ts.snap index dbd786b11..2b8ee08b4 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_angular_transform/types.gen.ts.snap @@ -34,7 +34,7 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export type ParentModelWithDatesResponse = ParentModelWithDates; +export type ParentModelWithDatesResponse = ParentModelWithDates | unknown; export type ModelWithDatesResponse = ModelWithDates; @@ -54,6 +54,10 @@ export type $OpenApiTs = { * Success */ 200: ParentModelWithDates; + /** + * Success + */ + 201: unknown; }; }; put: { @@ -144,7 +148,9 @@ export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesM }; export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { - ParentModelWithDatesModelResponseTransformer(data); + if (data) { + ParentModelWithDatesModelResponseTransformer(data); + } return data; }; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/services.gen.ts.snap index 7a30623b5..5211e9979 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/services.gen.ts.snap @@ -8,6 +8,7 @@ import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type Mo export class DefaultService { /** * @returns ParentModelWithDates Success + * @returns unknown Success * @throws ApiError */ public static parentModelWithDates(): CancelablePromise { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/types.gen.ts.snap index dbd786b11..2b8ee08b4 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_axios_transform/types.gen.ts.snap @@ -34,7 +34,7 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export type ParentModelWithDatesResponse = ParentModelWithDates; +export type ParentModelWithDatesResponse = ParentModelWithDates | unknown; export type ModelWithDatesResponse = ModelWithDates; @@ -54,6 +54,10 @@ export type $OpenApiTs = { * Success */ 200: ParentModelWithDates; + /** + * Success + */ + 201: unknown; }; }; put: { @@ -144,7 +148,9 @@ export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesM }; export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { - ParentModelWithDatesModelResponseTransformer(data); + if (data) { + ParentModelWithDatesModelResponseTransformer(data); + } return data; }; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/services.gen.ts.snap index 7d449f2b0..e83516e47 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/services.gen.ts.snap @@ -9,6 +9,7 @@ export class DefaultService { /** * @returns ParentModelWithDates Success + * @returns unknown Success * @throws ApiError */ public parentModelWithDates(): CancelablePromise { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/types.gen.ts.snap index dbd786b11..2b8ee08b4 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_client_transform/types.gen.ts.snap @@ -34,7 +34,7 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export type ParentModelWithDatesResponse = ParentModelWithDates; +export type ParentModelWithDatesResponse = ParentModelWithDates | unknown; export type ModelWithDatesResponse = ModelWithDates; @@ -54,6 +54,10 @@ export type $OpenApiTs = { * Success */ 200: ParentModelWithDates; + /** + * Success + */ + 201: unknown; }; }; put: { @@ -144,7 +148,9 @@ export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesM }; export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { - ParentModelWithDatesModelResponseTransformer(data); + if (data) { + ParentModelWithDatesModelResponseTransformer(data); + } return data; }; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/types.gen.ts.snap index b1e66047f..70670037c 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-axios_transform/types.gen.ts.snap @@ -34,7 +34,7 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export type ParentModelWithDatesResponse = ParentModelWithDates; +export type ParentModelWithDatesResponse = ParentModelWithDates | unknown; export type ParentModelWithDatesError = unknown; @@ -66,6 +66,10 @@ export type $OpenApiTs = { * Success */ '200': ParentModelWithDates; + /** + * Success + */ + '201': unknown; }; }; put: { @@ -156,7 +160,9 @@ export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesM }; export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { - ParentModelWithDatesModelResponseTransformer(data); + if (data) { + ParentModelWithDatesModelResponseTransformer(data); + } return data; }; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/types.gen.ts.snap index b1e66047f..70670037c 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_hey-api_client-fetch_transform/types.gen.ts.snap @@ -34,7 +34,7 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export type ParentModelWithDatesResponse = ParentModelWithDates; +export type ParentModelWithDatesResponse = ParentModelWithDates | unknown; export type ParentModelWithDatesError = unknown; @@ -66,6 +66,10 @@ export type $OpenApiTs = { * Success */ '200': ParentModelWithDates; + /** + * Success + */ + '201': unknown; }; }; put: { @@ -156,7 +160,9 @@ export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesM }; export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { - ParentModelWithDatesModelResponseTransformer(data); + if (data) { + ParentModelWithDatesModelResponseTransformer(data); + } return data; }; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/services.gen.ts.snap index 7a30623b5..5211e9979 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/services.gen.ts.snap @@ -8,6 +8,7 @@ import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type Mo export class DefaultService { /** * @returns ParentModelWithDates Success + * @returns unknown Success * @throws ApiError */ public static parentModelWithDates(): CancelablePromise { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/types.gen.ts.snap index dbd786b11..2b8ee08b4 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_node_transform/types.gen.ts.snap @@ -34,7 +34,7 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export type ParentModelWithDatesResponse = ParentModelWithDates; +export type ParentModelWithDatesResponse = ParentModelWithDates | unknown; export type ModelWithDatesResponse = ModelWithDates; @@ -54,6 +54,10 @@ export type $OpenApiTs = { * Success */ 200: ParentModelWithDates; + /** + * Success + */ + 201: unknown; }; }; put: { @@ -144,7 +148,9 @@ export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesM }; export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { - ParentModelWithDatesModelResponseTransformer(data); + if (data) { + ParentModelWithDatesModelResponseTransformer(data); + } return data; }; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/services.gen.ts.snap index 7a30623b5..5211e9979 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/services.gen.ts.snap @@ -8,6 +8,7 @@ import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type Mo export class DefaultService { /** * @returns ParentModelWithDates Success + * @returns unknown Success * @throws ApiError */ public static parentModelWithDates(): CancelablePromise { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/types.gen.ts.snap index dbd786b11..2b8ee08b4 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_transform/types.gen.ts.snap @@ -34,7 +34,7 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export type ParentModelWithDatesResponse = ParentModelWithDates; +export type ParentModelWithDatesResponse = ParentModelWithDates | unknown; export type ModelWithDatesResponse = ModelWithDates; @@ -54,6 +54,10 @@ export type $OpenApiTs = { * Success */ 200: ParentModelWithDates; + /** + * Success + */ + 201: unknown; }; }; put: { @@ -144,7 +148,9 @@ export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesM }; export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { - ParentModelWithDatesModelResponseTransformer(data); + if (data) { + ParentModelWithDatesModelResponseTransformer(data); + } return data; }; diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/services.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/services.gen.ts.snap index 7a30623b5..5211e9979 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/services.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/services.gen.ts.snap @@ -8,6 +8,7 @@ import { type ParentModelWithDatesResponse, type ModelWithDatesResponse, type Mo export class DefaultService { /** * @returns ParentModelWithDates Success + * @returns unknown Success * @throws ApiError */ public static parentModelWithDates(): CancelablePromise { diff --git a/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/types.gen.ts.snap b/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/types.gen.ts.snap index dbd786b11..2b8ee08b4 100644 --- a/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/types.gen.ts.snap +++ b/packages/openapi-ts/test/__snapshots__/test/generated/v3_xhr_transform/types.gen.ts.snap @@ -34,7 +34,7 @@ export type ParentModelWithDates = { strings?: Array<(string)>; }; -export type ParentModelWithDatesResponse = ParentModelWithDates; +export type ParentModelWithDatesResponse = ParentModelWithDates | unknown; export type ModelWithDatesResponse = ModelWithDates; @@ -54,6 +54,10 @@ export type $OpenApiTs = { * Success */ 200: ParentModelWithDates; + /** + * Success + */ + 201: unknown; }; }; put: { @@ -144,7 +148,9 @@ export const ParentModelWithDatesModelResponseTransformer: ParentModelWithDatesM }; export const ParentModelWithDatesResponseTransformer: ParentModelWithDatesResponseTransformer = data => { - ParentModelWithDatesModelResponseTransformer(data); + if (data) { + ParentModelWithDatesModelResponseTransformer(data); + } return data; }; diff --git a/packages/openapi-ts/test/sample.cjs b/packages/openapi-ts/test/sample.cjs index 9137043bb..055a2f240 100644 --- a/packages/openapi-ts/test/sample.cjs +++ b/packages/openapi-ts/test/sample.cjs @@ -5,6 +5,7 @@ const main = async () => { const config = { client: '@hey-api/client-fetch', // input: './test/spec/v3.json', + debug: true, input: './test/spec/v3-transforms.json', // input: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', output: { diff --git a/packages/openapi-ts/test/spec/v3-transforms.json b/packages/openapi-ts/test/spec/v3-transforms.json index 33496c54b..c42b55f7c 100644 --- a/packages/openapi-ts/test/spec/v3-transforms.json +++ b/packages/openapi-ts/test/spec/v3-transforms.json @@ -23,6 +23,9 @@ } } } + }, + "201": { + "description": "Success" } } }, From 6a5b96b59e4248f2acaf5422be262edde97427dd Mon Sep 17 00:00:00 2001 From: Lubos Date: Mon, 24 Jun 2024 05:36:43 +0200 Subject: [PATCH 4/5] ci: add changeset --- .changeset/quick-trees-laugh.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/quick-trees-laugh.md diff --git a/.changeset/quick-trees-laugh.md b/.changeset/quick-trees-laugh.md new file mode 100644 index 000000000..dbf4ee0f4 --- /dev/null +++ b/.changeset/quick-trees-laugh.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +fix: handle void responses in transformers From 0ce82e53bbf34a0bcf9265d310889b4dbc81397b Mon Sep 17 00:00:00 2001 From: Lubos Date: Mon, 24 Jun 2024 05:47:07 +0200 Subject: [PATCH 5/5] ci: fix build --- packages/openapi-ts/src/utils/write/type.ts | 4 ++-- packages/openapi-ts/src/utils/write/types.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/openapi-ts/src/utils/write/type.ts b/packages/openapi-ts/src/utils/write/type.ts index c00816073..fc0ed964f 100644 --- a/packages/openapi-ts/src/utils/write/type.ts +++ b/packages/openapi-ts/src/utils/write/type.ts @@ -155,7 +155,7 @@ export const toType = (model: Model): TypeNode => { } }; -interface SetUniqueTypeNameResult { +export interface SetUniqueTypeNameResult { /** * Did this function add a new property to the `client.types` object? */ @@ -227,7 +227,7 @@ export const setUniqueTypeName = ({ return result; }; -interface UnsetUniqueTypeNameResult { +export interface UnsetUniqueTypeNameResult { /** * Did this function delete a property from the `client.types` object? */ diff --git a/packages/openapi-ts/src/utils/write/types.ts b/packages/openapi-ts/src/utils/write/types.ts index c6e1e1196..6c005f070 100644 --- a/packages/openapi-ts/src/utils/write/types.ts +++ b/packages/openapi-ts/src/utils/write/types.ts @@ -20,7 +20,11 @@ import { operationErrorTypeName, operationResponseTypeName, } from './services'; -import { setUniqueTypeName, toType } from './type'; +import { + setUniqueTypeName, + type SetUniqueTypeNameResult, + toType, +} from './type'; export interface TypesProps { client: Client; @@ -95,7 +99,7 @@ export const generateType = ({ Pick & Pick & { onCreated?: (name: string) => void; - }) => { + }): SetUniqueTypeNameResult => { // generate types only for top-level models if (!meta) { return {