UNPKG

@nrfcloud/ts-json-schema-transformer

Version:

A TypeScript transformer that generates JSON schemas and validators from TypeScript interfaces

204 lines 8.48 kB
"use strict"; 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