UNPKG

zod-to-x

Version:

Multi language types generation from Zod schemas.

261 lines (260 loc) 10.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.zod2ProtoV3 = zod2ProtoV3; const case_1 = __importDefault(require("case")); const core_1 = require("../../core"); const number_limits_1 = require("../../utils/number_limits"); const string_utils_1 = __importDefault(require("../../utils/string_utils")); const options_1 = require("./options"); const allowedKeyTypes = [ "int32", "int64", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64", "bool", "string", ]; class Zod2ProtoV3 extends core_1.Zod2X { constructor(opt = {}) { super(Object.assign(Object.assign({}, options_1.defaultOpts), opt)); this.commentKey = "//"; this.getUnionType = () => { /** Covered by "transpileUnion" method */ return ""; }; this.getBooleanType = () => "bool"; this.getStringType = () => "string"; this.getNumberType = (isInt, range) => { if (!isInt && this.opt.encodeDoubleAsInt !== true) { return "double"; } 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 "uint32"; } else { return "uint64"; } } 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 "int32"; } else { return "int64"; } } }; this.getAnyType = () => { this.imports.add(`import "google/protobuf/any.proto";`); return "google.protobuf.Any"; }; this.getDateType = () => { this.imports.add(`import "google/protobuf/timestamp.proto";`); return "google.protobuf.Timestamp"; }; this.getSetType = (itemType) => { return this.getArrayType(itemType, 1); }; /** * @description Determines the equivalent Protobuf type for a tuple based on its item types. * * Protobuf v3 does not directly support tuples. However, if all the types * in the tuple are identical, it can be represented as a `repeated` field * of that type. If the tuple contains mixed types, Protobuf cannot represent * it directly, and an alternative approach (e.g., defining a Protobuf message) * should be considered. * * @param itemsType - An array of strings representing the types of the tuple elements. * @returns A string representing the Protobuf type for the tuple. * If all tuple elements are of the same type, it returns a `repeated` field of that * type. * @throws NotTranspilerableTypeError if the tuple contains mixed types. */ this.getTupleType = (itemsType) => { const uniqueTypes = new Set(itemsType); if (uniqueTypes.size === 1) { return this.getArrayType(itemsType[0], 1); } else { throw new core_1.NotTranspilerableTypeError("Protobuf v3 does not support mixed-type tuples. Consider defining a message type."); } }; this.getIntersectionType = (itemsType) => { throw new core_1.NotTranspilerableTypeError("Protobuf v3 does not support intersection types directly."); }; } addImportFromFile(filename, namespace) { // Zod2ProtoV3 does not support layered modeling. return ""; } getTypeFromExternalNamespace(namespace, typeName) { // Zod2ProtoV3 does not support layered modeling. return ""; } getGenericTemplatesTranslation(data) { // Zod2ProtoV3 does not support layered modeling nor generics. return ""; } addExtendedType(name, parentNamespace, aliasOf) { // Zod2ProtoV3 does not support layered modeling. return; } transpileAliasedType(data) { // Zod2ProtoV3 does not need to transpile aliased types. return; } getArrayType(arrayType, arrayDeep) { if (arrayDeep === 1) { return `repeated ${arrayType}`; } else { throw new core_1.NotTranspilerableTypeError("Protobuf v3 does not support multidimensional arrays directly. " + "You need to define nested message types for deeper arrays"); } } getLiteralStringType(value) { if (typeof value === "string") { return this.getStringType(); } else if (typeof value === "number") { return this.getNumberType(Number.isInteger(value), { min: value, max: value }); } else { throw new core_1.NotTranspilerableTypeError(`Protobuf v3 does not support Literals for this value type: ${value}`); } } getMapType(keyType, valueType) { if (!allowedKeyTypes.includes(keyType)) { throw new core_1.NotTranspilerableTypeError(`Protobuf map keys must be an integral or string type, got '${keyType}'.`); } return `map<${keyType}, ${valueType}>`; } getRecordType(keyType, valueType) { return this.getMapType(keyType, valueType); } transpileEnum(data) { if (data.isFromDiscriminatedUnion === true) { // Injected enum from ZodDiscriminatedUnion are not transpiled. return; } this.addComment(data.description); this.push0(`enum ${data.name} {`); data.values.forEach(([key, value], index) => { if (Number.isInteger(key.at(0))) { throw new core_1.NotTranspilerableTypeError(`Enumerate item name cannot start with number: ${key}`); } this.push1(`${key} = ${index};`); }); this.push0("}\n"); } transpileIntersection(data) { throw new core_1.NotTranspilerableTypeError(`Protobuf does not support message intersections.`); } transpileStruct(data) { this.addComment(data.description); this.push0(`message ${data.name} {`); Object.entries(data.properties).forEach(([key, value], index) => { if (this.opt.includeComments && value.description && !value.name && !this.isTranspilerable(value)) { // Avoid duplicated descriptions for transpiled items. this.addComment(value.description, `\n${this.indent[1]}`); } const fieldType = value.isOptional && this.opt.useExplicitOptional ? `optional ${this.getAttributeType(value)}` : this.getAttributeType(value); this.push1(`${fieldType} ${this._adaptField(key)} = ${index + 1};`); }); this.push0("}\n"); } /** * Transpiles a Zod union or discriminated union into a Protobuf-compatible `oneof` message. * * @limitations Currently supports `oneOf` for options that can be represented as a * Protobuf message or enum. Other types are not yet supported. * @param data The AST representation of a union or discriminated union, including its * common metadata. * @example * Input: * { * name: "UserContact", * options: ["EmailContact", "PhoneContact", "SocialContact"], * description: "Represents different ways to contact a user." * } * * Generated Output: * message UserContact { * oneof user_contact_oneof { * EmailContact email_contact = 1; * PhoneContact phone_contact = 2; * SocialContact social_contact = 3; * } * } */ transpileUnion(data) { this.addComment(data.description); const attributesTypes = data.options.map(this.getAttributeType.bind(this)); if (attributesTypes.find((i) => i.startsWith("map<") || i.startsWith("repeated "))) { throw new core_1.NotTranspilerableTypeError("Map and Repeated fields are not suported by Protobuf oneOf"); } this.push0(`message ${data.name} {`); this.push1(`oneof ${string_utils_1.default.lowerFirstChar(this._adaptField(data.name + "Oneof"))} {`); attributesTypes.forEach((item, index) => { this.push2(`${item} ${string_utils_1.default.lowerFirstChar(this._adaptField(item))} = ${index + 1};`); }); this.push1(`}`); this.push0("}\n"); } runBefore() { var _a, _b; this.preImports.add(`syntax = "proto3";`); if ((_a = this.opt) === null || _a === void 0 ? void 0 : _a.packageName) { this.preImports.add(`package ${(_b = this.opt) === null || _b === void 0 ? void 0 : _b.packageName};`); } } runAfter() { } /** * Adapt field name according to user input. * @param fieldName * @returns */ _adaptField(fieldName) { if (this.opt.keepKeys === true) { return fieldName; } else { return case_1.default.snake(fieldName); } } } /** * Converts a Zod schema into a Protocol Buffers v3 definition. * * @param schema - The Zod object schema to be converted. * @param opt - Optional configuration for the conversion process. * @param opt.strict - Whether to enforce strict mode during AST generation. * @param opt.packageName - The package name to use in the generated Protocol Buffers definition. * @param opt.keepKeys - Whether to keep the original property names in the generated file instead * of converting them to snake_case as per Protobuf conventions. * @param opt.header - Custom header text to include in the generated file. * @param opt.indent - The indentation style to use in the generated file. * @param opt.includeComments - Whether to include comments in the generated Protocol Buffers * definition. * @returns The Protocol Buffers v3 definition as a string. */ function zod2ProtoV3(schema, // TODO: fix any to force only ZodObjects opt = {}) { const astNode = new core_1.Zod2Ast({ strict: opt.strict, skipBasicTypes: true }).build(schema); return new Zod2ProtoV3(opt).transpile(astNode); }