fastify-zod
Advanced tools
Comparing version 0.0.2 to 0.1.0
@@ -85,4 +85,64 @@ "use strict"; | ||
}); | ||
test(`mergeRefs:`, () => { | ||
let FooFizzEnum; | ||
(function (FooFizzEnum) { | ||
FooFizzEnum["Foo"] = "Bar"; | ||
FooFizzEnum["Fizz"] = "Buzz"; | ||
})(FooFizzEnum || (FooFizzEnum = {})); | ||
const FooFizz = _zod.z.nativeEnum(FooFizzEnum); | ||
const FooFizzItem = _zod.z.object({ | ||
value: FooFizz | ||
}); | ||
const unmerged = (0, _.buildJsonSchemas)({ | ||
FooFizz, | ||
FooFizzItem | ||
}); | ||
expect(unmerged.schemas).toEqual([{ | ||
$id: `FooFizzItem`, | ||
$schema: `http://json-schema.org/draft-07/schema#`, | ||
type: `object`, | ||
properties: { | ||
value: { | ||
type: `string`, | ||
enum: [`Bar`, `Buzz`] | ||
} | ||
}, | ||
required: [`value`], | ||
additionalProperties: false | ||
}, { | ||
$id: `FooFizz`, | ||
$schema: `http://json-schema.org/draft-07/schema#`, | ||
type: `string`, | ||
enum: [`Bar`, `Buzz`] | ||
}]); | ||
const merged = (0, _.buildJsonSchemas)({ | ||
FooFizz, | ||
FooFizzItem | ||
}, { | ||
mergeRefs: true | ||
}); | ||
expect(merged.schemas).toEqual([{ | ||
$id: `FooFizzItem`, | ||
$schema: `http://json-schema.org/draft-07/schema#`, | ||
type: `object`, | ||
properties: { | ||
value: { | ||
$ref: `FooFizz#` | ||
} | ||
}, | ||
required: [`value`], | ||
additionalProperties: false | ||
}, { | ||
$id: `FooFizz`, | ||
$schema: `http://json-schema.org/draft-07/schema#`, | ||
type: `string`, | ||
enum: [`Bar`, `Buzz`] | ||
}]); | ||
}); | ||
} | ||
}); | ||
//# sourceMappingURL=buildJsonSchemas.test.js.map |
import { FastifyInstance } from "fastify"; | ||
import { z } from "zod"; | ||
export declare const TodoItemId: z.ZodObject<{ | ||
id: z.ZodString; | ||
}, "strip", z.ZodTypeAny, { | ||
id: string; | ||
}, { | ||
id: string; | ||
}>; | ||
export declare type TodoItemId = z.infer<typeof TodoItemId>; | ||
export declare const TodoItem: z.ZodObject<z.extendShape<{ | ||
id: z.ZodString; | ||
}, { | ||
label: z.ZodString; | ||
dueDate: z.ZodOptional<z.ZodDate>; | ||
state: z.ZodUnion<[z.ZodLiteral<"todo">, z.ZodLiteral<"in progress">, z.ZodLiteral<"done">]>; | ||
}>, "strip", z.ZodTypeAny, { | ||
dueDate?: Date | undefined; | ||
id: string; | ||
label: string; | ||
state: "done" | "todo" | "in progress"; | ||
}, { | ||
dueDate?: Date | undefined; | ||
id: string; | ||
label: string; | ||
state: "done" | "todo" | "in progress"; | ||
}>; | ||
export declare type TodoItem = z.infer<typeof TodoItem>; | ||
export declare const TodoItems: z.ZodArray<z.ZodObject<z.extendShape<{ | ||
id: z.ZodString; | ||
}, { | ||
label: z.ZodString; | ||
dueDate: z.ZodOptional<z.ZodDate>; | ||
state: z.ZodUnion<[z.ZodLiteral<"todo">, z.ZodLiteral<"in progress">, z.ZodLiteral<"done">]>; | ||
}>, "strip", z.ZodTypeAny, { | ||
dueDate?: Date | undefined; | ||
id: string; | ||
label: string; | ||
state: "done" | "todo" | "in progress"; | ||
}, { | ||
dueDate?: Date | undefined; | ||
id: string; | ||
label: string; | ||
state: "done" | "todo" | "in progress"; | ||
}>, "many">; | ||
export declare type TodoItems = z.infer<typeof TodoItems>; | ||
export declare const createTestServer: () => FastifyInstance; |
@@ -6,3 +6,3 @@ "use strict"; | ||
}); | ||
exports.createTestServer = void 0; | ||
exports.createTestServer = exports.TodoItems = exports.TodoItemId = exports.TodoItem = void 0; | ||
@@ -25,2 +25,3 @@ var _fastify = _interopRequireDefault(require("fastify")); | ||
exports.TodoItemId = TodoItemId; | ||
const TodoItem = TodoItemId.extend({ | ||
@@ -31,5 +32,7 @@ label: _zod.z.string(), | ||
}); | ||
exports.TodoItem = TodoItem; | ||
const TodoItems = _zod.z.array(TodoItem); | ||
exports.TodoItems = TodoItems; | ||
const { | ||
@@ -36,0 +39,0 @@ schemas, |
@@ -1,2 +0,2 @@ | ||
import { Options } from ".."; | ||
import { BuildJsonSchemaOptions } from ".."; | ||
declare type Helpers = { | ||
@@ -6,5 +6,5 @@ $schema: Record<string, unknown>; | ||
stringEnum: (values: unknown[]) => Record<string, unknown>; | ||
options: Options; | ||
options: BuildJsonSchemaOptions; | ||
}; | ||
export declare const helpers: (target: `jsonSchema7` | `openApi3` | undefined) => Helpers; | ||
export {}; |
@@ -5,7 +5,11 @@ import { z } from "zod"; | ||
$id: $id; | ||
$schema?: string; | ||
}; | ||
declare type Target = `openApi3` | `jsonSchema7`; | ||
export declare type Options = { | ||
export declare type BuildJsonSchemaOptions = { | ||
readonly target?: Target; | ||
}; | ||
export declare type BuildJsonSchemasOptions = BuildJsonSchemaOptions & { | ||
readonly mergeRefs?: boolean; | ||
}; | ||
export declare type JsonSchemas<$id extends string> = { | ||
@@ -17,5 +21,5 @@ schemas: JsonSchema<$id>[]; | ||
}; | ||
export declare const buildJsonSchema: <$id extends string>(ZodSchema: z.ZodType<unknown>, $id: $id, { target }?: Options) => JsonSchema<$id>; | ||
export declare const buildJsonSchemas: <$id extends string>(zodSchemas: Record<$id, z.ZodType<unknown, z.ZodTypeDef, unknown>>, { target }?: Options) => JsonSchemas<$id>; | ||
export declare const buildJsonSchema: <$id extends string>(ZodSchema: z.ZodType<unknown>, $id: $id, { target }?: BuildJsonSchemaOptions) => JsonSchema<$id>; | ||
export declare const buildJsonSchemas: <$id extends string>(zodSchemas: Record<$id, z.ZodType<unknown, z.ZodTypeDef, unknown>>, { target, mergeRefs }?: BuildJsonSchemasOptions) => JsonSchemas<$id>; | ||
export declare const withRefResolver: (options: SwaggerOptions) => SwaggerOptions; | ||
export {}; |
@@ -23,13 +23,59 @@ "use strict"; | ||
const traverse = (value, visit) => { | ||
const next = visit(value); | ||
if (Array.isArray(next)) { | ||
return next.map(item => traverse(item, visit)); | ||
} | ||
if (typeof next === `object` && next !== null) { | ||
return Object.entries(next).reduce((next, [key, value]) => ({ ...next, | ||
[key]: traverse(value, visit) | ||
}), Object.create(null)); | ||
} | ||
return next; | ||
}; | ||
const buildJsonSchemas = (zodSchemas, { | ||
target = `jsonSchema7` | ||
} = {}) => ({ | ||
schemas: Object.entries(zodSchemas).reduce((schemas, [$id, ZodSchema]) => [buildJsonSchema(ZodSchema, $id, { | ||
target | ||
}), ...schemas], []), | ||
$ref: $id => ({ | ||
$ref: `${$id}#` | ||
}) | ||
}); | ||
target = `jsonSchema7`, | ||
mergeRefs = false | ||
} = {}) => { | ||
let schemas = { | ||
schemas: Object.entries(zodSchemas).reduce((schemas, [$id, ZodSchema]) => [buildJsonSchema(ZodSchema, $id, { | ||
target | ||
}), ...schemas], []), | ||
$ref: $id => ({ | ||
$ref: `${$id}#` | ||
}) | ||
}; | ||
if (mergeRefs) { | ||
let dirty = true; | ||
while (dirty) { | ||
dirty = false; | ||
schemas = traverse(schemas, value => { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
for (const { | ||
$id, | ||
$schema, | ||
...schema | ||
} of schemas.schemas) { | ||
if (value !== schema && JSON.stringify(value) === JSON.stringify(schema)) { | ||
dirty = true; | ||
return { | ||
$ref: `${$id}#` | ||
}; | ||
} | ||
} | ||
return value; | ||
}); | ||
} | ||
} | ||
return schemas; | ||
}; | ||
exports.buildJsonSchemas = buildJsonSchemas; | ||
@@ -36,0 +82,0 @@ |
{ | ||
"name": "fastify-zod", | ||
"version": "0.0.2", | ||
"version": "0.1.0", | ||
"description": "Zod integration with Fastify", | ||
@@ -32,30 +32,30 @@ "main": "build/index.js", | ||
"devDependencies": { | ||
"@babel/cli": "^7.16.7", | ||
"@babel/core": "^7.16.7", | ||
"@babel/preset-env": "^7.16.7", | ||
"@babel/cli": "^7.17.6", | ||
"@babel/core": "^7.17.5", | ||
"@babel/preset-env": "^7.16.11", | ||
"@babel/preset-typescript": "^7.16.7", | ||
"@types/http-errors": "^1.8.1", | ||
"@types/jest": "^27.4.0", | ||
"@types/node": "^17.0.8", | ||
"@typescript-eslint/eslint-plugin": "^5.9.0", | ||
"@typescript-eslint/parser": "^5.9.0", | ||
"eslint": "^8.6.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"@types/http-errors": "^1.8.2", | ||
"@types/jest": "^27.4.1", | ||
"@types/node": "^17.0.21", | ||
"@typescript-eslint/eslint-plugin": "^5.12.1", | ||
"@typescript-eslint/parser": "^5.12.1", | ||
"eslint": "^8.9.0", | ||
"eslint-config-prettier": "^8.4.0", | ||
"eslint-plugin-import": "^2.25.4", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"fastify": "^3.25.3", | ||
"fastify-swagger": "^4.13.0", | ||
"fastify": "^3.27.2", | ||
"fastify-swagger": "^4.15.0", | ||
"http-errors": "^1.8.0", | ||
"jest": "^27.4.7", | ||
"jest": "^27.5.1", | ||
"prettier": "^2.5.1", | ||
"typescript": "^4.5.4", | ||
"zod": "^3.11.6", | ||
"typescript": "^4.5.5", | ||
"zod": "^3.12.0", | ||
"zod-to-json-schema": "^3.11.3" | ||
}, | ||
"peerDependencies": { | ||
"fastify": "^3.25.3", | ||
"fastify-swagger": "^4.13.0", | ||
"zod": "^3.11.6", | ||
"fastify": "^3.27.2", | ||
"fastify-swagger": "^4.15.0", | ||
"zod": "^3.12.0", | ||
"zod-to-json-schema": "^3.11.3" | ||
} | ||
} |
@@ -69,2 +69,56 @@ import { z } from "zod"; | ||
}); | ||
test(`mergeRefs:`, () => { | ||
enum FooFizzEnum { | ||
Foo = `Bar`, | ||
Fizz = `Buzz`, | ||
} | ||
const FooFizz = z.nativeEnum(FooFizzEnum); | ||
const FooFizzItem = z.object({ | ||
value: FooFizz, | ||
}); | ||
const unmerged = buildJsonSchemas({ FooFizz, FooFizzItem }); | ||
expect(unmerged.schemas).toEqual([ | ||
{ | ||
$id: `FooFizzItem`, | ||
$schema: `http://json-schema.org/draft-07/schema#`, | ||
type: `object`, | ||
properties: { value: { type: `string`, enum: [`Bar`, `Buzz`] } }, | ||
required: [`value`], | ||
additionalProperties: false, | ||
}, | ||
{ | ||
$id: `FooFizz`, | ||
$schema: `http://json-schema.org/draft-07/schema#`, | ||
type: `string`, | ||
enum: [`Bar`, `Buzz`], | ||
}, | ||
]); | ||
const merged = buildJsonSchemas( | ||
{ FooFizz, FooFizzItem }, | ||
{ mergeRefs: true }, | ||
); | ||
expect(merged.schemas).toEqual([ | ||
{ | ||
$id: `FooFizzItem`, | ||
$schema: `http://json-schema.org/draft-07/schema#`, | ||
type: `object`, | ||
properties: { value: { $ref: `FooFizz#` } }, | ||
required: [`value`], | ||
additionalProperties: false, | ||
}, | ||
{ | ||
$id: `FooFizz`, | ||
$schema: `http://json-schema.org/draft-07/schema#`, | ||
type: `string`, | ||
enum: [`Bar`, `Buzz`], | ||
}, | ||
]); | ||
}); | ||
} | ||
@@ -71,0 +125,0 @@ }); |
@@ -8,8 +8,8 @@ import fastify, { FastifyInstance } from "fastify"; | ||
const TodoItemId = z.object({ | ||
export const TodoItemId = z.object({ | ||
id: z.string().uuid(), | ||
}); | ||
type TodoItemId = z.infer<typeof TodoItemId>; | ||
export type TodoItemId = z.infer<typeof TodoItemId>; | ||
const TodoItem = TodoItemId.extend({ | ||
export const TodoItem = TodoItemId.extend({ | ||
label: z.string(), | ||
@@ -24,6 +24,6 @@ dueDate: z.date().optional(), | ||
type TodoItem = z.infer<typeof TodoItem>; | ||
export type TodoItem = z.infer<typeof TodoItem>; | ||
const TodoItems = z.array(TodoItem); | ||
type TodoItems = z.infer<typeof TodoItems>; | ||
export const TodoItems = z.array(TodoItem); | ||
export type TodoItems = z.infer<typeof TodoItems>; | ||
@@ -30,0 +30,0 @@ const { schemas, $ref } = buildJsonSchemas({ |
@@ -1,2 +0,2 @@ | ||
import { Options } from ".."; | ||
import { BuildJsonSchemaOptions } from ".."; | ||
@@ -7,3 +7,3 @@ type Helpers = { | ||
stringEnum: (values: unknown[]) => Record<string, unknown>; | ||
options: Options; | ||
options: BuildJsonSchemaOptions; | ||
}; | ||
@@ -10,0 +10,0 @@ |
@@ -7,2 +7,3 @@ import { z } from "zod"; | ||
$id: $id; | ||
$schema?: string; | ||
}; | ||
@@ -12,6 +13,10 @@ | ||
export type Options = { | ||
export type BuildJsonSchemaOptions = { | ||
readonly target?: Target; | ||
}; | ||
export type BuildJsonSchemasOptions = BuildJsonSchemaOptions & { | ||
readonly mergeRefs?: boolean; | ||
}; | ||
export type JsonSchemas<$id extends string> = { | ||
@@ -29,3 +34,3 @@ schemas: JsonSchema<$id>[]; | ||
$id: $id, | ||
{ target = `jsonSchema7` }: Options = {}, | ||
{ target = `jsonSchema7` }: BuildJsonSchemaOptions = {}, | ||
): JsonSchema<$id> => ({ | ||
@@ -36,18 +41,65 @@ $id, | ||
const traverse = ( | ||
value: unknown, | ||
visit: (value: unknown) => unknown, | ||
): unknown => { | ||
const next = visit(value); | ||
if (Array.isArray(next)) { | ||
return next.map((item) => traverse(item, visit)); | ||
} | ||
if (typeof next === `object` && next !== null) { | ||
return Object.entries(next).reduce( | ||
(next, [key, value]) => ({ | ||
...next, | ||
[key]: traverse(value, visit), | ||
}), | ||
Object.create(null), | ||
); | ||
} | ||
return next; | ||
}; | ||
export const buildJsonSchemas = <$id extends string>( | ||
zodSchemas: Record<$id, z.ZodType<unknown>>, | ||
{ target = `jsonSchema7` }: Options = {}, | ||
): JsonSchemas<$id> => ({ | ||
schemas: Object.entries(zodSchemas).reduce<JsonSchemas<$id>[`schemas`]>( | ||
(schemas, [$id, ZodSchema]) => [ | ||
buildJsonSchema(ZodSchema as z.ZodType<unknown>, $id as $id, { target }), | ||
...schemas, | ||
], | ||
[], | ||
), | ||
$ref: ($id) => ({ | ||
$ref: `${$id}#`, | ||
}), | ||
}); | ||
{ target = `jsonSchema7`, mergeRefs = false }: BuildJsonSchemasOptions = {}, | ||
): JsonSchemas<$id> => { | ||
let schemas: JsonSchemas<$id> = { | ||
schemas: Object.entries(zodSchemas).reduce<JsonSchemas<$id>[`schemas`]>( | ||
(schemas, [$id, ZodSchema]) => [ | ||
buildJsonSchema(ZodSchema as z.ZodType<unknown>, $id as $id, { | ||
target, | ||
}), | ||
...schemas, | ||
], | ||
[], | ||
), | ||
$ref: ($id) => ({ | ||
$ref: `${$id}#`, | ||
}), | ||
}; | ||
if (mergeRefs) { | ||
let dirty = true; | ||
while (dirty) { | ||
dirty = false; | ||
schemas = traverse(schemas, (value) => { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
for (const { $id, $schema, ...schema } of schemas.schemas) { | ||
if ( | ||
value !== schema && | ||
JSON.stringify(value) === JSON.stringify(schema) | ||
) { | ||
dirty = true; | ||
return { | ||
$ref: `${$id}#`, | ||
}; | ||
} | ||
} | ||
return value; | ||
}) as JsonSchemas<$id>; | ||
} | ||
} | ||
return schemas; | ||
}; | ||
export const withRefResolver = (options: SwaggerOptions): SwaggerOptions => | ||
@@ -54,0 +106,0 @@ ({ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
101819
1862