schema2typebox
Version:
Creates typebox code from JSON schemas
501 lines (470 loc) • 14.7 kB
text/typescript
import { describe, expect, it } from "@jest/globals";
import { JSONSchema7 } from "json-schema";
import {
AllOfSchema,
AnyOfSchema,
ArraySchema,
ConstSchema,
EnumSchema,
MultipleTypesSchema,
NotSchema,
ObjectSchema,
OneOfSchema,
UnknownSchema,
} from "../src/schema-matchers";
import {
parseAllOf,
parseAnyOf,
parseArray,
parseConst,
parseEnum,
parseNot,
parseObject,
parseOneOf,
parseTypeName,
parseUnknown,
parseWithMultipleTypes,
} from "../src/schema-to-typebox";
import { expectEqualIgnoreFormatting } from "./util";
describe("parser unit tests", () => {
describe("parseObject() - when parsing an object schema", () => {
it("returns Type.Unknown() it the object has no properties", () => {
const dummySchema: ObjectSchema = {
type: "object",
properties: undefined,
};
const result = parseObject(dummySchema);
expect(result).toContain("Type.Unknown");
});
it("creates code with attributes for each property", async () => {
const dummySchema: ObjectSchema = {
type: "object",
properties: {
a: {
type: "number",
},
b: {
type: "string",
},
},
required: ["b"],
};
const result = parseObject(dummySchema);
const expectedResult = `Type.Object({a: Type.Optional(Type.Number()),\n b: Type.String()})`;
await expectEqualIgnoreFormatting(result, expectedResult);
});
it("works for different attribute naming schemes", async () => {
const dummySchema: ObjectSchema = {
type: "object",
properties: {
"@prop": {
type: "string",
},
"6": {
type: "boolean",
},
unquoted: {
type: "number",
},
__underscores: {
type: "string",
},
" spaces are weirdly valid ": {
type: "number",
},
"with-hyphen": {
type: "string",
},
$: {
type: "string",
},
},
required: [
"@prop",
"6",
"unquoted",
"__underscores",
" spaces are weirdly valid ",
"with-hyphen",
"$",
],
};
const result = parseObject(dummySchema);
const expectedResult = `Type.Object({"6": Type.Boolean(),\n "@prop": Type.String(),\n unquoted: Type.Number(),\n __underscores: Type.String(),\n " spaces are weirdly valid ": Type.Number(),\n "with-hyphen": Type.String(),\n $: Type.String()})`;
await expectEqualIgnoreFormatting(result, expectedResult);
});
it("creates code with schemaOptions", async () => {
const dummySchema: ObjectSchema = {
$id: "AnyStringHere",
type: "object",
properties: {
a: {
type: "number",
},
b: {
type: "string",
},
},
required: ["b"],
};
const result = parseObject(dummySchema);
const expectedResult = `Type.Object({a: Type.Optional(Type.Number()),\n b: Type.String()}, { $id: "AnyStringHere" })`;
await expectEqualIgnoreFormatting(expectedResult, result);
});
});
describe("parseEnum() - when parsing an enum schema", () => {
it("returns Type.Union()", () => {
const dummySchema: EnumSchema = {
title: "Status",
enum: ["unknown", 1, null],
};
const result = parseEnum(dummySchema);
expect(result).toContain("Type.Union");
});
it("creates code with schemaOptions", () => {
const dummySchema: EnumSchema = {
$id: "AnyStringHere",
title: "Status",
enum: ["unknown", 1, null],
};
const result = parseEnum(dummySchema);
expect(result).toContain("Type.Union");
expect(result).toContain('{"$id":"AnyStringHere"}');
});
});
describe("parseUnknown() - when parsing an empty schema", () => {
it("returns Type.Unknown()", async () => {
const dummySchema: UnknownSchema = {};
const result = parseUnknown(dummySchema);
expect(result).toEqual("Type.Unknown()");
});
});
describe("parseAnyOf() - when parsing an anyOf schema", () => {
it("returns Type.Union()", () => {
const dummySchema: AnyOfSchema = {
anyOf: [
{
type: "string",
},
{
type: "number",
},
],
};
const result = parseAnyOf(dummySchema);
expect(result).toContain("Type.Union");
});
it("creates one type per list of items inside anyOf", () => {
const dummySchema: AnyOfSchema = {
anyOf: [
{
type: "string",
},
{
type: "number",
},
],
};
const result = parseAnyOf(dummySchema);
expect(result).toContain("Type.String()");
expect(result).toContain("Type.Number()");
});
it("creates code with schemaOptions", () => {
const dummySchema: AnyOfSchema = {
$id: "AnyStringHere",
anyOf: [
{
type: "string",
},
{
type: "number",
},
],
};
const result = parseAnyOf(dummySchema);
expect(result).toContain("Type.Union");
expect(result).toContain('{"$id":"AnyStringHere"}');
});
});
describe("parseAllOf() - when parsing an allOf schema", () => {
it("returns Type.Intersect()", () => {
const schema: AllOfSchema = {
allOf: [
{
type: "string",
},
],
};
const result = parseAllOf(schema);
expect(result).toContain("Type.Intersect");
});
it("creates one type per list of items inside allOf", () => {
const schema: AllOfSchema = {
allOf: [
{
type: "string",
},
{
type: "number",
},
],
};
const result = parseAllOf(schema);
expect(result).toContain(`Type.String()`);
expect(result).toContain(`Type.Number()`);
});
it("creates code with schemaOptions", () => {
const schema: AllOfSchema = {
$id: "AnyStringHere",
allOf: [
{
type: "string",
},
],
};
const result = parseAllOf(schema);
expect(result).toContain('{"$id":"AnyStringHere"}');
});
});
describe("parseOneOf() - when parsing a oneOf schema", () => {
it("returns OneOf()", () => {
const schema: OneOfSchema = {
oneOf: [
{
type: "string",
},
],
};
const result = parseOneOf(schema);
expect(result).toContain(`OneOf`);
});
it("creates one type per list of items inside oneOf", () => {
const schema: OneOfSchema = {
oneOf: [
{
type: "string",
},
{
type: "number",
},
],
};
const result = parseOneOf(schema);
expect(result).toContain(`Type.String()`);
expect(result).toContain(`Type.Number()`);
});
it("creates code with schemaOptions", () => {
const schema: OneOfSchema = {
$id: "AnyStringHere",
oneOf: [
{
type: "string",
},
],
};
const result = parseOneOf(schema);
expect(result).toContain('{"$id":"AnyStringHere"}');
});
});
describe("parseNot() - when parsing a not schema", () => {
it("returns Type.Not()", () => {
const schema: NotSchema = {
not: {
type: "number",
},
};
const result = parseNot(schema);
expect(result).toContain(`Type.Not`);
});
it("creates code with schemaOptions", () => {
const schema: NotSchema = {
$id: "AnyStringHere",
not: {
type: "number",
},
};
const result = parseNot(schema);
expect(result).toContain('{"$id":"AnyStringHere"}');
});
});
describe("parseArray() - when parsing an array schema", () => {
describe('when "items" is not a list', () => {
it("returns Type.Array", () => {
const schema: ArraySchema = {
type: "array",
items: { type: "string" },
};
const result = parseArray(schema);
expect(result).toContain(`Type.Array`);
});
it("creates schemaOptions", () => {
const schema: ArraySchema = {
type: "array",
items: { type: "string", description: "test description" },
};
const result = parseArray(schema);
expect(result).toContain(
JSON.stringify({ description: "test description" })
);
});
});
describe('when "items" is a list', () => {
it("creates a Type.Union containing each item", () => {
const schema: ArraySchema = {
type: "array",
items: [{ type: "string" }, { type: "null" }],
};
const result = parseArray(schema);
expect(result).toContain(`Type.Array(Type.Union`);
expect(result).toContain(`Type.String`);
expect(result).toContain(`Type.Null`);
});
it("creates schemaOptions", () => {
const schema: ArraySchema = {
type: "array",
items: [
{ type: "string", description: "test description" },
{ type: "number", minimum: 1 },
],
};
const result = parseArray(schema);
expect(result).toContain(
JSON.stringify({ description: "test description" })
);
expect(result).toContain(JSON.stringify({ minimum: 1 }));
});
});
describe('when "items" is undefined', () => {
it("returns Type.Array and Type.Unknown", () => {
const schema: ArraySchema = {
type: "array",
};
const result = parseArray(schema);
expect(result).toContain(`Type.Array`);
expect(result).toContain(`Type.Unknown`);
});
it("creates schemaOptions", () => {
const schema: ArraySchema = {
type: "array",
description: "test description",
};
const result = parseArray(schema);
expect(result).toContain(
JSON.stringify({ description: "test description" })
);
});
});
});
describe("parseWithMultipleTypes() - when parsing a schema where 'types' is a list", () => {
it("returns Type.Union()", () => {
const schema: MultipleTypesSchema = {
type: ["string"],
};
const result = parseWithMultipleTypes(schema);
expect(result).toContain(`Type.Union`);
});
it("creates one type for each type in the list", () => {
const schema: MultipleTypesSchema = {
type: ["string", "null"],
};
const result = parseWithMultipleTypes(schema);
expect(result).toContain(`Type.Union`);
expect(result).toContain(`Type.String`);
expect(result).toContain(`Type.Null`);
});
it("creates union types for nullable objects", async () => {
const schema: MultipleTypesSchema = {
type: ["object", "null"],
properties: {},
};
const result = parseWithMultipleTypes(schema);
await expectEqualIgnoreFormatting(
result,
`Type.Union([Type.Object({}), Type.Null()])`
);
});
it("creates union types for nullable arrays", async () => {
const schema: MultipleTypesSchema = {
type: ["array", "null"],
items: { type: "string" },
};
const result = parseWithMultipleTypes(schema);
await expectEqualIgnoreFormatting(
result,
`Type.Union([Type.Array(Type.String()),Type.Null()])`
);
});
});
describe("parseConst() - when parsing a const schema", () => {
it("returns Type.Literal()", () => {
const schema: ConstSchema = {
const: "1",
};
const result = parseConst(schema);
expect(result).toContain(`Type.Literal`);
});
it("quotes strings", () => {
const schema: ConstSchema = {
const: "1",
};
const result = parseConst(schema);
expect(result).toContain(`"1"`);
});
it("does not quote numbers", () => {
const schema: ConstSchema = {
const: 1,
};
const result = parseConst(schema);
expect(result).toContain(`1`);
expect(result).not.toContain(`"1"`);
});
it("creates Type.Union() of Type.Literal()s for each item if const is a list", () => {
const schema: ConstSchema = {
const: [1, null],
};
const result = parseConst(schema);
expect(result).toContain(`Type.Union`);
expect(result).toContain(`Type.Literal`);
expect(result).toContain(`1`);
expect(result).toContain(`Type.Null`);
});
});
describe('parseTypeName() - when parsing a type name (e.g. "number", "string", "null" ..)', () => {
it('creates Type.Number for "number"', () => {
const result = parseTypeName("number");
expect(result).toEqual(`Type.Number()`);
});
it('applies schemaOptions for "number"', () => {
const schemaOptions: JSONSchema7 = { description: "test description" };
const result = parseTypeName("number", schemaOptions);
expect(result).toContain(JSON.stringify(schemaOptions));
});
it('creates Type.String for "string"', () => {
const result = parseTypeName("string");
expect(result).toEqual(`Type.String()`);
});
it('applies schemaOptions for "string"', () => {
const schemaOptions: JSONSchema7 = { description: "test description" };
const result = parseTypeName("string", schemaOptions);
expect(result).toContain(JSON.stringify(schemaOptions));
});
it('creates Type.Boolean for "boolean"', () => {
const result = parseTypeName("boolean");
expect(result).toEqual(`Type.Boolean()`);
});
it('applies schemaOptions for "boolean"', () => {
const schemaOptions: JSONSchema7 = { description: "test description" };
const result = parseTypeName("boolean", schemaOptions);
expect(result).toContain(JSON.stringify(schemaOptions));
});
it('creates Type.Null for "null"', () => {
const result = parseTypeName("null");
expect(result).toEqual(`Type.Null()`);
});
it('applies schemaOptions for "null"', () => {
const schemaOptions: JSONSchema7 = { description: "test description" };
const result = parseTypeName("null", schemaOptions);
expect(result).toContain(JSON.stringify(schemaOptions));
});
});
});