zod-to-x
Version:
Multi language types generation from Zod schemas.
910 lines (909 loc) • 41.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Zod2Cpp17 = exports.Zod2Cpp = void 0;
const case_1 = __importDefault(require("case"));
const core_1 = require("../../core");
const number_limits_1 = require("../../utils/number_limits");
const libs_1 = require("./libs");
const nlohmann_1 = require("./nlohmann");
const options_1 = require("./options");
/**
* @description Transpiler for Zod schemas to C++11 code.
*/
class Zod2Cpp extends core_1.Zod2X {
constructor(opt = {}) {
super(Object.assign(Object.assign({}, options_1.defaultOpts), opt));
this.commentKey = "//";
this.getIntersectionType = () => {
/** Covered by "transpileIntersection" method */
return "";
};
this.getDateType = () => this.getStringType(); // Representing ISO date as a string
this.getBooleanType = () => "bool";
this.getStringType = () => {
this.imports.add(this.lib.string);
return "std::string";
};
/** Ex: std::tuple<TypeA, TypeB, ...> */
this.getTupleType = (itemsType) => {
this.imports.add(this.lib.tuple);
return `std::tuple<${itemsType.join(", ")}>`;
};
this.getAnyType = () => {
this.imports.add(this.lib.nlohmann);
return nlohmann_1.NLOHMANN;
};
/** Ex: std::set<TypeA> */
this.getSetType = (itemType) => {
this.imports.add(this.lib.set);
return `std::set<${itemType}>`;
};
/** Ex: boost::variant<TypeA, TypeB> */
this.getUnionType = (itemsType) => {
this.imports.add(this.lib.variant);
const unionLines = itemsType.map((type) => `${this.indent[2]}${type}`).join(",\n");
return `boost::variant<\n${unionLines}\n${this.indent[1]}>`;
};
/** Ex: depends on number range (if any). One of:
* - std::uint32_t
* - std::uint64_t
* - std::int32_t
* - std::int64_t
* - double
*/
this.getNumberType = (isInt, range) => {
if (!isInt) {
return "double";
}
this.imports.add(this.lib.integers);
if ((range === null || range === void 0 ? void 0 : range.min) >= number_limits_1.UINT32_RANGES[0]) {
if ((range === null || range === void 0 ? void 0 : range.max) <= number_limits_1.UINT32_RANGES[1]) {
return "std::uint32_t";
}
else {
return "std::uint64_t";
}
}
else {
if ((range === null || range === void 0 ? void 0 : range.max) <= number_limits_1.INT32_RANGES[1] && (range === null || range === void 0 ? void 0 : range.min) >= number_limits_1.INT32_RANGES[0]) {
return "std::int32_t";
}
else {
return "std::int64_t";
}
}
};
// Push with indentation helpers for additional outputs
this._push0 = (item, data) => item.push(`${this.indent[0]}${data}`);
this._push1 = (item, data) => item.push(`${this.indent[1]}${data}`);
this._push2 = (item, data) => item.push(`${this.indent[2]}${data}`);
this._push3 = (item, data) => item.push(`${this.indent[3]}${data}`);
this._push4 = (item, data) => item.push(`${this.indent[4]}${data}`);
this.serializers = [];
this.useBoost = true;
this.lib = (0, libs_1.getLibs)(this.useBoost);
}
runBefore() { }
addImportFromFile(filename, _) {
const filenameWithExtension = filename.endsWith(".hpp") ? filename : `${filename}.hpp`;
return `#include "${filenameWithExtension}"`;
}
getTypeFromExternalNamespace(namespace, typeName) {
return `${namespace}::${typeName}`;
}
addExtendedType(name, parentNamespace, aliasOf, opt) {
var _a;
const extendedType = (opt === null || opt === void 0 ? void 0 : opt.isInternal)
? aliasOf
: this.getTypeFromExternalNamespace(parentNamespace, aliasOf);
const templates = (_a = opt === null || opt === void 0 ? void 0 : opt.templates) !== null && _a !== void 0 ? _a : "";
if (opt === null || opt === void 0 ? void 0 : opt.templateDefinition) {
this.push0(opt.templateDefinition);
}
if ((opt === null || opt === void 0 ? void 0 : opt.type) === "union" || (opt === null || opt === void 0 ? void 0 : opt.type) === "alias") {
this.push0(`using ${name} = ${extendedType}${templates};\n`);
}
else {
if (this.opt.outType === "class") {
this.push0(`class ${name} : public ${extendedType}${templates} {};\n`);
}
else {
this.push0(`struct ${name} : public ${extendedType}${templates} {};\n`);
}
}
if ((opt === null || opt === void 0 ? void 0 : opt.type) !== "alias" && parentNamespace !== undefined) {
this._addExtendedTypeSerializer(name, parentNamespace, {
templateDefinition: opt === null || opt === void 0 ? void 0 : opt.templateDefinition,
templateList: opt === null || opt === void 0 ? void 0 : opt.templateList,
});
this._addExtendedTypeDeserializer(name, parentNamespace, {
templateDefinition: opt === null || opt === void 0 ? void 0 : opt.templateDefinition,
templateList: opt === null || opt === void 0 ? void 0 : opt.templateList,
});
}
}
getGenericTemplatesTranslation(data) {
if ((data instanceof core_1.ASTObject || data instanceof core_1.ASTDefinition) &&
data.templatesTranslation.length > 0) {
return ("<" +
data.templatesTranslation
.map((t) => {
if (this.isExternalTypeImport(t)) {
this.addExternalTypeImport(t);
return this.getTypeFromExternalNamespace(t.parentNamespace, t.aliasOf);
}
else {
return t.aliasOf;
}
})
.join(", ") +
">");
}
}
/**
* Emits an alias/extension declaration early for layered references.
* It keeps concrete template translations and falls back to declared templates (e.g. <T>)
* for aliases of generic templates.
*/
checkExtendedTypeInclusion(data, type) {
const templatesMeta = data instanceof core_1.ASTObject && data.templates.size > 0
? this._getTemplates(data.templates)
: undefined;
const translatedTemplates = this.getGenericTemplatesTranslation(data);
const templates = translatedTemplates || (templatesMeta === null || templatesMeta === void 0 ? void 0 : templatesMeta.templateList);
const templateList = translatedTemplates ? undefined : templatesMeta === null || templatesMeta === void 0 ? void 0 : templatesMeta.templateList;
const templateDefinition = translatedTemplates
? undefined
: templatesMeta === null || templatesMeta === void 0 ? void 0 : templatesMeta.templateDefinition;
if (this.isExternalTypeImport(data)) {
if (data.aliasOf) {
this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
type,
templates,
templateDefinition,
templateList,
});
this.addExternalTypeImport(data);
}
return true;
}
else if (data.aliasOf) {
this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
type,
isInternal: true,
templates,
templateDefinition,
templateList,
});
return true;
}
return false;
}
runAfter() {
this.preImports.add("#pragma once");
// Add the output inside the namespace
this.output = this.output.map((i) => `${this.indent[1]}${i}`);
this.output.unshift(`namespace ${this.opt.namespace} {`);
this.output.push("}");
if (this.opt.skipSerialize !== true && this.serializers.length > 0) {
this.imports.add(this.lib.nlohmann);
this.output.push("");
this.output.push(`namespace ${this.opt.namespace} {`);
if (this.imports.has(this.lib.optional)) {
this.serializers.unshift(...(0, nlohmann_1.getNlohmannOptionalHelper)(this.opt.indent, this.opt.includeNulls === true, this.useBoost, this.opt.namespace));
}
this.serializers = this.serializers.map((i) => `${this.indent[1]}${i}`);
this.output.push(...this.serializers);
this.output.push("}");
}
}
/** Ex: std::vector<std::vector<TypeA>> */
getArrayType(arrayType, arrayDeep) {
this.imports.add(this.lib.vector);
let output = `std::vector<${arrayType}>`;
for (let i = 0; i < arrayDeep - 1; i++) {
output = `std::vector<${output}>`;
}
return output;
}
getLiteralStringType(value, parentEnumNameKey) {
var _a;
return ((_a = parentEnumNameKey === null || parentEnumNameKey === void 0 ? void 0 : parentEnumNameKey[0]) !== null && _a !== void 0 ? _a : (typeof value === "boolean"
? this.getBooleanType()
: isNaN(Number(value))
? this.getStringType()
: this.getNumberType(Number.isInteger(value), {
min: value,
max: value,
})));
}
/** Ex: std::unordered_map<TypeA> */
getMapType(keyType, valueType) {
this.imports.add(this.lib.map);
return `std::unordered_map<${keyType}, ${valueType}>`;
}
getRecordType(keyType, valueType) {
return this.getMapType(keyType, valueType);
}
_getOptional(type) {
this.imports.add(this.lib.optional);
return `boost::optional<${type}>`;
}
_addExtendedTypeSerializer(typeName, parentNamespace, opt) {
var _a;
if (opt === null || opt === void 0 ? void 0 : opt.templateDefinition) {
this._push0(this.serializers, opt.templateDefinition);
}
const typeNameWithTemplates = `${typeName}${(_a = opt === null || opt === void 0 ? void 0 : opt.templateList) !== null && _a !== void 0 ? _a : ""}`;
this._push0(this.serializers, `inline void to_json(${nlohmann_1.NLOHMANN}& j, const ${typeNameWithTemplates}& x) {`);
this._push1(this.serializers, `${parentNamespace}::to_json(j, x);`);
this._push0(this.serializers, "}\n");
}
_addExtendedTypeDeserializer(typeName, parentNamespace, opt) {
var _a;
if (opt === null || opt === void 0 ? void 0 : opt.templateDefinition) {
this._push0(this.serializers, opt.templateDefinition);
}
const typeNameWithTemplates = `${typeName}${(_a = opt === null || opt === void 0 ? void 0 : opt.templateList) !== null && _a !== void 0 ? _a : ""}`;
this._push0(this.serializers, `inline void from_json(const ${nlohmann_1.NLOHMANN}& j, ${typeNameWithTemplates}& x) {`);
this._push1(this.serializers, `${parentNamespace}::from_json(j, x);`);
this._push0(this.serializers, "}\n");
}
transpileAliasedType(data) {
if (this.checkExtendedTypeInclusion(data, "alias")) {
return;
}
let extendedType = undefined;
this.addComment(data.description);
if (data instanceof core_1.ASTArray) {
extendedType = this.getAttributeType(data.item);
}
else {
extendedType = this.getAttributeType(data);
}
if (extendedType !== undefined) {
this.push0(`using ${data.name} = ${extendedType};\n`);
}
}
/** Ex:
* enum class EnumA: int {
* Item1,
* Item2
* }
*/
transpileEnum(data) {
if (this.checkExtendedTypeInclusion(data, "alias")) {
return;
}
this.addComment(data.description);
const serializeData = [];
this.push0(`enum class ${data.name}: int {`);
data.values.forEach(([key, value], index) => {
key = case_1.default.pascal(key);
const separator = index + 1 == data.values.length ? "" : ",";
this.push1(`${key}${separator}`);
serializeData.push({ enumName: key, origValue: value });
});
this.push0("};\n");
this._createEnumSerializer(data.name, serializeData);
this._createEnumDeserializer(data.name, serializeData);
}
/** Ex:
* - Case of using classes:
* class MyClassC: public MyClassA, public MyClassB {
* public:
* MyClassC() = default;
* virtual ~MyClass() = default;
* }
*
* - Case of using structs
* struct MyStructC: public MyStructA, public MyStructB {
*
* }
*/
transpileIntersection(data) {
if (this.checkExtendedTypeInclusion(data)) {
return;
}
console.warn(`${data.name}: use zod's merge instead of intersection whenever possible.`);
console.warn(`${data.name}: no field name conflicts is assumed.`);
this.addComment(data.description);
if (!data.areAllObjects) {
throw new core_1.NotTranspilerableTypeError(`${data.name}: only intersection of ZodObjects is supported.`);
}
const leftType = this.getAttributeType(data.left);
const rightType = this.getAttributeType(data.right);
if (this.opt.outType === "class") {
this.push0(`class ${data.name} : public ${leftType}, public ${rightType} {`);
this.push0(`public:`);
this.push1(`${data.name}() = default;`);
this.push1(`virtual ~${data.name}() = default;`);
this.push0("");
this.addComment("Intersection fields are inherited from base classes.", this.indent[1]);
this.push0("};\n");
}
else {
this.push0(`struct ${data.name} : public ${leftType}, public ${rightType} {`);
this.addComment("Intersection fields are inherited from base structs.", this.indent[1]);
this.push0("};\n");
}
this._createIntersectionSerializer(data.name, [leftType, rightType]);
this._createIntersectionDeserializer(data.name, [leftType, rightType]);
}
/** Ex: using TypeC = boost::variant<TypeA, TypeB> */
transpileUnion(data) {
if (this.checkExtendedTypeInclusion(data, "union")) {
return;
}
this.addComment(data.description);
const attributesData = data.options.map((i) => {
var _a;
return {
type: this.getAttributeType(i),
discriminantValue: (_a = i.constraints) === null || _a === void 0 ? void 0 : _a.discriminantValue,
templates: this.getGenericTemplatesTranslation(i),
};
});
const attributesTypes = attributesData.map((i) => i.type);
this.push0(`using ${data.name} = ${this.getUnionType(attributesTypes)};\n`);
this._createUnionSerializer(data.name, attributesTypes);
this._createUnionDeserializer(data.name, attributesData, data.discriminantKey);
}
transpileStruct(data) {
if (this.checkExtendedTypeInclusion(data)) {
return;
}
this.addComment(data.description);
if (this.opt.outType === "class") {
this._transpileStructAsClass(data);
}
else {
this._transpileStructAsStruct(data);
}
}
_getTemplates(templates) {
return {
templateDefinition: templates.size > 0
? `template<${[...templates].map((i) => "typename " + i).join(", ")}>`
: "",
templateList: templates.size > 0 ? `<${[...templates].join(", ")}>` : "",
};
}
/** Ex:
* struct MyStruct {
* TypeA attribute1;
* TypeB attribute2;
* }
*/
_transpileStructAsStruct(data) {
const { templateDefinition } = this._getTemplates(data.templates);
if (templateDefinition) {
this.push0(templateDefinition);
}
this.push0(`struct ${data.name} {`);
const serializeData = [];
Object.entries(data.properties).forEach(([key, value]) => {
const snakeName = this.opt.keepKeys === true ? key : case_1.default.snake(key);
const origType = this._transpileMember(snakeName, value);
serializeData.push({
origName: key,
snakeName,
typeName: origType,
required: !(value.isNullable || value.isOptional),
});
});
this.push0("};\n");
this._createStructSerializer(data.name, serializeData, data.templates);
this._createStructDeserializer(data.name, serializeData, data.templates);
}
/** Ex:
* class MyClass {
* private:
* TypeA attribute1;
* TypeB attribute2;
*
* public:
* MyClass() = default;
* virtual ~MyClass() = default;
*
* const TypeA& get_attribute1() const { return this->attribute1; }
* TypeA& get_mut attribute1() { return this->attribute1; }
* void set_attribute1(TypeA& value) { this->attribute1 = value; }
* [...]
* }
*/
_transpileStructAsClass(data) {
const { templateDefinition } = this._getTemplates(data.templates);
if (templateDefinition) {
this.push0(templateDefinition);
}
this.push0(`class ${data.name} {`);
this.push0(`private:`);
const setterGetter = [];
const serializeData = [];
Object.entries(data.properties).forEach(([key, value]) => {
const snakeName = this.opt.keepKeys === true ? key : case_1.default.snake(key);
const origType = this._transpileMember(snakeName, value);
setterGetter.push(...this._createSetterGetter(snakeName, origType, !(value.isNullable || value.isOptional)));
serializeData.push({
origName: key,
snakeName,
typeName: origType,
required: !(value.isNullable || value.isOptional),
});
});
this.push0("");
this.push0(`public:`);
this.push1(`${data.name}() = default;`);
this.push1(`virtual ~${data.name}() = default;`);
setterGetter.forEach((i) => this.push1(i));
this.push0("};\n");
this._createClassSerializer(data.name, serializeData, data.templates);
this._createClassDeserializer(data.name, serializeData, data.templates);
}
/**
* @description Transpiles an individual member of a C++ class based on the provided ASTNode.
*
* @param memberName - The name of the member variable to transpile.
* @param memberNode - The ASTNode defining the member's type, description, and other
* attributes.
*
* @returns - The original C++ type of the member, without optional modifier.
*/
_transpileMember(memberName, memberNode) {
let keyType = this.getAttributeType(memberNode);
const origType = keyType;
if (this.opt.includeComments &&
memberNode.description &&
!memberNode.name &&
!this.isTranspilerable(memberNode)) {
// Avoid duplicated descriptions for transpiled items.
this.push1("");
this.addComment(memberNode.description, `${this.indent[1]}`);
}
if (memberNode.isOptional || memberNode.isNullable) {
keyType = this._getOptional(keyType);
}
this.push1(`${keyType} ${memberName};`);
return origType;
}
/**
* @description Creates getter and setter enumHelpers for a C++ class member variable.
*
* @param memberName - The name of the member variable.
* @param memberType - The C++ type of the member variable.
* @param [required] - Whether the member is required (default is `false`).
*
* @returns - An array of strings representing the C++ getter and setter enumHelpers.
*/
_createSetterGetter(memberName, memberType, required) {
const result = [];
result.push("");
if (required) {
result.push(`const ${memberType}& get_${memberName}() const { return this->${memberName}; }`);
result.push(`${memberType}& get_mut_${memberName}() { return this->${memberName}; }`);
result.push(`void set_${memberName}(const ${memberType}& value) { this->${memberName} = value; }`);
}
else {
const fullType = this._getOptional(memberType);
result.push(`${fullType} get_${memberName}() const { return this->${memberName}; }`);
result.push(`void set_${memberName}(${fullType} value) { this->${memberName} = value; }`);
}
return result;
}
/**
* @description Generates a JSON serializer for a struct. Each required field is serialized
* directly, and optional fields are checked for existence before serialization.
* Ex:
* inline void to_json(nlohmann::json& j, const MyStruct& x) {
* j["requiredField"] = x.required_field;
* if (x.optional_field) {
* j["optionalField"] = x.optional_field;
* }
* }
* @param parent - Name of the serialized structure
* @param childs - Structure attributes data.
* @param templates - Generic templates string (if any)
*/
_createStructSerializer(parent, childs, templates) {
const prefix = this.opt.namespace ? `${this.opt.namespace}::` : "";
const { templateDefinition, templateList } = this._getTemplates(templates);
if (templateDefinition) {
this._push0(this.serializers, templateDefinition);
}
this._push0(this.serializers, `inline void to_json(${nlohmann_1.NLOHMANN}& j, const ${parent}${templateList}& x) {`);
childs.forEach((i) => {
if (i.required) {
this._push1(this.serializers, `j["${i.origName}"] = x.${i.snakeName};`);
}
else {
this._push1(this.serializers, `${prefix}set_opt<${i.typeName}>(j, "${i.origName}", x.${i.snakeName});`);
}
});
this._push0(this.serializers, "}\n");
}
/**
* @description Generates a JSON deserializer for a struct. Each required field is deserialized
* using at, while optional fields are handled with get_opt.
* Ex:
* inline void from_json(const nlohmann::json& j, MyStruct& x) {
* x.required_field(j.at("requiredField").get<int>());
* x.optional_field(get_opt<std::string>(j, "optionalField"));
* }
* @param parent - Name of the deserialized structure
* @param childs - Structure attributes data.
* @param templates - Generic templates string (if any)
*/
_createStructDeserializer(parent, childs, templates) {
const prefix = this.opt.namespace ? `${this.opt.namespace}::` : "";
const { templateDefinition, templateList } = this._getTemplates(templates);
if (templateDefinition) {
this._push0(this.serializers, templateDefinition);
}
this._push0(this.serializers, `inline void from_json(const ${nlohmann_1.NLOHMANN}& j, ${parent}${templateList}& x) {`);
childs.forEach((i) => {
if (i.required) {
this._push1(this.serializers, `x.${i.snakeName} = j.at("${i.origName}").get<${i.typeName}>();`);
}
else {
this._push1(this.serializers, `x.${i.snakeName} = ${prefix}get_opt<${i.typeName}>(j, "${i.origName}");`);
}
});
this._push0(this.serializers, "}\n");
}
/**
* @description Generates a JSON serializer for a class. The serializer uses getter methods to
* access class attributes. Optional fields are checked for existence before
* serialization.
* Ex:
* inline void to_json(nlohmann::json& j, const MyClass& x) {
* j["requiredField"] = x.get_required_field();
* if (x.get_optional_field()) {
* j["optionalField"] = x.get_optional_field();
* }
* }
* @param parent - Name of the serialized structure
* @param childs - Structure attributes data.
* @param templates - Generic templates string (if any)
*/
_createClassSerializer(parent, childs, templates) {
const prefix = this.opt.namespace ? `${this.opt.namespace}::` : "";
const { templateDefinition, templateList } = this._getTemplates(templates);
if (templateDefinition) {
this._push0(this.serializers, templateDefinition);
}
this._push0(this.serializers, `inline void to_json(${nlohmann_1.NLOHMANN}& j, const ${parent}${templateList}& x) {`);
childs.forEach((i) => {
if (i.required) {
this._push1(this.serializers, `j["${i.origName}"] = x.get_${i.snakeName}();`);
}
else {
this._push1(this.serializers, `${prefix}set_opt<${i.typeName}>(j, "${i.origName}", x.get_${i.snakeName}());`);
}
});
this._push0(this.serializers, "}\n");
}
/**
* @description Generates a JSON deserializer for a class. The deserializer uses setter methods
* to populate class attributes.
* Ex:
* inline void from_json(const nlohmann::json& j, MyClass& x) {
* x.set_required_field(j.at("requiredField").get<int>());
* x.set_optional_field(get_opt<std::string>(j, "optionalField"));
* }
* @param parent - Name of the deserialized structure
* @param childs - Structure attributes data.
* @param templates - Generic templates string (if any)
*/
_createClassDeserializer(parent, childs, templates) {
const prefix = this.opt.namespace ? `${this.opt.namespace}::` : "";
const { templateDefinition, templateList } = this._getTemplates(templates);
if (templateDefinition) {
this._push0(this.serializers, templateDefinition);
}
this._push0(this.serializers, `inline void from_json(const ${nlohmann_1.NLOHMANN}& j, ${parent}${templateList}& x) {`);
childs.forEach((i) => {
if (i.required) {
this._push1(this.serializers, `x.set_${i.snakeName}(j.at("${i.origName}").get<${i.typeName}>());`);
}
else {
this._push1(this.serializers, `x.set_${i.snakeName}(${prefix}get_opt<${i.typeName}>(j, "${i.origName}"));`);
}
});
this._push0(this.serializers, "}\n");
}
/**
* @description Generates a JSON serializer for an enum. Maps enum values to strings for
* serialization, with a default case for unexpected values.
* Ex:
* inline void to_json(nlohmann::json& j, const MyEnum& x) {
* switch (x) {
* case MyEnum::Value1: j = "VALUE_1"; break;
* case MyEnum::Value2: j = "VALUE_2"; break;
* default: throw std::runtime_error("Unexpected value serializing enum MyEnum: " +
* std::to_string(static_cast<int>(x)));
* }
* }
* @param parent - Name of the serialized enumerate
* @param childs - Enumerate values data.
*/
_createEnumSerializer(parent, childs) {
this.imports.add(this.lib.exceptions);
this._push0(this.serializers, `inline void to_json(${nlohmann_1.NLOHMANN}& j, const ${parent}& x) {`);
this._push1(this.serializers, `switch (x) {`);
childs.forEach((i) => {
const value = isNaN(Number(i.origValue)) ? `"${i.origValue}"` : i.origValue;
this._push2(this.serializers, `case ${parent}::${i.enumName}: j = ${value}; break;`);
});
this._push2(this.serializers, `default: throw std::runtime_error("Unexpected value serializing enum ${parent}: "` +
` + std::to_string(static_cast<int>(x)));`);
this._push1(this.serializers, `}`);
this._push0(this.serializers, `}\n`);
}
/**
* @description Generates a JSON deserializer for an enum. Maps strings to enum values, with an
* error for unexpected strings.
* Ex:
* inline void from_json(const nlohmann::json& j, MyEnum& x) {
* if (j == "VALUE_1") x = MyEnum::Value1;
* else if (j == "VALUE_2") x = MyEnum::Value2;
* else {
* throw std::runtime_error("Unexpected value deserializing enum MyEnum.");
* }
* }
* @param parent - Name of the deserialized enumerate
* @param childs - Enumerate values data.
*/
_createEnumDeserializer(parent, childs) {
this.imports.add(this.lib.exceptions);
this._push0(this.serializers, `inline void from_json(const ${nlohmann_1.NLOHMANN}& j, ${parent}& x) {`);
childs.forEach((i, index) => {
const value = isNaN(Number(i.origValue)) ? `"${i.origValue}"` : i.origValue;
if (index === 0) {
this._push1(this.serializers, `if (j == ${value}) x = ${parent}::${i.enumName};`);
}
else {
this._push1(this.serializers, `else if (j == ${value}) x = ${parent}::${i.enumName};`);
}
});
this._push1(this.serializers, `else { throw std::runtime_error("Unexpected value deserializing enum ${parent}."); }`);
this._push0(this.serializers, `}\n`);
}
/**
* @description Creates a JSON serializer for a class or struct that is the intersection of
* multiple types.
*
* @example
* // inline void to_json(nlohmann::json& j, const DerivedType& x) {
* // to_json(j, static_cast<const BaseType1&>(x));
* // to_json(j, static_cast<const BaseType2&>(x));
* // }
*
* @param intersectName - The name of the intersected class or struct.
* @param itemsType - An array of strings representing the names of the base types to serialize.
*/
_createIntersectionSerializer(intersectName, itemsType) {
this._push0(this.serializers, `inline void to_json(${nlohmann_1.NLOHMANN}& j, const ${intersectName}& x) {`);
itemsType.forEach((i) => this._push1(this.serializers, `to_json(j, static_cast<const ${i}&>(x));`));
this._push0(this.serializers, `}\n`);
}
/**
* @description Creates a JSON deserializer for a class or struct that is the intersection of
* multiple types.
*
* @example
* // inline void from_json(const nlohmann::json& j, DerivedType& x) {
* // from_json(j, static_cast<BaseType1&>(x));
* // from_json(j, static_cast<BaseType2&>(x));
* // }
*
* @param intersectName - The name of the intersected class or struct.
* @param itemsType - An array of strings representing the names of the base types to deserialize.
*/
_createIntersectionDeserializer(intersectName, itemsType) {
this._push0(this.serializers, `inline void from_json(const ${nlohmann_1.NLOHMANN}& j, ${intersectName}& x) {`);
itemsType.forEach((i) => this._push1(this.serializers, `from_json(j, static_cast<${i}&>(x));`));
this._push0(this.serializers, `}\n`);
}
/**
* @description Generates a `to_json` function for a specified union type, allowing the JSON
* library to correctly serialize any variant within the union.
*
* @param unionName The name of the union type.
* @param itemsType A list of all variant types contained in the union.
*
* @example
* // Given: unionName = "MyUnion", itemsType = {"int", "std::string"}
* // The generated output might look like:
* //
* // inline void to_json(nlohmann::json& j, const MyUnion& x) {
* // if (x.type() == typeid(int)) {
* // j = boost::get<int>(x);
* // } else if (x.type() == typeid(std::string)) {
* // j = boost::get<std::string>(x);
* // } else {
* // throw std::runtime_error("Unknown MyUnion type.");
* // }
* // }
*/
_createUnionSerializer(unionName, itemsType) {
this.imports.add(this.lib.exceptions);
this._push0(this.serializers, `inline void to_json(${nlohmann_1.NLOHMANN}& j, const ${unionName}& x) {`);
itemsType.forEach((i, index) => {
const condition = index === 0 ? "if" : "else if";
this._push1(this.serializers, `${condition} (x.type() == typeid(${i})) {`);
this._push2(this.serializers, `j = boost::get<${i}>(x);`);
this._push1(this.serializers, `}`);
});
this._push1(this.serializers, `else {`);
this._push2(this.serializers, `throw std::runtime_error("Unknown ${unionName} type.");`);
this._push1(this.serializers, `}`);
this._push0(this.serializers, `}\n`);
}
/**
* @description Generates a `from_json` function for a specified union type, attempting to
* deserialize the provided JSON into one of the known variant types. If no match
* is found, it throws an error.
*
* @param unionName The name of the union type.
* @param items A list of possible variant types. Each item includes:
* - `type`: The C++ type for deserialization (e.g., `int`, `std::string`).
* - `discriminantValue` (optional): The value in the discriminator field
* that maps to the respective type.
* @param discriminator (optional) The JSON field name that acts as a type discriminator.
* Required for discriminator-based deserialization.
*
* @example
* // Discriminator-based deserialization:
* // Given: unionName = "MyUnion", discriminator = "type"
* // items = { {"type": "EmailContact", "discriminantValue": "email"},
* // {"type": "PhoneContact", "discriminantValue": "phone"} }
* //
* // The generated output might look like:
* // inline void from_json(const nlohmann::json& j, MyUnion& x) {
* // const auto& k = j.at("type").get<std::string>();
* // if (k == "email") {
* // x = j.get<EmailContact>();
* // } else if (k == "phone") {
* // x = j.get<PhoneContact>();
* // } else {
* // throw std::runtime_error("Failed to deserialize MyUnion: unknown type");
* // }
* // }
*
* @example
* // Fallback matching without a discriminator:
* // Given: unionName = "MyUnion", items = { {"type": "int"}, {"type": "std::string"} }
* //
* // The generated output might look like:
* // inline void from_json(const nlohmann::json& j, MyUnion& x) {
* // try {
* // // Try to deserialize as int
* // x = j.get<int>();
* // return;
* // } catch (const std::exception&) {
* // // Fall through to try the next type
* // }
* //
* // try {
* // // Try to deserialize as std::string
* // x = j.get<std::string>();
* // return;
* // } catch (const std::exception&) {
* // // None of the types matched. Error
* // throw std::runtime_error("Failed to deserialize MyUnion: unknown format");
* // }
* // }
*/
_createUnionDeserializer(unionName, items, discriminator) {
this.imports.add(this.lib.exceptions);
this._push0(this.serializers, `inline void from_json(const ${nlohmann_1.NLOHMANN}& j, ${unionName}& x) {`);
const useDiscriminator = discriminator && items.every((i) => i.discriminantValue);
if (useDiscriminator) {
this._push1(this.serializers, `const auto& k = j.at("${discriminator}").get<std::string>();`);
items.forEach((i, index) => {
const condition = index === 0 ? "if" : "else if";
this._push1(this.serializers, `${condition} (k == "${i.discriminantValue}") {`);
this._push2(this.serializers, `x = j.get<${i.type}>();`);
this._push1(this.serializers, `}`);
if (index == items.length - 1) {
this._push1(this.serializers, `else {`);
this._push2(this.serializers, this.getComment(`None of the types matched. Error`));
this._push2(this.serializers, `throw std::runtime_error("Failed to deserialize ${unionName}: unknown format");`);
this._push1(this.serializers, `}`);
}
});
}
else {
console.warn(`${unionName}: use ZodDiscriminatedUnion instead of ZodUnion whenever possible.`);
items.forEach((i, index) => {
this._push1(this.serializers, `try {`);
this._push2(this.serializers, this.getComment(`Try to deserialize as ${i.type}`));
this._push2(this.serializers, `x = j.get<${i.type}>();`);
this._push2(this.serializers, `return;`);
this._push1(this.serializers, `} catch (const std::exception&) {`);
if (index != items.length - 1) {
this._push2(this.serializers, this.getComment(`Fall through to try the next type`));
}
else {
this._push2(this.serializers, this.getComment(`None of the types matched. Error`));
this._push2(this.serializers, `throw std::runtime_error("Failed to deserialize ${unionName}: unknown format");`);
}
this._push1(this.serializers, `}`);
});
}
this._push0(this.serializers, `}\n`);
}
}
exports.Zod2Cpp = Zod2Cpp;
/**
* @description Transpiler for Zod schemas to C++17 code.
*/
class Zod2Cpp17 extends Zod2Cpp {
constructor(opt = {}) {
super(opt);
/** Ex: std::variant<TypeA, TypeB> */
this.getUnionType = (itemsType) => {
this.imports.add(this.lib.variant);
const unionLines = itemsType.map((type) => `${this.indent[2]}${type}`).join(",\n");
return `std::variant<\n${unionLines}\n${this.indent[1]}>`;
};
this.useBoost = false;
this.lib = (0, libs_1.getLibs)(this.useBoost);
}
_getOptional(type) {
this.imports.add(this.lib.optional);
return `std::optional<${type}>`;
}
/**
* @description Generates a `to_json` function for a specified union type, allowing the JSON
* library to correctly serialize any variant within the union.
*
* @param unionName The name of the union type.
* @param itemsType A list of all variant types contained in the union.
*
* @example
* // Given: unionName = "MyUnion", itemsType = {"int", "std::string"}
* // The generated output might look like:
* //
* // inline void to_json(nlohmann::json& j, const MyUnion& x) {
* // std::visit(
* // [&j](auto&& arg) {
* // using T = std::decay_t<decltype(arg)>;
* // if constexpr (std::is_same_v<T, int>) {
* // j = arg;
* // } else if constexpr (std::is_same_v<T, std::string>) {
* // j = arg;
* // } else {
* // throw std::runtime_error("Unknown MyUnion type.");
* // }
* // },
* // x
* // );
* // }
*/
_createUnionSerializer(unionName, itemsType) {
this.imports.add(this.lib.exceptions);
this._push0(this.serializers, `inline void to_json(${nlohmann_1.NLOHMANN}& j, const ${unionName}& x) {`);
this._push1(this.serializers, `std::visit(`);
this._push2(this.serializers, `[&j](auto&& arg) {`);
this._push3(this.serializers, `using T = std::decay_t<decltype(arg)>;`);
itemsType.forEach((i, index) => {
const condition = (index === 0 ? "if" : "else if") + " constexpr";
this._push3(this.serializers, `${condition} (std::is_same_v<T, ${i}>) {`);
this._push4(this.serializers, `j = arg;`);
this._push3(this.serializers, `}`);
});
this._push3(this.serializers, `else {`);
this._push4(this.serializers, `throw std::runtime_error("Unknown ${unionName} type.");`);
this._push3(this.serializers, `}`);
this._push2(this.serializers, `},`);
this._push2(this.serializers, `x`);
this._push1(this.serializers, `);`);
this._push0(this.serializers, `}\n`);
}
}
exports.Zod2Cpp17 = Zod2Cpp17;