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/)

916 lines (858 loc) 32.5 kB
import {getTypescriptFromOpenApi, TsConversionContext} from "./openApiToTypescript"; import type {SchemaObject, SchemasObject} from "openapi3-ts"; import {ts} from "tanu"; import {describe, expect, test} from "vitest"; import {makeSchemaResolver} from "./makeSchemaResolver"; import {asComponentSchema} from "./utils"; import type {TemplateContext} from "./template-context"; import type {OpenAPIV3} from "openapi-types"; const makeSchema = (schema: SchemaObject | OpenAPIV3.SchemaObject) => schema as SchemaObject; const getSchemaAsTsString = (schema: SchemaObject, meta?: { name: string }, options?: TemplateContext["options"]) => printTs(getTypescriptFromOpenApi({ schema: makeSchema(schema), meta, options }) as ts.Node); const file = ts.createSourceFile("", "", ts.ScriptTarget.ESNext, true); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); const printTs = (node: ts.Node) => printer.printNode(ts.EmitHint.Unspecified, node, file); test("getSchemaAsTsString", () => { expect(getSchemaAsTsString({ type: "null" })).toMatchInlineSnapshot('"null"'); expect(getSchemaAsTsString({ type: "boolean" })).toMatchInlineSnapshot('"boolean"'); expect(getSchemaAsTsString({ type: "string" })).toMatchInlineSnapshot('"string"'); expect(getSchemaAsTsString({ type: "number" })).toMatchInlineSnapshot('"number"'); expect(getSchemaAsTsString({ type: "integer" })).toMatchInlineSnapshot('"number"'); expect(getSchemaAsTsString({})).toMatchInlineSnapshot('"unknown"'); expect(getSchemaAsTsString({ type: "null" }, { name: "nullType" })).toMatchInlineSnapshot( '"export type nullType = null;"' ); expect(getSchemaAsTsString({ type: "boolean" }, { name: "booleanType" })).toMatchInlineSnapshot( '"export type booleanType = boolean;"' ); expect(getSchemaAsTsString({ type: "string" }, { name: "stringType" })).toMatchInlineSnapshot( '"export type stringType = string;"' ); expect(getSchemaAsTsString({ type: "number" }, { name: "numberType" })).toMatchInlineSnapshot( '"export type numberType = number;"' ); expect(getSchemaAsTsString({ type: "integer" }, { name: "integerType" })).toMatchInlineSnapshot( '"export type integerType = number;"' ); expect(getSchemaAsTsString({}, { name: "unknownType" })).toMatchInlineSnapshot( '"export type unknownType = unknown;"' ); expect(getSchemaAsTsString({ type: "array", items: { type: "string" } })).toMatchInlineSnapshot('"Array<string>"'); expect(getSchemaAsTsString({ type: "object" }, { name: "EmptyObject" })).toMatchInlineSnapshot( '"export type EmptyObject = {};"' ); expect(getSchemaAsTsString({ type: "object", properties: { str: { type: "string" } } }, { name: "BasicObject" })) .toMatchInlineSnapshot(` "export type BasicObject = Partial<{ str: string; }>;" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" }, nb: { type: "number" } } }, { name: "BasicObject2" } ) ).toMatchInlineSnapshot(` "export type BasicObject2 = Partial<{ str: string; nb: number; }>;" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" }, nb: { type: "number" } }, required: ["str", "nb"], }, { name: "AllPropertiesRequired" } ) ).toMatchInlineSnapshot(` "export type AllPropertiesRequired = { str: string; nb: number; };" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" }, nb: { type: "number" } }, required: ["str"] }, { name: "SomeOptionalProps" } ) ).toMatchInlineSnapshot(` "export type SomeOptionalProps = { str: string; nb?: number | undefined; };" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" }, nb: { type: "number" }, nested: { type: "object", properties: { nested_prop: { type: "boolean" }, }, }, }, }, { name: "ObjectWithNestedProp" } ) ).toMatchInlineSnapshot(` "export type ObjectWithNestedProp = Partial<{ str: string; nb: number; nested: Partial<{ nested_prop: boolean; }>; }>;" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" } }, additionalProperties: { type: "number" } }, { name: "ObjectWithAdditionalPropsNb" } ) ).toMatchInlineSnapshot(` "export type ObjectWithAdditionalPropsNb = Partial<{ str: string; } & { [key: string]: number; }>;" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" } }, additionalProperties: { type: "object", properties: { prop: { type: "boolean" } } }, }, { name: "ObjectWithNestedRecordBoolean" } ) ).toMatchInlineSnapshot(` "export type ObjectWithNestedRecordBoolean = Partial<{ str: string; } & { [key: string]: Partial<{ prop: boolean; }>; }>;" `); expect( getSchemaAsTsString({ type: "array", items: { type: "object", properties: { str: { type: "string" }, }, }, }) ).toMatchInlineSnapshot(` "Array<Partial<{ str: string; }>>" `); expect( getSchemaAsTsString({ type: "array", items: { type: "array", items: { type: "string", }, }, }) ).toMatchInlineSnapshot('"Array<Array<string>>"'); expect( getSchemaAsTsString( { type: "object", properties: { enumprop: { type: "string", enum: ["aaa", "bbb", "ccc"] }, }, }, { name: "ObjectWithEnum" } ) ).toMatchInlineSnapshot(` "export type ObjectWithEnum = Partial<{ enumprop: "aaa" | "bbb" | "ccc"; }>;" `); expect(getSchemaAsTsString({ type: "string", enum: ["aaa", "bbb", "ccc"] })).toMatchInlineSnapshot( '""aaa" | "bbb" | "ccc""' ); expect( getSchemaAsTsString({ type: "string", enum: ["aaa", "bbb", "ccc"] }, { name: "StringENum" }) ).toMatchInlineSnapshot('"export type StringENum = "aaa" | "bbb" | "ccc";"'); expect( getSchemaAsTsString( { type: "object", properties: { union: { oneOf: [{ type: "string" }, { type: "number" }] }, }, }, { name: "ObjectWithUnion" } ) ).toMatchInlineSnapshot(` "export type ObjectWithUnion = Partial<{ union: string | number; }>;" `); expect(getSchemaAsTsString({ oneOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot( '"string | number"' ); expect( getSchemaAsTsString({ oneOf: [{ type: "string" }, { type: "number" }] }, { name: "StringOrNumber" }) ).toMatchInlineSnapshot('"export type StringOrNumber = string | number;"'); expect(getSchemaAsTsString({ allOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot( '"string & number"' ); expect( getSchemaAsTsString({ allOf: [{ type: "string" }, { type: "number" }] }, { name: "StringAndNumber" }) ).toMatchInlineSnapshot('"export type StringAndNumber = string & number;"'); expect(getSchemaAsTsString({ nullable: true, anyOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot( '"(string | number) | Array<string | number> | null"' ); expect(getSchemaAsTsString({ nullable: true, oneOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot( '"string | number | null"' ); expect( getSchemaAsTsString({ nullable: true, oneOf: [{ type: "string" }, { type: "number" }] }, { name: "StringOrNumber" }) ).toMatchInlineSnapshot('"export type StringOrNumber = string | number | null;"'); expect(getSchemaAsTsString({ nullable: true, allOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot( '"(string & number) | null"' ); expect( getSchemaAsTsString({ nullable: true, allOf: [{ type: "string" }, { type: "number" }] }, { name: "StringAndNumber" }) ).toMatchInlineSnapshot('"export type StringAndNumber = (string & number) | null;"'); expect(getSchemaAsTsString({ nullable: true, anyOf: [{ type: "string" }, { type: "number" }] })).toMatchInlineSnapshot( '"(string | number) | Array<string | number> | null"' ); expect( getSchemaAsTsString( { anyOf: [{ type: "string" }, { type: "number" }] }, { name: "StringAndNumberMaybeMultiple" } ) ).toMatchInlineSnapshot('"export type StringAndNumberMaybeMultiple = (string | number) | Array<string | number>;"'); expect( getSchemaAsTsString( { type: "object", properties: { unionOrArrayOfUnion: { anyOf: [{ type: "string" }, { type: "number" }] }, }, }, { name: "ObjectWithArrayUnion" } ) ).toMatchInlineSnapshot(` "export type ObjectWithArrayUnion = Partial<{ unionOrArrayOfUnion: (string | number) | Array<string | number>; }>;" `); expect( getSchemaAsTsString( { type: "object", properties: { intersection: { allOf: [{ type: "string" }, { type: "number" }] }, }, }, { name: "ObjectWithIntersection" } ) ).toMatchInlineSnapshot(` "export type ObjectWithIntersection = Partial<{ intersection: string & number; }>;" `); expect(getSchemaAsTsString({ type: "string", enum: ["aaa", "bbb", "ccc"] })).toMatchInlineSnapshot( '""aaa" | "bbb" | "ccc""' ); expect(getSchemaAsTsString({ type: "number", enum: [1, 2, 3] })).toMatchInlineSnapshot('"1 | 2 | 3"'); expect( getSchemaAsTsString( { type: "object", required: ["propNumber", "propString", "propBoolean"], properties: { propNumber: { type: ["number"], nullable: true, }, propString: { type: ["string"], nullable: true, }, propBoolean: { type: ["boolean"], nullable: true, }, }, }, { name: "Category" } ) ).toMatchInlineSnapshot(` "export type Category = { propNumber: number | null; propString: string | null; propBoolean: boolean | null; };" `); }); describe("getSchemaAsTsString with context", () => { test("with ref", () => { const schemas = { Root: { type: "object", properties: { str: { type: "string" }, nb: { type: "number" }, nested: { $ref: "#/components/schemas/Nested" }, }, }, Nested: { type: "object", properties: { nested_prop: { type: "boolean" }, }, }, } as SchemasObject; const ctx: TsConversionContext = { nodeByRef: {}, visitedsRefs: {}, resolver: makeSchemaResolver({ components: { schemas } } as any), }; Object.keys(schemas).forEach((key) => ctx.resolver.getSchemaByRef(asComponentSchema(key))); expect(printTs(getTypescriptFromOpenApi({ schema: schemas["Root"]!, meta: { name: "Root" }, ctx }) as ts.Node)) .toMatchInlineSnapshot(` "export type Root = Partial<{ str: string; nb: number; nested: Nested; }>;" `); }); test("with multiple nested refs", () => { const schemas = { Root2: { type: "object", properties: { str: { type: "string" }, nb: { type: "number" }, nested: { $ref: "#/components/schemas/Nested2" }, }, }, Nested2: { type: "object", properties: { nested_prop: { type: "boolean" }, deeplyNested: { $ref: "#/components/schemas/DeeplyNested" }, }, }, DeeplyNested: { type: "array", items: { $ref: "#/components/schemas/VeryDeeplyNested" }, }, VeryDeeplyNested: { type: "string", enum: ["aaa", "bbb", "ccc"], }, } as SchemasObject; const ctx: TsConversionContext = { nodeByRef: {}, visitedsRefs: {}, resolver: makeSchemaResolver({ components: { schemas } } as any), }; Object.keys(schemas).forEach((key) => ctx.resolver.getSchemaByRef(asComponentSchema(key))); expect( printTs(getTypescriptFromOpenApi({ schema: schemas["Root2"]!, meta: { name: "Root2" }, ctx }) as ts.Node) ).toMatchInlineSnapshot(` "export type Root2 = Partial<{ str: string; nb: number; nested: Nested2; }>;" `); }); test("with indirect recursive ref", async () => { const schemas = { Root3: { type: "object", properties: { str: { type: "string" }, nb: { type: "number" }, nested: { $ref: "#/components/schemas/Nested3" }, arrayOfNested: { type: "array", items: { $ref: "#/components/schemas/Nested3" } }, }, }, Nested3: { type: "object", properties: { nested_prop: { type: "boolean" }, backToRoot: { $ref: "#/components/schemas/Root3" }, }, }, } as SchemasObject; const ctx: TsConversionContext = { nodeByRef: {}, visitedsRefs: {}, resolver: makeSchemaResolver({ components: { schemas } } as any), }; Object.keys(schemas).forEach((key) => ctx.resolver.getSchemaByRef(asComponentSchema(key))); expect( printTs( getTypescriptFromOpenApi({ schema: schemas["Root3"]!, meta: { name: "Root3", $ref: "#/components/schemas/Root3" }, ctx, }) as ts.Node ) ).toMatchInlineSnapshot(` "export type Root3 = Partial<{ str: string; nb: number; nested: Nested3; arrayOfNested: Array<Nested3>; }>;" `); }); test("with direct (self) recursive ref", async () => { const schemas = { Root4: { type: "object", properties: { str: { type: "string" }, nb: { type: "number" }, self: { $ref: "#/components/schemas/Root4" }, nested: { $ref: "#/components/schemas/Nested4" }, arrayOfSelf: { type: "array", items: { $ref: "#/components/schemas/Root4" } }, }, }, Nested4: { type: "object", properties: { nested_prop: { type: "boolean" }, backToRoot: { $ref: "#/components/schemas/Root4" }, }, }, } as SchemasObject; const ctx: TsConversionContext = { nodeByRef: {}, visitedsRefs: {}, resolver: makeSchemaResolver({ components: { schemas } } as any), }; Object.keys(schemas).forEach((key) => ctx.resolver.getSchemaByRef(asComponentSchema(key))); const result = getTypescriptFromOpenApi({ schema: schemas["Root4"]!, meta: { name: "Root4", $ref: "#/components/schemas/Root4" }, ctx, }) as ts.Node; expect(printTs(result)).toMatchInlineSnapshot(` "export type Root4 = Partial<{ str: string; nb: number; self: Root4; nested: Nested4; arrayOfSelf: Array<Root4>; }>;" `); }); test("same schemas as openApiToZod", () => { const schemas = { User: { type: "object", properties: { name: { type: "string" }, middle: { $ref: "#/components/schemas/Middle" }, }, }, Middle: { type: "object", properties: { user: { $ref: "#/components/schemas/User" }, }, }, Root: { type: "object", properties: { recursive: { $ref: "#/components/schemas/User", }, basic: { type: "number" }, }, }, } as SchemasObject; const ctx: TsConversionContext = { nodeByRef: {}, visitedsRefs: {}, resolver: makeSchemaResolver({ components: { schemas } } as any), }; Object.keys(schemas).forEach((key) => ctx.resolver.getSchemaByRef(asComponentSchema(key))); const result = getTypescriptFromOpenApi({ schema: schemas["Root"]!, meta: { name: "Root", $ref: "#/components/schemas/Root" }, ctx, }) as ts.Node; expect(printTs(result)).toMatchInlineSnapshot(` "export type Root = Partial<{ recursive: User; basic: number; }>;" `); }); test("anyOf with refs", () => { const schemas = { User: { type: "object", properties: { name: { type: "string" }, }, }, Member: { type: "object", properties: { name: { type: "string" }, }, }, Root: { type: "object", properties: { user: { oneOf: [{ $ref: "#/components/schemas/User" }, { $ref: "#/components/schemas/Member" }] }, users: { type: "array", items: { anyOf: [{ $ref: "#/components/schemas/User" }, { $ref: "#/components/schemas/Member" }], }, }, basic: { type: "number" }, }, }, } as SchemasObject; const ctx: TsConversionContext = { nodeByRef: {}, visitedsRefs: {}, resolver: makeSchemaResolver({ components: { schemas } } as any), }; Object.keys(schemas).forEach((key) => ctx.resolver.getSchemaByRef(asComponentSchema(key))); const result = getTypescriptFromOpenApi({ schema: schemas["Root"]!, meta: { name: "Root", $ref: "#/components/schemas/Root" }, ctx, }) as ts.Node; expect(printTs(result)).toMatchInlineSnapshot(` "export type Root = Partial<{ user: User | Member; users: Array<(User | Member) | Array<User | Member>>; basic: number; }>;" `); }); }); test("getSchemaAsTsString with readonly", () => { const options: TemplateContext['options'] = { allReadonly: true }; expect(getSchemaAsTsString({ type: "null" }, undefined, options)).toMatchInlineSnapshot('"null"'); expect(getSchemaAsTsString({ type: "boolean" }, undefined, options)).toMatchInlineSnapshot('"boolean"'); expect(getSchemaAsTsString({ type: "string" }, undefined, options)).toMatchInlineSnapshot('"string"'); expect(getSchemaAsTsString({ type: "number" }, undefined, options)).toMatchInlineSnapshot('"number"'); expect(getSchemaAsTsString({ type: "integer" }, undefined, options)).toMatchInlineSnapshot('"number"'); expect(getSchemaAsTsString({}, undefined, options)).toMatchInlineSnapshot('"unknown"'); expect(getSchemaAsTsString({ type: "null" }, { name: "nullType" }, options)).toMatchInlineSnapshot( '"export type nullType = null;"' ); expect(getSchemaAsTsString({ type: "boolean" }, { name: "booleanType" }, options)).toMatchInlineSnapshot( '"export type booleanType = boolean;"' ); expect(getSchemaAsTsString({ type: "string" }, { name: "stringType" }, options)).toMatchInlineSnapshot( '"export type stringType = string;"' ); expect(getSchemaAsTsString({ type: "number" }, { name: "numberType" }, options)).toMatchInlineSnapshot( '"export type numberType = number;"' ); expect(getSchemaAsTsString({ type: "integer" }, { name: "integerType" }, options)).toMatchInlineSnapshot( '"export type integerType = number;"' ); expect(getSchemaAsTsString({}, { name: "unknownType" }, options)).toMatchInlineSnapshot( '"export type unknownType = unknown;"' ); expect(getSchemaAsTsString({ type: "array", items: { type: "string" } }, undefined, options)).toMatchInlineSnapshot('"Readonly<Array<string>>"'); expect(getSchemaAsTsString({ type: "object" }, { name: "EmptyObject" }, options)).toMatchInlineSnapshot( '"export type EmptyObject = {};"' ); expect(getSchemaAsTsString({ type: "object", properties: { str: { type: "string" } } }, { name: "BasicObject" }, options)) .toMatchInlineSnapshot(` "export type BasicObject = Partial<Readonly<{ str: string; }>>;" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" }, nb: { type: "number" } } }, { name: "BasicObject2" }, options ) ).toMatchInlineSnapshot(` "export type BasicObject2 = Partial<Readonly<{ str: string; nb: number; }>>;" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" }, nb: { type: "number" } }, required: ["str", "nb"], }, { name: "AllPropertiesRequired" }, options ) ).toMatchInlineSnapshot(` "export type AllPropertiesRequired = Readonly<{ str: string; nb: number; }>;" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" }, nb: { type: "number" } }, required: ["str"] }, { name: "SomeOptionalProps" }, options ) ).toMatchInlineSnapshot(` "export type SomeOptionalProps = Readonly<{ str: string; nb?: number | undefined; }>;" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" }, nb: { type: "number" }, nested: { type: "object", properties: { nested_prop: { type: "boolean" }, }, }, }, }, { name: "ObjectWithNestedProp" }, options ) ).toMatchInlineSnapshot(` "export type ObjectWithNestedProp = Partial<Readonly<{ str: string; nb: number; nested: Partial<Readonly<{ nested_prop: boolean; }>>; }>>;" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" } }, additionalProperties: { type: "number" } }, { name: "ObjectWithAdditionalPropsNb" }, options ) ).toMatchInlineSnapshot(` "export type ObjectWithAdditionalPropsNb = Partial<Readonly<{ str: string; } & { [key: string]: number; }>>;" `); expect( getSchemaAsTsString( { type: "object", properties: { str: { type: "string" } }, additionalProperties: { type: "object", properties: { prop: { type: "boolean" } } }, }, { name: "ObjectWithNestedRecordBoolean" }, options ) ).toMatchInlineSnapshot(` "export type ObjectWithNestedRecordBoolean = Partial<Readonly<{ str: string; } & { [key: string]: Partial<Readonly<{ prop: boolean; }>>; }>>;" `); expect( getSchemaAsTsString({ type: "array", items: { type: "object", properties: { str: { type: "string" }, }, }, }, undefined, options) ).toMatchInlineSnapshot(` "Readonly<Array<Partial<Readonly<{ str: string; }>>>>" `); expect( getSchemaAsTsString({ type: "array", items: { type: "array", items: { type: "string", }, }, }, undefined, options) ).toMatchInlineSnapshot('"Readonly<Array<Readonly<Array<string>>>>"'); expect( getSchemaAsTsString( { type: "object", properties: { enumprop: { type: "string", enum: ["aaa", "bbb", "ccc"] }, }, }, { name: "ObjectWithEnum" }, options ) ).toMatchInlineSnapshot(` "export type ObjectWithEnum = Partial<Readonly<{ enumprop: "aaa" | "bbb" | "ccc"; }>>;" `); expect(getSchemaAsTsString({ type: "string", enum: ["aaa", "bbb", "ccc"] }, undefined, options)).toMatchInlineSnapshot( '""aaa" | "bbb" | "ccc""' ); expect( getSchemaAsTsString({ type: "string", enum: ["aaa", "bbb", "ccc"] }, { name: "StringENum" }, options) ).toMatchInlineSnapshot('"export type StringENum = "aaa" | "bbb" | "ccc";"'); expect( getSchemaAsTsString( { type: "object", properties: { union: { oneOf: [{ type: "string" }, { type: "number" }] }, }, }, { name: "ObjectWithUnion" }, options ) ).toMatchInlineSnapshot(` "export type ObjectWithUnion = Partial<Readonly<{ union: string | number; }>>;" `); expect(getSchemaAsTsString({ oneOf: [{ type: "string" }, { type: "number" }] }, undefined, options)).toMatchInlineSnapshot( '"string | number"' ); expect( getSchemaAsTsString({ oneOf: [{ type: "string" }, { type: "number" }] }, { name: "StringOrNumber" }, options) ).toMatchInlineSnapshot('"export type StringOrNumber = string | number;"'); expect(getSchemaAsTsString({ allOf: [{ type: "string" }, { type: "number" }] }, undefined, options)).toMatchInlineSnapshot( '"string & number"' ); expect( getSchemaAsTsString({ allOf: [{ type: "string" }, { type: "number" }] }, { name: "StringAndNumber" }, options) ).toMatchInlineSnapshot('"export type StringAndNumber = string & number;"'); expect(getSchemaAsTsString({ nullable: true, anyOf: [{ type: "string" }, { type: "number" }] }, undefined, options)).toMatchInlineSnapshot( '"(string | number) | Readonly<Array<string | number>> | null"' ); expect(getSchemaAsTsString({ nullable: true, oneOf: [{ type: "string" }, { type: "number" }] }, undefined, options)).toMatchInlineSnapshot( '"string | number | null"' ); expect( getSchemaAsTsString({ nullable: true, oneOf: [{ type: "string" }, { type: "number" }] }, { name: "StringOrNumber" }, options) ).toMatchInlineSnapshot('"export type StringOrNumber = string | number | null;"'); expect(getSchemaAsTsString({ nullable: true, allOf: [{ type: "string" }, { type: "number" }] }, undefined, options)).toMatchInlineSnapshot( '"(string & number) | null"' ); expect( getSchemaAsTsString({ nullable: true, allOf: [{ type: "string" }, { type: "number" }] }, { name: "StringAndNumber" }, options) ).toMatchInlineSnapshot('"export type StringAndNumber = (string & number) | null;"'); expect(getSchemaAsTsString({ nullable: true, anyOf: [{ type: "string" }, { type: "number" }] }, undefined, options)).toMatchInlineSnapshot( '"(string | number) | Readonly<Array<string | number>> | null"' ); expect( getSchemaAsTsString( { anyOf: [{ type: "string" }, { type: "number" }] }, { name: "StringAndNumberMaybeMultiple" }, options ) ).toMatchInlineSnapshot('"export type StringAndNumberMaybeMultiple = (string | number) | Readonly<Array<string | number>>;"'); expect( getSchemaAsTsString( { type: "object", properties: { unionOrArrayOfUnion: { anyOf: [{ type: "string" }, { type: "number" }] }, }, }, { name: "ObjectWithArrayUnion" }, options ) ).toMatchInlineSnapshot(` "export type ObjectWithArrayUnion = Partial<Readonly<{ unionOrArrayOfUnion: (string | number) | Readonly<Array<string | number>>; }>>;" `); expect( getSchemaAsTsString( { type: "object", properties: { intersection: { allOf: [{ type: "string" }, { type: "number" }] }, }, }, { name: "ObjectWithIntersection" }, options ) ).toMatchInlineSnapshot(` "export type ObjectWithIntersection = Partial<Readonly<{ intersection: string & number; }>>;" `); expect(getSchemaAsTsString({ type: "string", enum: ["aaa", "bbb", "ccc"] }, undefined, options)).toMatchInlineSnapshot( '""aaa" | "bbb" | "ccc""' ); expect(getSchemaAsTsString({ type: "number", enum: [1, 2, 3] }, undefined, options)).toMatchInlineSnapshot('"1 | 2 | 3"'); expect( getSchemaAsTsString( { type: "object", required: ["propNumber", "propString", "propBoolean"], properties: { propNumber: { type: ["number"], nullable: true, }, propString: { type: ["string"], nullable: true, }, propBoolean: { type: ["boolean"], nullable: true, }, }, }, { name: "Category" }, options ) ).toMatchInlineSnapshot(` "export type Category = Readonly<{ propNumber: number | null; propString: string | null; propBoolean: boolean | null; }>;" `); });