Skip to content

Commit

Permalink
fix: infinite loop in transforms
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlubos committed Jun 23, 2024
1 parent 0e79f7b commit e040c59
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 138 deletions.
2 changes: 1 addition & 1 deletion packages/openapi-ts/src/openApi/common/parser/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions packages/openapi-ts/src/openApi/v3/parser/getModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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];
Expand Down
210 changes: 101 additions & 109 deletions packages/openapi-ts/src/utils/write/transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
};
Loading

0 comments on commit e040c59

Please sign in to comment.