"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const types_1 = require("./types"); const __1 = require(".."); const codegen_1 = require("../codegen"); const ref_error_1 = require("../ref_error"); const names_1 = require("../names"); const code_1 = require("../../vocabularies/code"); const ref_1 = require("../../vocabularies/jtd/ref"); const util_1 = require("../util"); const quote_1 = require("../../runtime/quote"); const genSerialize = { elements: serializeElements, values: serializeValues, discriminator: serializeDiscriminator, properties: serializeProperties, optionalProperties: serializeProperties, enum: serializeString, type: serializeType, ref: serializeRef, }; function compileSerializer(sch, definitions) { const _sch = __1.getCompilingSchema.call(this, sch); if (_sch) return _sch; const { es5, lines } = this.opts.code; const { ownProperties } = this.opts; const gen = new codegen_1.CodeGen(this.scope, { es5, lines, ownProperties }); const serializeName = gen.scopeName("serialize"); const cxt = { self: this, gen, schema: sch.schema, schemaEnv: sch, definitions, data: names_1.default.data, }; let sourceCode; try { this._compilations.add(sch); sch.serializeName = serializeName; gen.func(serializeName, names_1.default.data, false, () => { gen.let(names_1.default.json, (0, codegen_1.str) ``); serializeCode(cxt); gen.return(names_1.default.json); }); gen.optimize(this.opts.code.optimize); const serializeFuncCode = gen.toString(); sourceCode = `${gen.scopeRefs(names_1.default.scope)}return ${serializeFuncCode}`; const makeSerialize = new Function(`${names_1.default.scope}`, sourceCode); const serialize = makeSerialize(this.scope.get()); this.scope.value(serializeName, { ref: serialize }); sch.serialize = serialize; } catch (e) { if (sourceCode) this.logger.error("Error compiling serializer, function code:", sourceCode); delete sch.serialize; delete sch.serializeName; throw e; } finally { this._compilations.delete(sch); } return sch; } exports.default = compileSerializer; function serializeCode(cxt) { let form; for (const key of types_1.jtdForms) { if (key in cxt.schema) { form = key; break; } } serializeNullable(cxt, form ? genSerialize[form] : serializeEmpty); } function serializeNullable(cxt, serializeForm) { const { gen, schema, data } = cxt; if (!schema.nullable) return serializeForm(cxt); gen.if((0, codegen_1._) `${data} === undefined || ${data} === null`, () => gen.add(names_1.default.json, (0, codegen_1._) `"null"`), () => serializeForm(cxt)); } function serializeElements(cxt) { const { gen, schema, data } = cxt; gen.add(names_1.default.json, (0, codegen_1.str) `[`); const first = gen.let("first", true); gen.forOf("el", data, (el) => { addComma(cxt, first); serializeCode({ ...cxt, schema: schema.elements, data: el }); }); gen.add(names_1.default.json, (0, codegen_1.str) `]`); } function serializeValues(cxt) { const { gen, schema, data } = cxt; gen.add(names_1.default.json, (0, codegen_1.str) `{`); const first = gen.let("first", true); gen.forIn("key", data, (key) => serializeKeyValue(cxt, key, schema.values, first)); gen.add(names_1.default.json, (0, codegen_1.str) `}`); } function serializeKeyValue(cxt, key, schema, first) { const { gen, data } = cxt; addComma(cxt, first); serializeString({ ...cxt, data: key }); gen.add(names_1.default.json, (0, codegen_1.str) `:`); const value = gen.const("value", (0, codegen_1._) `${data}${(0, codegen_1.getProperty)(key)}`); serializeCode({ ...cxt, schema, data: value }); } function serializeDiscriminator(cxt) { const { gen, schema, data } = cxt; const { discriminator } = schema; gen.add(names_1.default.json, (0, codegen_1.str) `{${JSON.stringify(discriminator)}:`); const tag = gen.const("tag", (0, codegen_1._) `${data}${(0, codegen_1.getProperty)(discriminator)}`); serializeString({ ...cxt, data: tag }); gen.if(false); for (const tagValue in schema.mapping) { gen.elseIf((0, codegen_1._) `${tag} === ${tagValue}`); const sch = schema.mapping[tagValue]; serializeSchemaProperties({ ...cxt, schema: sch }, discriminator); } gen.endIf(); gen.add(names_1.default.json, (0, codegen_1.str) `}`); } function serializeProperties(cxt) { const { gen } = cxt; gen.add(names_1.default.json, (0, codegen_1.str) `{`); serializeSchemaProperties(cxt); gen.add(names_1.default.json, (0, codegen_1.str) `}`); } function serializeSchemaProperties(cxt, discriminator) { const { gen, schema, data } = cxt; const { properties, optionalProperties } = schema; const props = keys(properties); const optProps = keys(optionalProperties); const allProps = allProperties(props.concat(optProps)); let first = !discriminator; for (const key of props) { serializeProperty(key, properties[key], keyValue(key)); } for (const key of optProps) { const value = keyValue(key); gen.if((0, codegen_1.and)((0, codegen_1._) `${value} !== undefined`, (0, code_1.isOwnProperty)(gen, data, key)), () => serializeProperty(key, optionalProperties[key], value)); } if (schema.additionalProperties) { gen.forIn("key", data, (key) => gen.if(isAdditional(key, allProps), () => serializeKeyValue(cxt, key, {}, gen.let("first", first)))); } function keys(ps) { return ps ? Object.keys(ps) : []; } function allProperties(ps) { if (discriminator) ps.push(discriminator); if (new Set(ps).size !== ps.length) { throw new Error("JTD: properties/optionalProperties/disciminator overlap"); } return ps; } function keyValue(key) { return gen.const("value", (0, codegen_1._) `${data}${(0, codegen_1.getProperty)(key)}`); } function serializeProperty(key, propSchema, value) { if (first) first = false; else gen.add(names_1.default.json, (0, codegen_1.str) `,`); gen.add(names_1.default.json, (0, codegen_1.str) `${JSON.stringify(key)}:`); serializeCode({ ...cxt, schema: propSchema, data: value }); } function isAdditional(key, ps) { return ps.length ? (0, codegen_1.and)(...ps.map((p) => (0, codegen_1._) `${key} !== ${p}`)) : true; } } function serializeType(cxt) { const { gen, schema, data } = cxt; switch (schema.type) { case "boolean": gen.add(names_1.default.json, (0, codegen_1._) `${data} ? "true" : "false"`); break; case "string": serializeString(cxt); break; case "timestamp": gen.if((0, codegen_1._) `${data} instanceof Date`, () => gen.add(names_1.default.json, (0, codegen_1._) `'"' + ${data}.toISOString() + '"'`), () => serializeString(cxt)); break; default: serializeNumber(cxt); } } function serializeString({ gen, data }) { gen.add(names_1.default.json, (0, codegen_1._) `${(0, util_1.useFunc)(gen, quote_1.default)}(${data})`); } function serializeNumber({ gen, data }) { gen.add(names_1.default.json, (0, codegen_1._) `"" + ${data}`); } function serializeRef(cxt) { const { gen, self, data, definitions, schema, schemaEnv } = cxt; const { ref } = schema; const refSchema = definitions[ref]; if (!refSchema) throw new ref_error_1.default(self.opts.uriResolver, "", ref, `No definition ${ref}`); if (!(0, ref_1.hasRef)(refSchema)) return serializeCode({ ...cxt, schema: refSchema }); const { root } = schemaEnv; const sch = compileSerializer.call(self, new __1.SchemaEnv({ schema: refSchema, root }), definitions); gen.add(names_1.default.json, (0, codegen_1._) `${getSerialize(gen, sch)}(${data})`); } function getSerialize(gen, sch) { return sch.serialize ? gen.scopeValue("serialize", { ref: sch.serialize }) : (0, codegen_1._) `${gen.scopeValue("wrapper", { ref: sch })}.serialize`; } function serializeEmpty({ gen, data }) { gen.add(names_1.default.json, (0, codegen_1._) `JSON.stringify(${data})`); } function addComma({ gen }, first) { gen.if(first, () => gen.assign(first, false), () => gen.add(names_1.default.json, (0, codegen_1.str) `,`)); } //# sourceMappingURL=serialize.js.map