UNPKG

@alexop/openapi-zod-client

Version:

[![Screenshot 2022-11-12 at 18 52 25](https://user-images.githubusercontent.com/47224540/201487856-ffc4c862-6f31-4de1-8ef1-3981fabf3416.png)](https://openapi-zod-client.vercel.app/)

338 lines (312 loc) 13.2 kB
import type { SchemaObject } from "openapi3-ts"; import { expect, test } from "vitest"; import { getZodSchema } from "./openApiToZod"; import type { CodeMetaData, ConversionTypeContext } from "./CodeMeta"; import { makeSchemaResolver } from "./makeSchemaResolver"; import { asComponentSchema } from "./utils"; const makeSchema = (schema: SchemaObject) => schema; const getSchemaAsZodString = (schema: SchemaObject, meta?: CodeMetaData | undefined) => getZodSchema({ schema: makeSchema(schema), meta }).toString(); test("getSchemaAsZodString", () => { expect(getSchemaAsZodString({ type: "null" })).toMatchInlineSnapshot('"z.null()"'); expect(getSchemaAsZodString({ type: "null", enum: ["Dogs", "Cats", "Mice"] })).toMatchInlineSnapshot('"z.null()"'); expect(getSchemaAsZodString({ type: "boolean" })).toMatchInlineSnapshot('"z.boolean()"'); expect(getSchemaAsZodString({ type: "string" })).toMatchInlineSnapshot('"z.string()"'); expect(getSchemaAsZodString({ type: "number" })).toMatchInlineSnapshot('"z.number()"'); expect(getSchemaAsZodString({ type: "integer" })).toMatchInlineSnapshot('"z.number()"'); // expect(getSchemaAsZodString({ type: "string", format: "date-time" })).toMatchInlineSnapshot('"z.string().datetime()"'); // expect(getSchemaAsZodString({ type: "number", nullable: true, minimum: 0 })).toMatchInlineSnapshot('"z.number().nullable().gte(0)"'); expect(getSchemaAsZodString({ type: "array", items: { type: "string" } })).toMatchInlineSnapshot( '"z.array(z.string())"' ); expect(getSchemaAsZodString({ type: "object" })).toMatchInlineSnapshot('"z.object({}).partial().passthrough()"'); expect(getSchemaAsZodString({ type: "object", properties: { str: { type: "string" } } })).toMatchInlineSnapshot( '"z.object({ str: z.string() }).partial().passthrough()"' ); expect(getSchemaAsZodString({ type: "object", properties: { str: { type: "string" } } })).toMatchInlineSnapshot( '"z.object({ str: z.string() }).partial().passthrough()"' ); expect(getSchemaAsZodString({ type: "object", properties: { nb: { type: "integer" } } })).toMatchInlineSnapshot( '"z.object({ nb: z.number().int() }).partial().passthrough()"' ); expect( getSchemaAsZodString({ type: "object", properties: { pa: { type: "number", minimum: 0 } } }) ).toMatchInlineSnapshot('"z.object({ pa: z.number().gte(0) }).partial().passthrough()"'); expect( getSchemaAsZodString({ type: "object", properties: { pa: { type: "number", minimum: 0, maximum: 100 } } }) ).toMatchInlineSnapshot('"z.object({ pa: z.number().gte(0).lte(100) }).partial().passthrough()"'); expect( getSchemaAsZodString({ type: "object", properties: { ml: { type: "string", minLength: 0 } } }) ).toMatchInlineSnapshot('"z.object({ ml: z.string().min(0) }).partial().passthrough()"'); expect( getSchemaAsZodString({ type: "object", properties: { dt: { type: "string", format: "date-time" } } }) ).toMatchInlineSnapshot('"z.object({ dt: z.string().datetime({ offset: true }) }).partial().passthrough()"'); expect( getSchemaAsZodString({ type: "object", properties: { str: { type: "string" }, nb: { type: "number" }, nested: { type: "object", properties: { nested_prop: { type: "boolean" }, }, }, }, }) ).toMatchInlineSnapshot( '"z.object({ str: z.string(), nb: z.number(), nested: z.object({ nested_prop: z.boolean() }).partial().passthrough() }).partial().passthrough()"' ); expect( getSchemaAsZodString({ type: "array", items: { type: "object", properties: { str: { type: "string" }, }, }, }) ).toMatchInlineSnapshot('"z.array(z.object({ str: z.string() }).partial().passthrough())"'); expect( getSchemaAsZodString({ type: "array", items: { type: "array", items: { type: "string", }, }, }) ).toMatchInlineSnapshot('"z.array(z.array(z.string()))"'); expect( getSchemaAsZodString({ type: "object", properties: { union: { oneOf: [{ type: "string" }, { type: "number" }] }, }, }) ).toMatchInlineSnapshot('"z.object({ union: z.union([z.string(), z.number()]) }).partial().passthrough()"'); expect( getSchemaAsZodString({ type: "object", oneOf: [ { type: "object", required: ["type", "a"], properties: { type: { type: "string", enum: ["a"], }, a: { type: "string", }, }, }, { type: "object", required: ["type", "b"], properties: { type: { type: "string", enum: ["b"], }, b: { type: "string", }, }, }, ], discriminator: { propertyName: "type" }, }) ).toMatchInlineSnapshot(` " z.discriminatedUnion("type", [z.object({ type: z.literal("a"), a: z.string() }).passthrough(), z.object({ type: z.literal("b"), b: z.string() }).passthrough()]) " `); expect( getSchemaAsZodString({ type: "object", properties: { anyOfExample: { anyOf: [{ type: "string" }, { type: "number" }] }, }, }) ).toMatchInlineSnapshot( '"z.object({ anyOfExample: z.union([z.string(), z.number()]) }).partial().passthrough()"' ); expect( getSchemaAsZodString({ type: "object", properties: { intersection: { allOf: [{ type: "string" }, { type: "number" }] }, }, }) ).toMatchInlineSnapshot('"z.object({ intersection: z.string().and(z.number()) }).partial().passthrough()"'); expect(getSchemaAsZodString({ type: "string", enum: ["aaa", "bbb", "ccc"] })).toMatchInlineSnapshot( '"z.enum(["aaa", "bbb", "ccc"])"' ); expect(getSchemaAsZodString({ type: "number", enum: [1, 2, 3, null] })).toMatchInlineSnapshot( '"z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(null)])"' ); expect(getSchemaAsZodString({ type: "number", enum: [1] })).toMatchInlineSnapshot('"z.literal(1)"'); expect(getSchemaAsZodString({ type: "string", enum: ["aString"] })).toMatchInlineSnapshot('"z.literal("aString")"'); }); test("getSchemaWithChainableAsZodString", () => { expect(getSchemaAsZodString({ type: "string", nullable: true })).toMatchInlineSnapshot('"z.string()"'); expect(getSchemaAsZodString({ type: "string", nullable: false })).toMatchInlineSnapshot('"z.string()"'); expect(getSchemaAsZodString({ type: "string", nullable: false }, { isRequired: true })).toMatchInlineSnapshot( '"z.string()"' ); expect(getSchemaAsZodString({ type: "string", nullable: true }, { isRequired: true })).toMatchInlineSnapshot( '"z.string()"' ); }); test("CodeMeta with missing ref", () => { const ctx: ConversionTypeContext = { resolver: makeSchemaResolver({ components: { schemas: {} } } as any), zodSchemaByName: {}, schemaByName: {}, }; expect(() => getZodSchema({ schema: makeSchema({ type: "object", properties: { str: { type: "string" }, reference: { $ref: "Example", }, inline: { type: "object", properties: { nested_prop: { type: "boolean" }, }, }, }, }), ctx, }) ).toThrowErrorMatchingInlineSnapshot('"Schema Example not found"'); }); test("CodeMeta with ref", () => { const schemas = { Example: { type: "object", properties: { exampleProp: { type: "string" }, another: { type: "number" }, }, }, } as Record<string, SchemaObject>; const ctx: ConversionTypeContext = { resolver: makeSchemaResolver({ components: { schemas } } as any), zodSchemaByName: {}, schemaByName: {}, }; Object.keys(schemas).forEach((key) => ctx.resolver.getSchemaByRef(asComponentSchema(key))); const code = getZodSchema({ schema: makeSchema({ type: "object", properties: { str: { type: "string" }, reference: { $ref: "#/components/schemas/Example", }, inline: { type: "object", properties: { nested_prop: { type: "boolean" }, }, }, }, }), ctx, }); expect(code.toString()).toMatchInlineSnapshot( '"z.object({ str: z.string(), reference: Example, inline: z.object({ nested_prop: z.boolean() }).partial().passthrough() }).partial().passthrough()"' ); expect(code.children).toMatchInlineSnapshot(` [ "z.string()", "Example", "z.object({ nested_prop: z.boolean() }).partial().passthrough()", ] `); }); test("CodeMeta with nested refs", () => { const schemas = { Basic: { type: "object", properties: { prop: { type: "string" }, second: { type: "number" } } }, WithNested: { type: "object", properties: { nested: { type: "string" }, nestedRef: { $ref: "#/components/schemas/DeepNested" } }, }, ObjectWithArrayOfRef: { type: "object", properties: { exampleProp: { type: "string" }, another: { type: "number" }, link: { type: "array", items: { $ref: "#/components/schemas/WithNested" } }, someReference: { $ref: "#/components/schemas/Basic" }, }, }, DeepNested: { type: "object", properties: { deep: { type: "boolean" } } }, } as Record<string, SchemaObject>; const ctx: ConversionTypeContext = { resolver: makeSchemaResolver({ components: { schemas } } as any), zodSchemaByName: {}, schemaByName: {}, }; Object.keys(schemas).forEach((key) => ctx.resolver.getSchemaByRef(asComponentSchema(key))); const code = getZodSchema({ schema: makeSchema({ type: "object", properties: { str: { type: "string" }, reference: { $ref: "#/components/schemas/ObjectWithArrayOfRef", }, inline: { type: "object", properties: { nested_prop: { type: "boolean" }, }, }, another: { $ref: "#components/schemas/WithNested" }, basic: { $ref: "#/components/schemas/Basic" }, differentPropSameRef: { $ref: "#/components/schemas/Basic" }, }, }), ctx, }); expect(code.toString()).toMatchInlineSnapshot( '"z.object({ str: z.string(), reference: ObjectWithArrayOfRef, inline: z.object({ nested_prop: z.boolean() }).partial().passthrough(), another: WithNested, basic: Basic, differentPropSameRef: Basic }).partial().passthrough()"' ); expect(code.children).toMatchInlineSnapshot(` [ "z.string()", "ObjectWithArrayOfRef", "z.object({ nested_prop: z.boolean() }).partial().passthrough()", "WithNested", "Basic", "Basic", ] `); expect(ctx).toMatchInlineSnapshot(` { "resolver": { "getSchemaByRef": [Function], "resolveRef": [Function], "resolveSchemaName": [Function], }, "schemaByName": {}, "zodSchemaByName": { "Basic": "z.object({ prop: z.string(), second: z.number() }).partial().passthrough()", "DeepNested": "z.object({ deep: z.boolean() }).partial().passthrough()", "ObjectWithArrayOfRef": "z.object({ exampleProp: z.string(), another: z.number(), link: z.array(WithNested), someReference: Basic }).partial().passthrough()", "WithNested": "z.object({ nested: z.string(), nestedRef: DeepNested }).partial().passthrough()", }, } `); });