Skip to content

Commit

Permalink
Merge pull request #223 from asteasolutions/use-prefix-items-for-tuples
Browse files Browse the repository at this point in the history
handle tuples as prefix items for v3.1.0 of openapi
  • Loading branch information
AGalabov committed Apr 5, 2024
2 parents 8773c3e + 3881050 commit cc5ca8e
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 45 deletions.
9 changes: 5 additions & 4 deletions spec/lib/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type {
ComponentsObject,
OperationObject,
SchemasObject,
SchemasObject as SchemasObjectV30,
} from 'openapi3-ts/oas30';
import type { SchemasObject as SchemasObjectV31 } from 'openapi3-ts/oas31';
import type { ZodTypeAny } from 'zod';
import {
OpenAPIDefinitions,
Expand Down Expand Up @@ -33,10 +34,10 @@ export function createSchemas(
return components;
}

export function expectSchema(
export function expectSchema<T extends OpenApiVersion = '3.0.0'>(
zodSchemas: ZodTypeAny[],
openAPISchemas: SchemasObject,
openApiVersion: OpenApiVersion = '3.0.0'
openAPISchemas: T extends '3.1.0' ? SchemasObjectV31 : SchemasObjectV30,
openApiVersion?: T
) {
const components = createSchemas(zodSchemas, openApiVersion);

Expand Down
59 changes: 43 additions & 16 deletions spec/types/tuple.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from 'zod';
import { expectSchema } from '../lib/helpers';

describe('tuple', () => {
it('supports tuples', () => {
it('supports tuples in 3.0.0', () => {
expectSchema(
[z.tuple([z.string(), z.number(), z.boolean()]).openapi('Test')],
{
Expand All @@ -18,7 +18,25 @@ describe('tuple', () => {
minItems: 3,
maxItems: 3,
},
}
},
'3.0.0'
);
});

it('supports tuples in 3.1.0', () => {
expectSchema(
[z.tuple([z.string(), z.number(), z.boolean()]).openapi('Test')],
{
Test: {
type: 'array',
prefixItems: [
{ type: 'string' },
{ type: 'number' },
{ type: 'boolean' },
],
},
},
'3.1.0'
);
});

Expand All @@ -35,7 +53,7 @@ describe('tuple', () => {
});
});

it('supports tuples of duplicate types', () => {
it('supports tuples of duplicate types in 3.0.0', () => {
expectSchema(
[z.tuple([z.string(), z.number(), z.string()]).openapi('Test')],
{
Expand All @@ -51,6 +69,23 @@ describe('tuple', () => {
);
});

it('supports tuples of duplicate types in 3.1.0', () => {
expectSchema(
[z.tuple([z.string(), z.number(), z.string()]).openapi('Test')],
{
Test: {
type: 'array',
prefixItems: [
{ type: 'string' },
{ type: 'number' },
{ type: 'string' },
],
},
},
'3.1.0'
);
});

it('supports tuples of referenced schemas', () => {
const stringSchema = z.string().openapi('String');

Expand Down Expand Up @@ -134,14 +169,10 @@ describe('tuple', () => {
{
Test: {
type: 'array',
items: {
anyOf: [
{ type: ['string', 'null'] },
{ type: ['number', 'null'] },
],
},
minItems: 2,
maxItems: 2,
prefixItems: [
{ type: ['string', 'null'] },
{ type: ['number', 'null'] },
],
},
},
'3.1.0'
Expand Down Expand Up @@ -172,11 +203,7 @@ describe('tuple', () => {
{
Test: {
type: ['array', 'null'],
items: {
anyOf: [{ type: 'string' }, { type: 'number' }],
},
minItems: 2,
maxItems: 2,
prefixItems: [{ type: 'string' }, { type: 'number' }],
},
},
'3.1.0'
Expand Down
15 changes: 14 additions & 1 deletion src/openapi-generator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import type { AnyZodObject, ZodRawShape, ZodType, ZodTypeAny } from 'zod';
import type {
AnyZodObject,
ZodRawShape,
ZodTuple,
ZodType,
ZodTypeAny,
} from 'zod';
import {
ConflictError,
MissingParameterDataError,
Expand Down Expand Up @@ -60,6 +66,13 @@ export interface OpenApiVersionSpecifics {
isNullable: boolean
): Pick<SchemaObject, 'type' | 'nullable'>;

mapTupleItems(schemas: (SchemaObject | ReferenceObject)[]): {
items?: SchemaObject | ReferenceObject;
minItems?: number;
maxItems?: number;
prefixItems?: (SchemaObject | ReferenceObject)[];
};

getNumberChecks(checks: ZodNumericCheck[]): any;
}

Expand Down
6 changes: 4 additions & 2 deletions src/transformers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ export class OpenApiTransformer {
private enumTransformer = new EnumTransformer();
private nativeEnumTransformer = new NativeEnumTransformer();
private arrayTransformer = new ArrayTransformer();
private tupleTransformer = new TupleTransformer();
private tupleTransformer: TupleTransformer;
private unionTransformer = new UnionTransformer();
private discriminatedUnionTransformer = new DiscriminatedUnionTransformer();
private intersectionTransformer = new IntersectionTransformer();
private recordTransformer = new RecordTransformer();

constructor(private versionSpecifics: OpenApiVersionSpecifics) {}
constructor(private versionSpecifics: OpenApiVersionSpecifics) {
this.tupleTransformer = new TupleTransformer(versionSpecifics);
}

transform<T>(
zodSchema: ZodType<T>,
Expand Down
25 changes: 5 additions & 20 deletions src/transformers/tuple.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
import { MapNullableType, MapSubSchema, SchemaObject } from '../types';
import { ZodTuple } from 'zod';
import { uniq } from '../lib/lodash';
import { OpenApiVersionSpecifics } from '../openapi-generator';

export class TupleTransformer {
constructor(private versionSpecifics: OpenApiVersionSpecifics) {}

transform(
zodSchema: ZodTuple,
mapNullableType: MapNullableType,
mapItem: MapSubSchema
): SchemaObject {
const { items } = zodSchema._def;

const tupleLength = items.length;

const schemas = items.map(schema => mapItem(schema));

const uniqueSchemas = uniq(schemas);

if (uniqueSchemas.length === 1) {
return {
type: 'array',
items: uniqueSchemas[0],
minItems: tupleLength,
maxItems: tupleLength,
};
}
const schemas = items.map(mapItem);

return {
...mapNullableType('array'),
items: {
anyOf: uniqueSchemas,
},
minItems: tupleLength,
maxItems: tupleLength,
...this.versionSpecifics.mapTupleItems(schemas),
};
}
}
16 changes: 15 additions & 1 deletion src/v3.0/specifics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ReferenceObject, SchemaObject } from 'openapi3-ts/oas30';
import { OpenApiVersionSpecifics } from '../openapi-generator';
import { ZodNumericCheck } from '../types';
import { ZodNumericCheck, SchemaObject as CommonSchemaObject } from '../types';
import { uniq } from '../lib/lodash';

export class OpenApiGeneratorV30Specifics implements OpenApiVersionSpecifics {
get nullType() {
Expand All @@ -27,6 +28,19 @@ export class OpenApiGeneratorV30Specifics implements OpenApiVersionSpecifics {
};
}

mapTupleItems(schemas: (CommonSchemaObject | ReferenceObject)[]) {
const uniqueSchemas = uniq(schemas);

return {
items:
uniqueSchemas.length === 1
? uniqueSchemas[0]
: { anyOf: uniqueSchemas },
minItems: schemas.length,
maxItems: schemas.length,
};
}

getNumberChecks(
checks: ZodNumericCheck[]
): Pick<
Expand Down
8 changes: 7 additions & 1 deletion src/v3.1/specifics.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ReferenceObject, SchemaObject } from 'openapi3-ts/oas31';

import { OpenApiVersionSpecifics } from '../openapi-generator';
import { ZodNumericCheck } from '../types';
import { ZodNumericCheck, SchemaObject as CommonSchemaObject } from '../types';

export class OpenApiGeneratorV31Specifics implements OpenApiVersionSpecifics {
get nullType() {
Expand Down Expand Up @@ -39,6 +39,12 @@ export class OpenApiGeneratorV31Specifics implements OpenApiVersionSpecifics {
};
}

mapTupleItems(schemas: (CommonSchemaObject | ReferenceObject)[]) {
return {
prefixItems: schemas,
};
}

getNumberChecks(
checks: ZodNumericCheck[]
): Pick<
Expand Down

0 comments on commit cc5ca8e

Please sign in to comment.