Skip to content

Commit

Permalink
replaced current and previous properties with symbols
Browse files Browse the repository at this point in the history
Avoid serializing them and encountering issues circular references
  • Loading branch information
alon-gb committed Dec 20, 2024
1 parent 7acdad5 commit 86c2ad5
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 27 deletions.
11 changes: 6 additions & 5 deletions src/create/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { enhanceWithMetadata } from './metadata';
import { createSchemaSwitch } from './parsers';
import { verifyEffects } from './parsers/transform';
import type { CreateSchemaOptions } from './single';

Check failure on line 15 in src/create/schema/index.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

There should be at least one empty line between import groups
import { currentSymbol, previousSymbol } from '../../extendZodTypes';

Check failure on line 16 in src/create/schema/index.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

`../../extendZodTypes` import should occur before type import of `../../openapi3-ts/dist`

export type LazyMap = Map<ZodType, true>;

Expand Down Expand Up @@ -220,16 +221,16 @@ export const createSchemaOrRef = <
return existingRef;
}

const previous = zodSchema._def.zodOpenApi?.previous
? (createSchemaOrRef(zodSchema._def.zodOpenApi.previous, state, true) as
const previous = zodSchema._def.zodOpenApi?.[previousSymbol]
? (createSchemaOrRef(zodSchema._def.zodOpenApi[previousSymbol], state, true) as

Check failure on line 225 in src/create/schema/index.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

Unsafe argument of type error typed assigned to a parameter of type `ZodType<unknown, ZodTypeDef, unknown>`
| RefObject
| undefined)
: undefined;

const current =
zodSchema._def.zodOpenApi?.current &&
zodSchema._def.zodOpenApi.current !== zodSchema
? (createSchemaOrRef(zodSchema._def.zodOpenApi.current, state, true) as
zodSchema._def.zodOpenApi?.[currentSymbol] &&
zodSchema._def.zodOpenApi[currentSymbol] !== zodSchema
? (createSchemaOrRef(zodSchema._def.zodOpenApi[currentSymbol], state, true) as

Check failure on line 233 in src/create/schema/index.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

Unsafe argument of type error typed assigned to a parameter of type `ZodType<unknown, ZodTypeDef, unknown>`
| RefObject
| undefined)
: undefined;
Expand Down
13 changes: 9 additions & 4 deletions src/create/schema/metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,10 @@ describe('enhanceWithMetadata', () => {
"coerce": false,
"typeName": "ZodString",
"zodOpenApi": {
"current": [Circular],
"openapi": {
"ref": "foo",
},
Symbol(current): [Circular],
},
},
"and": [Function],
Expand Down Expand Up @@ -339,10 +339,10 @@ describe('enhanceWithMetadata', () => {
"coerce": false,
"typeName": "ZodString",
"zodOpenApi": {
"current": [Circular],
"openapi": {
"ref": "foo",
},
Symbol(current): [Circular],
},
},
"and": [Function],
Expand Down Expand Up @@ -604,10 +604,10 @@ describe('enhanceWithMetadata', () => {
"coerce": false,
"typeName": "ZodString",
"zodOpenApi": {
"current": [Circular],
"openapi": {
"ref": "foo",
},
Symbol(current): [Circular],
},
},
"and": [Function],
Expand Down Expand Up @@ -690,10 +690,10 @@ describe('enhanceWithMetadata', () => {
"coerce": false,
"typeName": "ZodString",
"zodOpenApi": {
"current": [Circular],
"openapi": {
"ref": "foo",
},
Symbol(current): [Circular],
},
},
"and": [Function],
Expand Down Expand Up @@ -934,4 +934,9 @@ describe('enhanceWithMetadata', () => {
]
`);
});

it('does not fail JSON serialization', () => {
const FooSchema = z.string().openapi({ ref: 'foo' });
expect(()=>{JSON.stringify(FooSchema)}).not.toThrow();
});
});
11 changes: 6 additions & 5 deletions src/extendZod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { z } from 'zod';

import { createSchema } from './create/schema/single';
import { extendZodWithOpenApi } from './extendZod';
import { currentSymbol, previousSymbol } from './extendZodTypes';

extendZodWithOpenApi(z);

Expand All @@ -22,16 +23,16 @@ describe('extendZodWithOpenApi', () => {
expect(a._def.zodOpenApi?.openapi?.description).toBe('test');
expect(b._def.zodOpenApi?.openapi?.description).toBe('test2');
expect(
b._def.zodOpenApi?.previous?._def.zodOpenApi?.openapi?.description,
b._def.zodOpenApi?.[previousSymbol]?._def.zodOpenApi?.openapi?.description,
).toBe('test');
});

it('sets current metadata when a schema is used again', () => {
const a = z.string().openapi({ ref: 'a' });
const b = a.uuid();

expect(a._def.zodOpenApi?.current).toBe(a);
expect(b._def.zodOpenApi?.current).toBe(a);
expect(a._def.zodOpenApi?.[currentSymbol]).toBe(a);
expect(b._def.zodOpenApi?.[currentSymbol]).toBe(a);
});

it('adds ._def.zodOpenApi.openapi fields to a zod type', () => {
Expand All @@ -53,7 +54,7 @@ describe('extendZodWithOpenApi', () => {
const b = a.extend({ b: z.string() });

expect(a._def.zodOpenApi?.openapi?.ref).toBe('a');
expect(b._def.zodOpenApi?.previous).toStrictEqual(a);
expect(b._def.zodOpenApi?.[previousSymbol]).toStrictEqual(a);
});

it('removes previous openapi ref for an object when .omit or .pick is used', () => {
Expand All @@ -74,7 +75,7 @@ describe('extendZodWithOpenApi', () => {
});

expect(a._def.zodOpenApi?.openapi?.ref).toBe('a');
expect(b._def.zodOpenApi?.previous).toStrictEqual(a);
expect(b._def.zodOpenApi?.[previousSymbol]).toStrictEqual(a);
expect(c._def.zodOpenApi?.openapi).toEqual({});
expect(d._def.zodOpenApi?.openapi).toEqual({});

Expand Down
23 changes: 12 additions & 11 deletions src/extendZod.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ZodRawShape, ZodTypeDef, z } from 'zod';

import './extendZodTypes';

Check failure on line 3 in src/extendZod.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

'/home/runner/work/zod-openapi/zod-openapi/src/extendZodTypes.ts' imported multiple times
import { currentSymbol, previousSymbol } from './extendZodTypes';

Check failure on line 4 in src/extendZod.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

'/home/runner/work/zod-openapi/zod-openapi/src/extendZodTypes.ts' imported multiple times

type ZodOpenApiMetadataDef = NonNullable<ZodTypeDef['zodOpenApi']>;
type ZodOpenApiMetadata = ZodOpenApiMetadataDef['openapi'];
Expand Down Expand Up @@ -42,11 +43,11 @@ export function extendZodWithOpenApi(zod: typeof z) {
});

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
result._def.zodOpenApi.current = result;
result._def.zodOpenApi[currentSymbol] = result;

if (zodOpenApi) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
result._def.zodOpenApi.previous = this;
result._def.zodOpenApi[previousSymbol] = this;
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
Expand All @@ -63,13 +64,13 @@ export function extendZodWithOpenApi(zod: typeof z) {
if (def.zodOpenApi) {
const cloned = { ...def.zodOpenApi };
cloned.openapi = mergeOpenApi({ description: args[0] }, cloned.openapi);
cloned.previous = this;
cloned.current = result;
cloned[previousSymbol] = this;
cloned[currentSymbol] = result;
def.zodOpenApi = cloned;
} else {
def.zodOpenApi = {
openapi: { description: args[0] },
current: result,
[currentSymbol]: result,
};
}

Expand All @@ -88,11 +89,11 @@ export function extendZodWithOpenApi(zod: typeof z) {
if (zodOpenApi) {
const cloned = { ...zodOpenApi };
cloned.openapi = mergeOpenApi({}, cloned.openapi);
cloned.previous = this;
cloned[previousSymbol] = this;
extendResult._def.zodOpenApi = cloned;
} else {
extendResult._def.zodOpenApi = {
previous: this,
[previousSymbol]: this,
};
}

Expand All @@ -112,8 +113,8 @@ export function extendZodWithOpenApi(zod: typeof z) {
if (zodOpenApi) {
const cloned = { ...zodOpenApi };
cloned.openapi = mergeOpenApi({}, cloned.openapi);
delete cloned.previous;
delete cloned.current;
delete cloned[previousSymbol];
delete cloned[currentSymbol];
omitResult._def.zodOpenApi = cloned;
}

Expand All @@ -133,8 +134,8 @@ export function extendZodWithOpenApi(zod: typeof z) {
if (zodOpenApi) {
const cloned = { ...zodOpenApi };
cloned.openapi = mergeOpenApi({}, cloned.openapi);
delete cloned.previous;
delete cloned.current;
delete cloned[previousSymbol];
delete cloned[currentSymbol];
pickResult._def.zodOpenApi = cloned;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any
Expand Down
7 changes: 5 additions & 2 deletions src/extendZodTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ type SchemaObject = oas30.SchemaObject & oas31.SchemaObject;

type ReplaceDate<T> = T extends Date ? Date | string : T;

export const currentSymbol = Symbol("current");

Check warning on line 10 in src/extendZodTypes.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

Strings must use singlequote
export const previousSymbol = Symbol("previous");

Check warning on line 11 in src/extendZodTypes.ts

View workflow job for this annotation

GitHub Actions / Lint & Test

Strings must use singlequote

/**
* zod-openapi metadata
*/
Expand Down Expand Up @@ -77,12 +80,12 @@ interface ZodOpenApiMetadataDef {
/**
* Used to keep track of the Zod Schema had `.openapi` called on it
*/
current?: ZodTypeAny;
[currentSymbol]?: ZodTypeAny;
/**
* Used to keep track of the previous Zod Schema that had `.openapi` called on it if another `.openapi` is called.
* This can also be present when .extend is called on an object.
*/
previous?: ZodTypeAny;
[previousSymbol]?: ZodTypeAny;
}

interface ZodOpenApiExtendMetadata {
Expand Down

0 comments on commit 86c2ad5

Please sign in to comment.