@nrfcloud/ts-json-schema-transformer
Version:
A TypeScript transformer that generates JSON schemas and validators from TypeScript interfaces
204 lines • 8.48 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.dereference = void 0;
exports.hasTransformMarker = hasTransformMarker;
exports.getGenericArg = getGenericArg;
exports.getTmpDir = getTmpDir;
exports.wrapCall = wrapCall;
exports.bundleSource = bundleSource;
exports.fixAjvImportCode = fixAjvImportCode;
exports.addFormatsAjv = addFormatsAjv;
exports.addFormatsJsf = addFormatsJsf;
exports.convertObjectToLiteralExpression = convertObjectToLiteralExpression;
exports.convertValueToExpression = convertValueToExpression;
exports.derefJSONSchemaRoot = derefJSONSchemaRoot;
const json_schema_ref_parser_1 = __importDefault(require("@apidevtools/json-schema-ref-parser"));
const dereference_js_1 = __importDefault(require("@apidevtools/json-schema-ref-parser/dist/lib/dereference.js"));
const ajv_1 = require("ajv");
const crypto_1 = require("crypto");
const esbuild_1 = require("esbuild");
const fs_1 = require("fs");
const path_1 = require("path");
const typescript_1 = __importDefault(require("typescript"));
const formats_1 = require("../formats");
const file_transformer_1 = require("./file-transformer");
const { JSONSchemaFaker: jsf } = require("json-schema-faker");
function hasTransformMarker(node) {
const jsdoc = typescript_1.default.getJSDocTags(node);
return jsdoc.some((tag) => tag.tagName.getText() === "transformer" && tag.comment === "ts-json-schema-transformer");
}
function getGenericArg(project, expression, idx = 0) {
return expression.typeArguments && expression.typeArguments[0]
? [
project.checker.getTypeFromTypeNode(expression.typeArguments[idx]),
expression.typeArguments[idx],
true,
]
: [
project.checker.getTypeAtLocation(expression.arguments[idx]),
expression.arguments[idx],
false,
];
}
function getTmpDir() {
const dir = (0, path_1.join)(__dirname, "..", "..", ".tmp");
if (!(0, fs_1.existsSync)(dir)) {
(0, fs_1.mkdirSync)(dir, { recursive: true });
}
return dir;
}
function wrapCall(call) {
const wrapFn = file_transformer_1.FileTransformer.getOrCreateImport(call.getSourceFile(), "@nrfcloud/ts-json-schema-transformer", "parser");
return typescript_1.default.factory.createCallExpression(wrapFn, undefined, [call]);
}
function bundleSource(source, options) {
const dir = getTmpDir();
const tmpPrefix = (0, crypto_1.randomUUID)();
const tempFilePath = (0, path_1.join)(dir, `${tmpPrefix}.temp.js`);
const outfile = (0, path_1.join)(dir, `${tmpPrefix}.temp.bundled.js`);
(0, fs_1.writeFileSync)(tempFilePath, source);
(0, esbuild_1.buildSync)({
...options,
entryPoints: [tempFilePath],
outfile,
});
const bundled = (0, fs_1.readFileSync)(outfile, "utf-8");
(0, fs_1.rmSync)(tempFilePath);
(0, fs_1.rmSync)(outfile);
return bundled;
}
function fixAjvImportCode(code) {
const regex = /const ([a-zA-Z\d_]+?) = require\("([\S]+?)"\)\.([a-zA-Z_]+?)[;|$]/;
let input = code.toString();
let match = null;
while (match = input.match(regex)) {
const [, variable, module, value] = match;
const replacement = `import {${value} as ${variable}} from "${module}";\n`;
const frontEnd = match.index;
const backStart = (match.index || 0) + match[0].length;
const front = input.substring(0, frontEnd);
const back = input.substring(backStart);
input = replacement + front + back;
}
return input;
}
function addFormatsAjv(ajv) {
ajv.opts.code.formats = (0, ajv_1._) `require(${__dirname + "/../generated/formats.mjs"})`;
ajv.addFormat("uuid", formats_1.uuid);
ajv.addFormat("json_pointer", formats_1.json_pointer);
ajv.addFormat("json_pointer_uri_fragment", formats_1.json_pointer_uri_fragment);
ajv.addFormat("relative_json_pointer", formats_1.relative_json_pointer);
ajv.addFormat("byte", formats_1.byte);
ajv.addFormat("int32", formats_1.int32);
ajv.addFormat("int64", formats_1.int64);
ajv.addFormat("float", formats_1.float);
ajv.addFormat("double", formats_1.double);
ajv.addFormat("password", formats_1.password);
ajv.addFormat("binary", formats_1.binary);
ajv.addFormat("regex", formats_1.regex);
ajv.addFormat("date_time", formats_1.date_time);
ajv.addFormat("date", formats_1.date);
ajv.addFormat("time", formats_1.time);
ajv.addFormat("email", formats_1.email);
ajv.addFormat("hostname", formats_1.hostname);
ajv.addFormat("ipv4", formats_1.ipv4);
ajv.addFormat("ipv6", formats_1.ipv6);
ajv.addFormat("uri", formats_1.uri);
ajv.addFormat("uri_reference", formats_1.uri_reference);
ajv.addFormat("uri_template", formats_1.uri_template);
ajv.addFormat("duration", formats_1.duration);
ajv.addFormat("iso_time", formats_1.iso_time);
ajv.addFormat("iso_date_time", formats_1.iso_date_time);
ajv.addFormat("url", formats_1.url);
}
function addFormatsJsf() {
jsf.format("relative-json-pointer", () => {
const test = jsf.random.randexp(formats_1.relative_json_pointer.source).toString();
return test;
});
jsf.format("byte", () => jsf.random.randexp(formats_1.BYTE.source).toString());
jsf.format("password", () => jsf.random.randexp("\S{8,20}").toString());
jsf.format("binary", () => jsf.random.randexp("\S{8,20}").toString());
jsf.format("iso-time", () => jsf.random.randexp(formats_1.iso_time.validate.source).toString());
jsf.format("iso-date-time", () => jsf.random.randexp(formats_1.iso_date_time.validate.source).toString());
}
/**
* takes a json object and recursively converts it to an object literal expression
*/
function convertObjectToLiteralExpression(object) {
const properties = [];
for (const [key, value] of Object.entries(object)) {
properties.push(typescript_1.default.factory.createPropertyAssignment(typescript_1.default.factory.createStringLiteral(key), convertValueToExpression(value)));
}
return typescript_1.default.factory.createObjectLiteralExpression(properties);
}
function convertValueToExpression(value) {
if (typeof value === "string") {
return typescript_1.default.factory.createStringLiteral(value);
}
else if (typeof value === "number") {
if (value < 0) {
return typescript_1.default.factory.createPrefixUnaryExpression(typescript_1.default.SyntaxKind.MinusToken, typescript_1.default.factory.createNumericLiteral(Math.abs(value)));
}
else {
return typescript_1.default.factory.createNumericLiteral(value);
}
}
else if (typeof value === "boolean") {
if (value) {
return typescript_1.default.factory.createTrue();
}
else {
return typescript_1.default.factory.createFalse();
}
}
else if (typeof value === "object") {
if (Array.isArray(value)) {
return typescript_1.default.factory.createArrayLiteralExpression(value.map(convertValueToExpression));
}
return convertObjectToLiteralExpression(value);
}
else if (value === null) {
return typescript_1.default.factory.createNull();
}
else if (value === undefined) {
return typescript_1.default.factory.createIdentifier("undefined");
}
else {
throw new Error(`Unknown type ${typeof value}`);
}
}
/**
* Inlines the root $ref in a json schema
*/
function derefJSONSchemaRoot(schema) {
const deepCopy = JSON.parse(JSON.stringify(schema));
const { "$ref": rootRef, ...baseSchema } = deepCopy;
const wrappedSchema = {
...baseSchema,
properties: {
temp: {
$ref: rootRef,
},
},
};
(0, exports.dereference)(wrappedSchema);
const derefedSchema = wrappedSchema.properties?.temp;
delete wrappedSchema.properties;
return {
...wrappedSchema,
...derefedSchema,
};
}
const dereference = (schema) => {
const parser = new json_schema_ref_parser_1.default();
parser.parse(schema);
parser.schema = schema;
(0, dereference_js_1.default)(parser, { dereference: { circular: true } }); // NOTE: mutates schema
return schema;
};
exports.dereference = dereference;
//# sourceMappingURL=utils.js.map