Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/prefix items #701

Merged
merged 3 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lazy-cheetahs-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': patch
---

feat: add initial implementation of prefixItems
72 changes: 30 additions & 42 deletions packages/openapi-ts/src/compiler/typedef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
tsNodeToString,
} from './utils';

const nullNode = ts.factory.createTypeReferenceNode('null');

export const createTypeNode = (
base: any | ts.TypeNode,
args?: (any | ts.TypeNode)[],
Expand Down Expand Up @@ -62,6 +64,23 @@
comment?: Comments;
};

/**
* Returns a union of provided node with null if marked as nullable,
* otherwise returns the provided node unmodified.
*/
const maybeNullable = ({
isNullable,
node,
}: {
node: ts.TypeNode;
isNullable: boolean;
}) => {
if (!isNullable) {
return node;
}
return ts.factory.createUnionTypeNode([node, nullNode]);
};

Check warning on line 82 in packages/openapi-ts/src/compiler/typedef.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/typedef.ts#L72-L82

Added lines #L72 - L82 were not covered by tests

/**
* Create a interface type node. Example `{ readonly x: string, y?: number }`
* @param properties - the properties of the interface.
Expand Down Expand Up @@ -90,13 +109,7 @@
return signature;
}),
);
if (!isNullable) {
return node;
}
return ts.factory.createUnionTypeNode([
node,
ts.factory.createTypeReferenceNode('null'),
]);
return maybeNullable({ isNullable, node });

Check warning on line 112 in packages/openapi-ts/src/compiler/typedef.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/typedef.ts#L112

Added line #L112 was not covered by tests
};

/**
Expand All @@ -110,10 +123,10 @@
isNullable: boolean = false,
) => {
const nodes = types.map((type) => createTypeNode(type));
if (isNullable) {
nodes.push(ts.factory.createTypeReferenceNode('null'));
if (!isNullable) {
return ts.factory.createUnionTypeNode(nodes);

Check warning on line 127 in packages/openapi-ts/src/compiler/typedef.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/typedef.ts#L126-L127

Added lines #L126 - L127 were not covered by tests
}
return ts.factory.createUnionTypeNode(nodes);
return ts.factory.createUnionTypeNode([...nodes, nullNode]);

Check warning on line 129 in packages/openapi-ts/src/compiler/typedef.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/typedef.ts#L129

Added line #L129 was not covered by tests
};

/**
Expand All @@ -126,15 +139,9 @@
types: (any | ts.TypeNode)[],
isNullable: boolean = false,
) => {
const nodes = types.map((t) => createTypeNode(t));
const intersect = ts.factory.createIntersectionTypeNode(nodes);
if (isNullable) {
return ts.factory.createUnionTypeNode([
intersect,
ts.factory.createTypeReferenceNode('null'),
]);
}
return intersect;
const nodes = types.map((type) => createTypeNode(type));
const node = ts.factory.createIntersectionTypeNode(nodes);
return maybeNullable({ isNullable, node });

Check warning on line 144 in packages/openapi-ts/src/compiler/typedef.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/typedef.ts#L142-L144

Added lines #L142 - L144 were not covered by tests
};

/**
Expand All @@ -151,15 +158,8 @@
types: Array<any | ts.TypeNode>;
}) => {
const nodes = types.map((type) => createTypeNode(type));
const tupleNode = ts.factory.createTupleTypeNode(nodes);
if (isNullable) {
const unionNode = ts.factory.createUnionTypeNode([
tupleNode,
ts.factory.createTypeReferenceNode('null'),
]);
return unionNode;
}
return tupleNode;
const node = ts.factory.createTupleTypeNode(nodes);
return maybeNullable({ isNullable, node });

Check warning on line 162 in packages/openapi-ts/src/compiler/typedef.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/typedef.ts#L161-L162

Added lines #L161 - L162 were not covered by tests
};

/**
Expand All @@ -186,13 +186,7 @@
type: valueNode,
},
]);
if (!isNullable) {
return node;
}
return ts.factory.createUnionTypeNode([
node,
ts.factory.createTypeReferenceNode('null'),
]);
return maybeNullable({ isNullable, node });

Check warning on line 189 in packages/openapi-ts/src/compiler/typedef.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/typedef.ts#L189

Added line #L189 was not covered by tests
};

/**
Expand All @@ -208,11 +202,5 @@
const node = ts.factory.createTypeReferenceNode('Array', [
createTypeUnionNode(types),
]);
if (!isNullable) {
return node;
}
return ts.factory.createUnionTypeNode([
node,
ts.factory.createTypeReferenceNode('null'),
]);
return maybeNullable({ isNullable, node });

Check warning on line 205 in packages/openapi-ts/src/compiler/typedef.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/typedef.ts#L205

Added line #L205 was not covered by tests
};
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export interface Model extends Schema {
| OpenApiParameter['in']
| OperationResponse['in']
| '';
link: Model | null;
link: Model | Model[] | null;
meta?: ModelMeta;
/**
* @deprecated use `meta.name` instead
Expand Down
15 changes: 14 additions & 1 deletion packages/openapi-ts/src/openApi/common/parser/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,20 @@
const equal =
a.type === b.type && a.base === b.base && a.template === b.template;
if (equal && a.link && b.link) {
return areEqual(a.link, b.link);
if (!Array.isArray(a.link) && !Array.isArray(b.link)) {
return areEqual(a.link, b.link);
}

if (
Array.isArray(a.link) &&

Check warning on line 16 in packages/openapi-ts/src/openApi/common/parser/operation.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/common/parser/operation.ts#L14-L16

Added lines #L14 - L16 were not covered by tests
Array.isArray(b.link) &&
a.link.length === b.link.length

Check warning on line 18 in packages/openapi-ts/src/openApi/common/parser/operation.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/common/parser/operation.ts#L18

Added line #L18 was not covered by tests
) {
const bLinks = b.link;
return a.link.every((model, index) => areEqual(model, bLinks[index]!));
}

return false;

Check warning on line 24 in packages/openapi-ts/src/openApi/common/parser/operation.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/common/parser/operation.ts#L20-L24

Added lines #L20 - L24 were not covered by tests
}
return equal;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface OpenApiSchema extends OpenApiReference, WithEnumExtension {
nullable?: boolean;
oneOf?: OpenApiSchema[];
pattern?: string;
prefixItems?: OpenApiSchema[];
properties?: Dictionary<OpenApiSchema>;
readOnly?: boolean;
required?: string[];
Expand Down
39 changes: 38 additions & 1 deletion packages/openapi-ts/src/openApi/v3/parser/getModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,44 @@
}
}

if (definitionTypes.includes('array') && definition.items) {
if (
definitionTypes.includes('array') &&
(definition.items || definition.prefixItems)
) {
if (definition.prefixItems) {
const arrayItems = definition.prefixItems.map((item) =>
getModel({
definition: item,
openApi,
parentDefinition: definition,
types,
}),
);

model.export = 'array';
model.$refs = [
...model.$refs,
...arrayItems.reduce(
(acc, m) => [...acc, ...m.$refs],
[] as Model['$refs'],
),
];
model.imports = [
...model.imports,
...arrayItems.reduce(
(acc, m) => [...acc, ...m.imports],
[] as Model['imports'],
),
];
model.link = arrayItems;
model.default = getDefault(definition, model);
return model;
}

if (!definition.items) {
return model;

Check warning on line 145 in packages/openapi-ts/src/openApi/v3/parser/getModel.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/v3/parser/getModel.ts#L145

Added line #L145 was not covered by tests
}

if (definition.items.$ref) {
const arrayItems = getType({ type: definition.items.$ref });
model.$refs = [...model.$refs, definition.items.$ref];
Expand Down
48 changes: 30 additions & 18 deletions packages/openapi-ts/src/utils/write/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,33 @@
};

const typeArray = (model: Model) => {
// Special case where we use tuple to define constant size array.
if (
model.export === 'array' &&
model.link &&
model.maxItems &&
model.minItems &&
model.maxItems === model.minItems &&
model.maxItems <= 100
) {
const types = Array(model.maxItems).fill(toType(model.link));
const tuple = compiler.typedef.tuple({
isNullable: model.isNullable,
types,
});
return tuple;
}

if (model.link) {
// We treat an array of `model.link` as constant size array definition.
if (Array.isArray(model.link)) {
const types = model.link.map((m) => toType(m));
const tuple = compiler.typedef.tuple({
isNullable: model.isNullable,
types,
});
return tuple;
}

// Special case where we use tuple to define constant size array.
if (
model.export === 'array' &&
model.maxItems &&
model.minItems &&
model.maxItems === model.minItems &&
model.maxItems <= 100
) {
const types = Array(model.maxItems).fill(toType(model.link));
const tuple = compiler.typedef.tuple({
isNullable: model.isNullable,
types,
});
return tuple;
}

Check warning on line 61 in packages/openapi-ts/src/utils/write/type.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/utils/write/type.ts#L36-L61

Added lines #L36 - L61 were not covered by tests
return compiler.typedef.array([toType(model.link)], model.isNullable);
}

Expand All @@ -62,7 +71,8 @@
};

const typeDict = (model: Model) => {
const type = model.link ? toType(model.link) : base(model);
const type =
model.link && !Array.isArray(model.link) ? toType(model.link) : base(model);

Check warning on line 75 in packages/openapi-ts/src/utils/write/type.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/utils/write/type.ts#L74-L75

Added lines #L74 - L75 were not covered by tests
return compiler.typedef.record(['string'], [type], model.isNullable);
};

Expand Down Expand Up @@ -137,6 +147,8 @@
return typeEnum(model);
case 'interface':
return typeInterface(model);
case 'const':
case 'generic':
case 'reference':
default:
return typeReference(model);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,28 @@ export const $ModelWithAnyOfConstantSizeArray = {
maxItems: 3
} as const;

export const $ModelWithPrefixItemsConstantSizeArray = {
type: 'array',
prefixItems: [
{
'$ref': '#/components/schemas/ModelWithInteger'
},
{
oneOf: [
{
type: 'number'
},
{
type: 'string'
}
]
},
{
type: 'string'
}
]
} as const;

export const $ModelWithAnyOfConstantSizeArrayNullable = {
type: ['array'],
items: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,12 @@ export type ModelWithAnyOfConstantSizeArray = [
number | string
];

export type ModelWithPrefixItemsConstantSizeArray = [
ModelWithInteger,
number | string,
string
];

export type ModelWithAnyOfConstantSizeArrayNullable = [
number | null | string,
number | null | string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,28 @@ export const $ModelWithAnyOfConstantSizeArray = {
maxItems: 3
} as const;

export const $ModelWithPrefixItemsConstantSizeArray = {
type: 'array',
prefixItems: [
{
'$ref': '#/components/schemas/ModelWithInteger'
},
{
oneOf: [
{
type: 'number'
},
{
type: 'string'
}
]
},
{
type: 'string'
}
]
} as const;

export const $ModelWithAnyOfConstantSizeArrayNullable = {
type: ['array'],
items: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,12 @@ export type ModelWithAnyOfConstantSizeArray = [
number | string
];

export type ModelWithPrefixItemsConstantSizeArray = [
ModelWithInteger,
number | string,
string
];

export type ModelWithAnyOfConstantSizeArrayNullable = [
number | null | string,
number | null | string,
Expand Down
Loading