schema2typebox
Version:
Creates typebox code from JSON schemas
256 lines (248 loc) • 9.05 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const globals_1 = require("@jest/globals");
const Array_1 = require("fp-ts/Array");
const node_fs_1 = __importDefault(require("node:fs"));
const shelljs_1 = __importDefault(require("shelljs"));
const programmatic_usage_1 = require("../src/programmatic-usage");
const util_1 = require("./util");
const SHELLJS_RETURN_CODE_OK = 0;
(0, globals_1.describe)("programmatic usage - when running the programmatic usage", () => {
(0, globals_1.test)("generated typebox names are based on title attribute", async () => {
const dummySchema = `
{
"title": "Contract",
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}
`;
const expectedTypebox = programmatic_usage_1.addCommentThatCodeIsGenerated.run(`
import { Static, Type } from "@sinclair/typebox";
export type Contract = Static<typeof Contract>;
export const Contract = Type.Object({name: Type.String()}, { $id: "Contract" });
`);
await (0, util_1.expectEqualIgnoreFormatting)(await (0, programmatic_usage_1.schema2typebox)({ input: dummySchema }), expectedTypebox);
});
(0, globals_1.describe)("when working with files containing $refs (sanity check of refparser library)", () => {
(0, globals_1.test)("object with $ref pointing to external files in relative path", async () => {
const dummySchema = `
{
"title": "Contract",
"type": "object",
"properties": {
"person": {
"$ref": "person.json"
},
"status": {
"$ref": "status.json"
}
},
"required": ["person"]
}
`;
const referencedPersonSchema = `
{
"title": "Person",
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 100
},
"age": {
"type": "number",
"minimum": 18
}
},
"required": ["name", "age"]
}
`;
const referencedStatusSchema = `
{
"title": "Status",
"enum": ["unknown", "accepted", "denied"]
}
`;
const expectedTypebox = programmatic_usage_1.addCommentThatCodeIsGenerated.run(`
import { Static, Type } from "@sinclair/typebox";
export type Contract = Static<typeof Contract>;
export const Contract = Type.Object({
person: Type.Object({
name: Type.String({ maxLength: 100 }),
age: Type.Number({ minimum: 18 }),
}),
status: Type.Optional(
Type.Union([
Type.Literal("unknown"),
Type.Literal("accepted"),
Type.Literal("denied"),
])
),
},
{ $id: "Contract" });
`);
const inputPaths = ["person.json", "status.json"].flatMap((currItem) => {
return (0, util_1.buildOsIndependentPath)([__dirname, "..", currItem]);
});
(0, Array_1.zip)(inputPaths, [referencedPersonSchema, referencedStatusSchema]).map(([fileName, data]) => {
return node_fs_1.default.writeFileSync(fileName, data, undefined);
});
await (0, util_1.expectEqualIgnoreFormatting)(await (0, programmatic_usage_1.schema2typebox)({ input: dummySchema }), expectedTypebox);
// cleanup generated files
const { code: returnCode } = shelljs_1.default.rm("-f", inputPaths);
expect(returnCode).toBe(SHELLJS_RETURN_CODE_OK);
});
(0, globals_1.test)("object with $ref inside anyOf", async () => {
const dummySchema = {
anyOf: [{ $ref: "./cat.json" }, { $ref: "./dog.json" }],
};
const referencedCatSchema = {
title: "Cat",
type: "object",
properties: {
type: {
type: "string",
const: "cat",
},
name: {
type: "string",
maxLength: 100,
},
},
required: ["type", "name"],
};
const referencedDogSchema = {
title: "Dog",
type: "object",
properties: {
type: {
type: "string",
const: "dog",
},
name: {
type: "string",
maxLength: 100,
},
},
required: ["type", "name"],
};
const expectedTypebox = programmatic_usage_1.addCommentThatCodeIsGenerated.run(`
import { Static, Type } from "@sinclair/typebox";
export type T = Static<typeof T>;
export const T = Type.Union([
Type.Object({
type: Type.Literal("cat"),
name: Type.String({ maxLength: 100 }),
}),
Type.Object({
type: Type.Literal("dog"),
name: Type.String({ maxLength: 100 }),
}),
],
{ $id: "T" });
`);
const inputPaths = ["cat.json", "dog.json"].flatMap((currItem) => {
return (0, util_1.buildOsIndependentPath)([__dirname, "..", currItem]);
});
(0, Array_1.zip)(inputPaths, [referencedCatSchema, referencedDogSchema]).map(([fileName, data]) => {
return node_fs_1.default.writeFileSync(fileName, JSON.stringify(data), undefined);
});
await (0, util_1.expectEqualIgnoreFormatting)(await (0, programmatic_usage_1.schema2typebox)({ input: JSON.stringify(dummySchema) }), expectedTypebox);
// cleanup generated files
const { code: returnCode } = shelljs_1.default.rm("-f", inputPaths);
expect(returnCode).toBe(SHELLJS_RETURN_CODE_OK);
});
// NOTE: This test might break if github adapts their links to raw github user
// content.
(0, globals_1.test)("object with $ref to remote files", async () => {
const dummySchema = {
anyOf: [
{
$ref: "https://raw.githubusercontent.com/xddq/schema2typebox/main/examples/ref-to-remote-files/cat.json",
},
{
$ref: "https://raw.githubusercontent.com/xddq/schema2typebox/main/examples/ref-to-remote-files/dog.json",
},
],
};
const expectedTypebox = programmatic_usage_1.addCommentThatCodeIsGenerated.run(`
import { Static, Type } from "@sinclair/typebox";
export type T = Static<typeof T>;
export const T = Type.Union(
[
Type.Object({
type: Type.Literal("cat"),
name: Type.String({ maxLength: 100 }),
}),
Type.Object({
type: Type.Literal("dog"),
name: Type.String({ maxLength: 100 }),
}),
],
{ $id: "T" }
);
`);
await (0, util_1.expectEqualIgnoreFormatting)(await (0, programmatic_usage_1.schema2typebox)({ input: JSON.stringify(dummySchema) }), expectedTypebox);
});
});
(0, globals_1.test)("object with oneOf generates custom typebox TypeRegistry code", async () => {
const dummySchema = `
{
"type": "object",
"properties": {
"a": {
"oneOf": [
{
"type": "string"
},
{
"type": "number"
}
]
}
},
"required": ["a"]
}
`;
const expectedTypebox = programmatic_usage_1.addCommentThatCodeIsGenerated.run(`
import {
Kind,
SchemaOptions,
Static,
TSchema,
TUnion,
Type,
TypeRegistry,
} from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
TypeRegistry.Set(
"ExtendedOneOf",
(schema: any, value) =>
1 ===
schema.oneOf.reduce(
(acc: number, schema: any) => acc + (Value.Check(schema, value) ? 1 : 0),
0
)
);
const OneOf = <T extends TSchema[]>(
oneOf: [...T],
options: SchemaOptions = {}
) => Type.Unsafe<Static<TUnion<T>>>({ ...options, [Kind]: "ExtendedOneOf", oneOf });
export type T = Static<typeof T>;
export const T = Type.Object(
{ a: OneOf([Type.String(), Type.Number()]) },
{ $id: "T" }
);
`);
await (0, util_1.expectEqualIgnoreFormatting)(await (0, programmatic_usage_1.schema2typebox)({ input: dummySchema }), expectedTypebox);
});
});
//# sourceMappingURL=programmatic-usage.spec.js.map