zod-to-x 2.2.1-dev.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -8
- package/dist/core/ast_node.d.ts +1 -1
- package/dist/core/ast_node.js +47 -34
- package/dist/layered-modeling/layer.js +3 -1
- package/dist/layered-modeling/model.d.ts +2 -0
- package/dist/layered-modeling/model.js +1 -1
- package/dist/lib/zod_helpers.js +8 -0
- package/dist/transpilers/cpp/runner.d.ts +15 -2
- package/dist/transpilers/cpp/runner.js +45 -8
- package/dist/transpilers/dart/libs.d.ts +6 -0
- package/dist/transpilers/dart/libs.js +11 -0
- package/dist/transpilers/dart/options.d.ts +21 -0
- package/dist/transpilers/dart/options.js +10 -0
- package/dist/transpilers/dart/runner.d.ts +149 -0
- package/dist/transpilers/dart/runner.js +624 -0
- package/dist/transpilers/go/libs.d.ts +8 -0
- package/dist/transpilers/go/libs.js +13 -0
- package/dist/transpilers/go/options.d.ts +17 -0
- package/dist/transpilers/go/options.js +11 -0
- package/dist/transpilers/go/runner.d.ts +100 -0
- package/dist/transpilers/go/runner.js +399 -0
- package/dist/transpilers/index.d.ts +2 -0
- package/dist/transpilers/index.js +5 -1
- package/dist/transpilers/python/runner.d.ts +7 -0
- package/dist/transpilers/python/runner.js +40 -3
- package/dist/transpilers/typescript/runner.d.ts +6 -0
- package/dist/transpilers/typescript/runner.js +22 -8
- package/package.json +7 -4
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultOpts = void 0;
|
|
4
|
+
exports.defaultOpts = {
|
|
5
|
+
includeComments: true,
|
|
6
|
+
indent: 4,
|
|
7
|
+
useImports: true,
|
|
8
|
+
packageName: "models",
|
|
9
|
+
keepKeys: false,
|
|
10
|
+
useJsonTags: true,
|
|
11
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ASTAliasedTypes, ASTEnum, ASTIntersection, ASTNode, ASTObject, ASTUnion, Zod2X } from "../../core";
|
|
2
|
+
import { IZod2GoOpt } from "./options";
|
|
3
|
+
/**
|
|
4
|
+
* Transpiler for Zod schemas to Go structs and types.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Zod2Go extends Zod2X<IZod2GoOpt> {
|
|
7
|
+
protected readonly commentKey = "//";
|
|
8
|
+
protected lib: {
|
|
9
|
+
timePackage: string;
|
|
10
|
+
jsonPackage: string;
|
|
11
|
+
fmtPackage: string;
|
|
12
|
+
};
|
|
13
|
+
constructor(opt?: IZod2GoOpt);
|
|
14
|
+
protected runBefore(): void;
|
|
15
|
+
protected runAfter(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Consolidates all collected imports into a proper Go import block.
|
|
18
|
+
* Single import → `import "pkg"`, multiple → `import (\n\t"pkg"\n)`.
|
|
19
|
+
*/
|
|
20
|
+
private _consolidateImports;
|
|
21
|
+
protected addImportFromFile(filename: string, namespace: string): string;
|
|
22
|
+
protected getTypeFromExternalNamespace(namespace: string, typeName: string): string;
|
|
23
|
+
protected addExtendedType(name: string, parentNamespace: string, aliasOf: string, opt?: {
|
|
24
|
+
type?: "union" | "alias";
|
|
25
|
+
isInternal?: boolean;
|
|
26
|
+
templates?: string;
|
|
27
|
+
declaredTemplates?: string;
|
|
28
|
+
}): void;
|
|
29
|
+
protected getGenericTemplatesTranslation(data: ASTNode): string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Emits an alias/extension declaration early when a node references another layered type.
|
|
32
|
+
*/
|
|
33
|
+
protected checkExtendedTypeInclusion(data: ASTNode, type?: "union" | "alias"): boolean;
|
|
34
|
+
protected getStringType: () => string;
|
|
35
|
+
protected getBooleanType: () => string;
|
|
36
|
+
protected getAnyType: () => string;
|
|
37
|
+
protected getDateType: () => string;
|
|
38
|
+
protected getNumberType: (isInt: boolean, range: {
|
|
39
|
+
min?: number;
|
|
40
|
+
max?: number;
|
|
41
|
+
}) => string;
|
|
42
|
+
protected getLiteralStringType(value: string | number | boolean, parentEnumNameKey?: [string, string]): string | number;
|
|
43
|
+
/** Ex: []TypeA, [][]TypeA */
|
|
44
|
+
protected getArrayType(arrayType: string, arrayDeep: number): string;
|
|
45
|
+
/** Ex: map[TypeA]struct{} */
|
|
46
|
+
protected getSetType: (itemType: string) => string;
|
|
47
|
+
/** Ex: map[KeyType]ValueType */
|
|
48
|
+
protected getMapType: (keyType: string, valueType: string) => string;
|
|
49
|
+
/** Ex: map[KeyType]ValueType */
|
|
50
|
+
protected getRecordType: (keyType: string, valueType: string) => string;
|
|
51
|
+
/** Go has no native tuple; use []any */
|
|
52
|
+
protected getTupleType: (_itemsType: string[]) => string;
|
|
53
|
+
/** Go has no native union; use any */
|
|
54
|
+
protected getUnionType: (_itemsType: string[]) => string;
|
|
55
|
+
/** Handled entirely in transpileIntersection via struct embedding */
|
|
56
|
+
protected getIntersectionType: () => string;
|
|
57
|
+
protected transpileAliasedType(data: ASTAliasedTypes): void;
|
|
58
|
+
/**
|
|
59
|
+
* Emit a Go enum using a typed string or int alias + const block.
|
|
60
|
+
*
|
|
61
|
+
* All-string values:
|
|
62
|
+
* type EnumItem string
|
|
63
|
+
* const (
|
|
64
|
+
* EnumItemEnum1 EnumItem = "Enum1"
|
|
65
|
+
* )
|
|
66
|
+
*
|
|
67
|
+
* All-int values:
|
|
68
|
+
* type EnumItem int
|
|
69
|
+
* const (
|
|
70
|
+
* EnumItemNativeEnum1 EnumItem = 1
|
|
71
|
+
* )
|
|
72
|
+
*
|
|
73
|
+
* Mixed (int + string): untyped constants with warning comment.
|
|
74
|
+
*/
|
|
75
|
+
protected transpileEnum(data: ASTEnum): void;
|
|
76
|
+
/**
|
|
77
|
+
* Go union: emit `type Name any` with a comment listing possible types.
|
|
78
|
+
*
|
|
79
|
+
* For discriminated unions: emit a marker interface + marker stubs on each
|
|
80
|
+
* member type + an `UnmarshalXxx` helper that dispatches on the discriminant
|
|
81
|
+
* key using a `json.RawMessage` probe (uniform for string, bool, and number
|
|
82
|
+
* discriminant values).
|
|
83
|
+
*/
|
|
84
|
+
protected transpileUnion(data: ASTUnion): void;
|
|
85
|
+
/**
|
|
86
|
+
* Go intersection: struct embedding.
|
|
87
|
+
*
|
|
88
|
+
* type IntersectionItem struct {
|
|
89
|
+
* ObjectItem
|
|
90
|
+
* OtherObjectItem
|
|
91
|
+
* }
|
|
92
|
+
*/
|
|
93
|
+
protected transpileIntersection(data: ASTIntersection): void;
|
|
94
|
+
protected transpileStruct(data: ASTObject): void;
|
|
95
|
+
/** Render a Go struct body for an ASTObject. */
|
|
96
|
+
private _transpileStructBody;
|
|
97
|
+
/** Render a single struct field: `FieldName Type \`json:"key"\`` */
|
|
98
|
+
private _transpileMember;
|
|
99
|
+
private _buildJsonTag;
|
|
100
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Zod2Go = void 0;
|
|
7
|
+
const case_1 = __importDefault(require("case"));
|
|
8
|
+
const core_1 = require("../../core");
|
|
9
|
+
const number_limits_1 = require("../../utils/number_limits");
|
|
10
|
+
const libs_1 = require("./libs");
|
|
11
|
+
const options_1 = require("./options");
|
|
12
|
+
/**
|
|
13
|
+
* Transpiler for Zod schemas to Go structs and types.
|
|
14
|
+
*/
|
|
15
|
+
class Zod2Go extends core_1.Zod2X {
|
|
16
|
+
constructor(opt = {}) {
|
|
17
|
+
super(Object.assign(Object.assign({}, options_1.defaultOpts), opt));
|
|
18
|
+
this.commentKey = "//";
|
|
19
|
+
// ── Primitive type methods ──────────────────────────────────────────────
|
|
20
|
+
this.getStringType = () => "string";
|
|
21
|
+
this.getBooleanType = () => "bool";
|
|
22
|
+
this.getAnyType = () => "any";
|
|
23
|
+
this.getDateType = () => {
|
|
24
|
+
this.imports.add(this.lib.timePackage);
|
|
25
|
+
return "time.Time";
|
|
26
|
+
};
|
|
27
|
+
this.getNumberType = (isInt, range) => {
|
|
28
|
+
if (!isInt)
|
|
29
|
+
return "float64";
|
|
30
|
+
const min = range.min;
|
|
31
|
+
const max = range.max;
|
|
32
|
+
if (min !== undefined &&
|
|
33
|
+
max !== undefined &&
|
|
34
|
+
min >= number_limits_1.INT32_RANGES[0] &&
|
|
35
|
+
max <= number_limits_1.INT32_RANGES[1]) {
|
|
36
|
+
return "int32";
|
|
37
|
+
}
|
|
38
|
+
return "int64";
|
|
39
|
+
};
|
|
40
|
+
/** Ex: map[TypeA]struct{} */
|
|
41
|
+
this.getSetType = (itemType) => `map[${itemType}]struct{}`;
|
|
42
|
+
/** Ex: map[KeyType]ValueType */
|
|
43
|
+
this.getMapType = (keyType, valueType) => `map[${keyType}]${valueType}`;
|
|
44
|
+
/** Ex: map[KeyType]ValueType */
|
|
45
|
+
this.getRecordType = (keyType, valueType) => `map[${keyType}]${valueType}`;
|
|
46
|
+
/** Go has no native tuple; use []any */
|
|
47
|
+
this.getTupleType = (_itemsType) => "[]any";
|
|
48
|
+
/** Go has no native union; use any */
|
|
49
|
+
this.getUnionType = (_itemsType) => "any";
|
|
50
|
+
/** Handled entirely in transpileIntersection via struct embedding */
|
|
51
|
+
this.getIntersectionType = () => "";
|
|
52
|
+
this.lib = (0, libs_1.getLibs)();
|
|
53
|
+
}
|
|
54
|
+
runBefore() {
|
|
55
|
+
var _a;
|
|
56
|
+
this.preImports.add(`package ${(_a = this.opt.packageName) !== null && _a !== void 0 ? _a : "models"}`);
|
|
57
|
+
}
|
|
58
|
+
runAfter() {
|
|
59
|
+
this._consolidateImports();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Consolidates all collected imports into a proper Go import block.
|
|
63
|
+
* Single import → `import "pkg"`, multiple → `import (\n\t"pkg"\n)`.
|
|
64
|
+
*/
|
|
65
|
+
_consolidateImports() {
|
|
66
|
+
if (this.imports.size === 0)
|
|
67
|
+
return;
|
|
68
|
+
const sorted = Array.from(this.imports).sort();
|
|
69
|
+
let block;
|
|
70
|
+
if (sorted.length === 1) {
|
|
71
|
+
block = `import ${sorted[0]}`;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
block = `import (\n${sorted.map((s) => `\t${s}`).join("\n")}\n)`;
|
|
75
|
+
}
|
|
76
|
+
// Replace the individual entries with the consolidated block
|
|
77
|
+
this.imports.clear();
|
|
78
|
+
this.imports.add(block);
|
|
79
|
+
}
|
|
80
|
+
addImportFromFile(filename, namespace) {
|
|
81
|
+
const base = filename.endsWith(".go") ? filename.slice(0, -3) : filename;
|
|
82
|
+
return `${namespace} "./${base}"`;
|
|
83
|
+
}
|
|
84
|
+
getTypeFromExternalNamespace(namespace, typeName) {
|
|
85
|
+
return `${namespace}.${typeName}`;
|
|
86
|
+
}
|
|
87
|
+
addExtendedType(name, parentNamespace, aliasOf, opt) {
|
|
88
|
+
var _a, _b;
|
|
89
|
+
const extendedType = (opt === null || opt === void 0 ? void 0 : opt.isInternal)
|
|
90
|
+
? aliasOf
|
|
91
|
+
: this.getTypeFromExternalNamespace(parentNamespace, aliasOf);
|
|
92
|
+
const templates = (_a = opt === null || opt === void 0 ? void 0 : opt.templates) !== null && _a !== void 0 ? _a : "";
|
|
93
|
+
const declaredTemplates = (_b = opt === null || opt === void 0 ? void 0 : opt.declaredTemplates) !== null && _b !== void 0 ? _b : "";
|
|
94
|
+
if ((opt === null || opt === void 0 ? void 0 : opt.type) === "alias" || (opt === null || opt === void 0 ? void 0 : opt.type) === "union") {
|
|
95
|
+
this.push0(`type ${name}${declaredTemplates} = ${extendedType}${templates}\n`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Struct embedding: type ChildName struct { ParentType }
|
|
99
|
+
this.push0(`type ${name}${declaredTemplates} struct {`);
|
|
100
|
+
this.push1(`${extendedType}${templates}`);
|
|
101
|
+
this.push0(`}\n`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
getGenericTemplatesTranslation(data) {
|
|
105
|
+
if ((data instanceof core_1.ASTObject || data instanceof core_1.ASTDefinition) &&
|
|
106
|
+
data.templatesTranslation.length > 0) {
|
|
107
|
+
return ("[" +
|
|
108
|
+
data.templatesTranslation
|
|
109
|
+
.map((t) => {
|
|
110
|
+
if (this.isExternalTypeImport(t)) {
|
|
111
|
+
this.addExternalTypeImport(t);
|
|
112
|
+
return this.getTypeFromExternalNamespace(t.parentNamespace, t.aliasOf);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
return t.aliasOf;
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
.join(", ") +
|
|
119
|
+
"]");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Emits an alias/extension declaration early when a node references another layered type.
|
|
124
|
+
*/
|
|
125
|
+
checkExtendedTypeInclusion(data, type) {
|
|
126
|
+
const isStruct = data instanceof core_1.ASTObject ||
|
|
127
|
+
(data instanceof core_1.ASTIntersection && data.newObject !== undefined);
|
|
128
|
+
const translatedTemplates = this.getGenericTemplatesTranslation(data);
|
|
129
|
+
const templates = translatedTemplates || undefined;
|
|
130
|
+
// For declared-template fallback on the declared (definition) side
|
|
131
|
+
const declaredTemplates = !translatedTemplates && data instanceof core_1.ASTObject && data.templates.size > 0
|
|
132
|
+
? `[${[...data.templates].map((t) => `${t} any`).join(", ")}]`
|
|
133
|
+
: undefined;
|
|
134
|
+
if (this.isExternalTypeImport(data)) {
|
|
135
|
+
if (data.aliasOf) {
|
|
136
|
+
this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
|
|
137
|
+
type: isStruct ? undefined : type,
|
|
138
|
+
templates,
|
|
139
|
+
declaredTemplates,
|
|
140
|
+
});
|
|
141
|
+
this.addExternalTypeImport(data);
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
else if (data.aliasOf) {
|
|
146
|
+
this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
|
|
147
|
+
type: isStruct ? undefined : type,
|
|
148
|
+
isInternal: true,
|
|
149
|
+
templates,
|
|
150
|
+
declaredTemplates,
|
|
151
|
+
});
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
getLiteralStringType(value, parentEnumNameKey) {
|
|
157
|
+
if (parentEnumNameKey) {
|
|
158
|
+
// Go constants cannot be used as types; use the parent enum type name
|
|
159
|
+
return parentEnumNameKey[0];
|
|
160
|
+
}
|
|
161
|
+
// Go has no literal types; return the underlying primitive type
|
|
162
|
+
if (typeof value === "boolean")
|
|
163
|
+
return "bool";
|
|
164
|
+
if (typeof value === "number")
|
|
165
|
+
return isNaN(value) ? "float64" : Number.isInteger(value) ? "int64" : "float64";
|
|
166
|
+
return "string";
|
|
167
|
+
}
|
|
168
|
+
// ── Composite type methods ──────────────────────────────────────────────
|
|
169
|
+
/** Ex: []TypeA, [][]TypeA */
|
|
170
|
+
getArrayType(arrayType, arrayDeep) {
|
|
171
|
+
let output = `[]${arrayType}`;
|
|
172
|
+
for (let i = 0; i < arrayDeep - 1; i++) {
|
|
173
|
+
output = `[]${output}`;
|
|
174
|
+
}
|
|
175
|
+
return output;
|
|
176
|
+
}
|
|
177
|
+
// ── Transpile methods ───────────────────────────────────────────────────
|
|
178
|
+
transpileAliasedType(data) {
|
|
179
|
+
if (this.checkExtendedTypeInclusion(data, "alias")) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
this.addComment(data.description);
|
|
183
|
+
let extendedType;
|
|
184
|
+
if (data instanceof core_1.ASTArray) {
|
|
185
|
+
extendedType = this.getAttributeType(data.item);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
extendedType = this.getAttributeType(data);
|
|
189
|
+
}
|
|
190
|
+
this.push0(`type ${data.name} = ${extendedType}\n`);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Emit a Go enum using a typed string or int alias + const block.
|
|
194
|
+
*
|
|
195
|
+
* All-string values:
|
|
196
|
+
* type EnumItem string
|
|
197
|
+
* const (
|
|
198
|
+
* EnumItemEnum1 EnumItem = "Enum1"
|
|
199
|
+
* )
|
|
200
|
+
*
|
|
201
|
+
* All-int values:
|
|
202
|
+
* type EnumItem int
|
|
203
|
+
* const (
|
|
204
|
+
* EnumItemNativeEnum1 EnumItem = 1
|
|
205
|
+
* )
|
|
206
|
+
*
|
|
207
|
+
* Mixed (int + string): untyped constants with warning comment.
|
|
208
|
+
*/
|
|
209
|
+
transpileEnum(data) {
|
|
210
|
+
if (this.checkExtendedTypeInclusion(data, "alias")) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
this.addComment(data.description);
|
|
214
|
+
const allStrings = data.values.every(([, v]) => typeof v === "string");
|
|
215
|
+
const allInts = data.values.every(([, v]) => typeof v === "number");
|
|
216
|
+
if (allStrings) {
|
|
217
|
+
this.push0(`type ${data.name} string\n`);
|
|
218
|
+
this.push0(`const (`);
|
|
219
|
+
data.values.forEach(([key, value]) => {
|
|
220
|
+
const constName = `${data.name}${case_1.default.pascal(key)}`;
|
|
221
|
+
this.push1(`${constName} ${data.name} = "${value}"`);
|
|
222
|
+
});
|
|
223
|
+
this.push0(`)\n`);
|
|
224
|
+
}
|
|
225
|
+
else if (allInts) {
|
|
226
|
+
this.push0(`type ${data.name} int\n`);
|
|
227
|
+
this.push0(`const (`);
|
|
228
|
+
data.values.forEach(([key, value]) => {
|
|
229
|
+
const constName = `${data.name}${case_1.default.pascal(key)}`;
|
|
230
|
+
this.push1(`${constName} ${data.name} = ${value}`);
|
|
231
|
+
});
|
|
232
|
+
this.push0(`)\n`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// Mixed types — Go cannot express this as a single typed enum.
|
|
236
|
+
// Declare as `any` so struct fields can reference the type name.
|
|
237
|
+
this.push0(`type ${data.name} = any\n`);
|
|
238
|
+
this.output.push(`// ${data.name}: mixed-type enum — no single Go base type available`);
|
|
239
|
+
this.push0(`const (`);
|
|
240
|
+
data.values.forEach(([key, value]) => {
|
|
241
|
+
const constName = `${data.name}${case_1.default.pascal(key)}`;
|
|
242
|
+
const v = typeof value === "string" ? `"${value}"` : `${value}`;
|
|
243
|
+
this.push1(`${constName} = ${v}`);
|
|
244
|
+
});
|
|
245
|
+
this.push0(`)\n`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Go union: emit `type Name any` with a comment listing possible types.
|
|
250
|
+
*
|
|
251
|
+
* For discriminated unions: emit a marker interface + marker stubs on each
|
|
252
|
+
* member type + an `UnmarshalXxx` helper that dispatches on the discriminant
|
|
253
|
+
* key using a `json.RawMessage` probe (uniform for string, bool, and number
|
|
254
|
+
* discriminant values).
|
|
255
|
+
*/
|
|
256
|
+
transpileUnion(data) {
|
|
257
|
+
if (this.checkExtendedTypeInclusion(data, "union")) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
this.addComment(data.description);
|
|
261
|
+
const optionNames = data.options.map(this.getAttributeType.bind(this));
|
|
262
|
+
if (data.discriminantKey) {
|
|
263
|
+
const methodName = `is${data.name}`;
|
|
264
|
+
this.push0(`// ${data.name} is a discriminated union on "${data.discriminantKey}".`);
|
|
265
|
+
this.push0(`// Possible types: ${optionNames.join(", ")}`);
|
|
266
|
+
this.push0(`type ${data.name} interface {`);
|
|
267
|
+
this.push1(`${methodName}()`);
|
|
268
|
+
this.push0(`}\n`);
|
|
269
|
+
// Marker stubs: one no-op method per member type so each satisfies the interface.
|
|
270
|
+
// For generic instantiations (e.g. "HttpSuccessfulResponse[SomeDtoResult]") we emit
|
|
271
|
+
// a generic receiver "func (t Base[T]) isXxx() {}" using the base type name only.
|
|
272
|
+
for (const name of optionNames) {
|
|
273
|
+
const bracketIdx = name.indexOf("[");
|
|
274
|
+
const receiver = bracketIdx !== -1 ? `${name.slice(0, bracketIdx)}[T]` : name;
|
|
275
|
+
this.push0(`func (t ${receiver}) ${methodName}() {}\n`);
|
|
276
|
+
}
|
|
277
|
+
// UnmarshalXxx helper — only when every member has a discriminant value in the AST.
|
|
278
|
+
const optionsData = data.options.map((opt, i) => {
|
|
279
|
+
var _a;
|
|
280
|
+
return ({
|
|
281
|
+
typeName: optionNames[i],
|
|
282
|
+
discriminantValue: (_a = opt.constraints) === null || _a === void 0 ? void 0 : _a.discriminantValue,
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
const allHaveDiscriminantValue = optionsData.every((o) => o.discriminantValue !== undefined);
|
|
286
|
+
if (allHaveDiscriminantValue) {
|
|
287
|
+
this.imports.add(this.lib.jsonPackage);
|
|
288
|
+
this.imports.add(this.lib.fmtPackage);
|
|
289
|
+
const probeField = data.discriminantKey.charAt(0).toUpperCase() + data.discriminantKey.slice(1);
|
|
290
|
+
this.push0(`// Unmarshal${data.name} deserializes JSON into the correct ${data.name} concrete type`);
|
|
291
|
+
this.push0(`// by probing the "${data.discriminantKey}" discriminant field.`);
|
|
292
|
+
this.push0(`func Unmarshal${data.name}(data []byte) (${data.name}, error) {`);
|
|
293
|
+
this.push1(`var probe struct {`);
|
|
294
|
+
this.push2(`${probeField} json.RawMessage \`json:"${data.discriminantKey}"\``);
|
|
295
|
+
this.push1(`}`);
|
|
296
|
+
this.push1(`if err := json.Unmarshal(data, &probe); err != nil {`);
|
|
297
|
+
this.push2(`return nil, err`);
|
|
298
|
+
this.push1(`}`);
|
|
299
|
+
this.push1(`switch string(probe.${probeField}) {`);
|
|
300
|
+
for (const opt of optionsData) {
|
|
301
|
+
const dv = opt.discriminantValue;
|
|
302
|
+
// Determine raw JSON representation of the case value.
|
|
303
|
+
// String literals appear in JSON with surrounding quotes ("Enum1" → "Enum1").
|
|
304
|
+
// Bool and number literals appear without quotes (true → true, 42 → 42).
|
|
305
|
+
const isStringLiteral = isNaN(Number(dv)) && dv !== "true" && dv !== "false";
|
|
306
|
+
const caseVal = isStringLiteral ? `\`"${dv}"\`` : `"${dv}"`;
|
|
307
|
+
this.push1(`case ${caseVal}:`);
|
|
308
|
+
this.push2(`var v ${opt.typeName}`);
|
|
309
|
+
this.push2(`if err := json.Unmarshal(data, &v); err != nil {`);
|
|
310
|
+
this.push3(`return nil, err`);
|
|
311
|
+
this.push2(`}`);
|
|
312
|
+
this.push2(`return v, nil`);
|
|
313
|
+
}
|
|
314
|
+
this.push1(`}`);
|
|
315
|
+
this.push1(`return nil, fmt.Errorf("failed to deserialize ${data.name}: unknown discriminator %s", string(probe.${probeField}))`);
|
|
316
|
+
this.push0(`}\n`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
this.push0(`// ${data.name} is a union of: ${optionNames.join(", ")}`);
|
|
321
|
+
this.push0(`type ${data.name} = any\n`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Go intersection: struct embedding.
|
|
326
|
+
*
|
|
327
|
+
* type IntersectionItem struct {
|
|
328
|
+
* ObjectItem
|
|
329
|
+
* OtherObjectItem
|
|
330
|
+
* }
|
|
331
|
+
*/
|
|
332
|
+
transpileIntersection(data) {
|
|
333
|
+
if (this.checkExtendedTypeInclusion(data)) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
this.addComment(data.description);
|
|
337
|
+
if (data.newObject) {
|
|
338
|
+
// Flatten the merged object into a plain struct
|
|
339
|
+
this._transpileStructBody(data.newObject);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Embed both sides
|
|
343
|
+
const leftType = this.getAttributeType(data.left);
|
|
344
|
+
const rightType = this.getAttributeType(data.right);
|
|
345
|
+
this.push0(`type ${data.name} struct {`);
|
|
346
|
+
this.push1(leftType);
|
|
347
|
+
this.push1(rightType);
|
|
348
|
+
this.push0(`}\n`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
transpileStruct(data) {
|
|
352
|
+
if (this.checkExtendedTypeInclusion(data)) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
this.addComment(data.description);
|
|
356
|
+
this._transpileStructBody(data);
|
|
357
|
+
}
|
|
358
|
+
/** Render a Go struct body for an ASTObject. */
|
|
359
|
+
_transpileStructBody(data) {
|
|
360
|
+
const templateParams = data.templates.size > 0
|
|
361
|
+
? `[${[...data.templates].map((t) => `${t} any`).join(", ")}]`
|
|
362
|
+
: "";
|
|
363
|
+
this.push0(`type ${data.name}${templateParams} struct {`);
|
|
364
|
+
const hasProperties = Object.keys(data.properties).length > 0;
|
|
365
|
+
if (!hasProperties) {
|
|
366
|
+
this.push0(`}\n`);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
for (const [key, value] of Object.entries(data.properties)) {
|
|
370
|
+
const fieldName = this.opt.keepKeys === true ? key : case_1.default.pascal(key);
|
|
371
|
+
this._transpileMember(fieldName, key, value);
|
|
372
|
+
}
|
|
373
|
+
this.push0(`}\n`);
|
|
374
|
+
}
|
|
375
|
+
/** Render a single struct field: `FieldName Type \`json:"key"\`` */
|
|
376
|
+
_transpileMember(fieldName, originalKey, memberNode) {
|
|
377
|
+
const isOptionalOrNullable = memberNode.isOptional || memberNode.isNullable;
|
|
378
|
+
let varType = this.getAttributeType(memberNode);
|
|
379
|
+
// Optional/nullable fields use pointer types
|
|
380
|
+
if (isOptionalOrNullable) {
|
|
381
|
+
// Avoid double-pointer for types already expressed as pointers/interfaces
|
|
382
|
+
if (!varType.startsWith("*") && varType !== "any") {
|
|
383
|
+
varType = `*${varType}`;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (memberNode.description && !memberNode.name && !this.isTranspilerable(memberNode)) {
|
|
387
|
+
this.addComment(memberNode.description, `\n${this.indent[1]}`);
|
|
388
|
+
}
|
|
389
|
+
const tag = this._buildJsonTag(originalKey, isOptionalOrNullable !== null && isOptionalOrNullable !== void 0 ? isOptionalOrNullable : false);
|
|
390
|
+
this.push1(`${fieldName} ${varType}${tag}`);
|
|
391
|
+
}
|
|
392
|
+
_buildJsonTag(originalKey, omitempty) {
|
|
393
|
+
if (this.opt.useJsonTags === false)
|
|
394
|
+
return "";
|
|
395
|
+
const flags = omitempty ? `,omitempty` : "";
|
|
396
|
+
return ` \`json:"${originalKey}${flags}"\``;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
exports.Zod2Go = Zod2Go;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Zod2Py = exports.Zod2Cpp17 = exports.Zod2Cpp = exports.Zod2Ts = void 0;
|
|
3
|
+
exports.Zod2Dart = exports.Zod2Go = exports.Zod2Py = exports.Zod2Cpp17 = exports.Zod2Cpp = exports.Zod2Ts = void 0;
|
|
4
4
|
var runner_1 = require("./typescript/runner");
|
|
5
5
|
Object.defineProperty(exports, "Zod2Ts", { enumerable: true, get: function () { return runner_1.Zod2Ts; } });
|
|
6
6
|
var runner_2 = require("./cpp/runner");
|
|
@@ -8,3 +8,7 @@ Object.defineProperty(exports, "Zod2Cpp", { enumerable: true, get: function () {
|
|
|
8
8
|
Object.defineProperty(exports, "Zod2Cpp17", { enumerable: true, get: function () { return runner_2.Zod2Cpp17; } });
|
|
9
9
|
var runner_3 = require("./python/runner");
|
|
10
10
|
Object.defineProperty(exports, "Zod2Py", { enumerable: true, get: function () { return runner_3.Zod2Py; } });
|
|
11
|
+
var runner_4 = require("./go/runner");
|
|
12
|
+
Object.defineProperty(exports, "Zod2Go", { enumerable: true, get: function () { return runner_4.Zod2Go; } });
|
|
13
|
+
var runner_5 = require("./dart/runner");
|
|
14
|
+
Object.defineProperty(exports, "Zod2Dart", { enumerable: true, get: function () { return runner_5.Zod2Dart; } });
|
|
@@ -23,6 +23,7 @@ export declare class Zod2Py extends Zod2X<IZod2PyOpt> {
|
|
|
23
23
|
};
|
|
24
24
|
private baseSchemaAdded;
|
|
25
25
|
private typeVars;
|
|
26
|
+
private pendingTypeVars;
|
|
26
27
|
constructor(opt?: IZod2PyOpt);
|
|
27
28
|
protected runAfter(): void;
|
|
28
29
|
protected runBefore(): void;
|
|
@@ -31,6 +32,7 @@ export declare class Zod2Py extends Zod2X<IZod2PyOpt> {
|
|
|
31
32
|
* This is the base class for all Pydantic models with shared configuration.
|
|
32
33
|
*/
|
|
33
34
|
private _addBaseSchema;
|
|
35
|
+
private _flushPendingTypeVars;
|
|
34
36
|
/**
|
|
35
37
|
* Declares TypeVars that haven't been declared yet.
|
|
36
38
|
* Adds them right before their first usage.
|
|
@@ -51,6 +53,11 @@ export declare class Zod2Py extends Zod2X<IZod2PyOpt> {
|
|
|
51
53
|
isClass?: boolean;
|
|
52
54
|
}): void;
|
|
53
55
|
protected getGenericTemplatesTranslation(data: ASTNode): string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Emits an alias/extension declaration early for layered references.
|
|
58
|
+
* It keeps concrete template translations and falls back to declared templates (e.g. [T])
|
|
59
|
+
* for aliases of generic templates.
|
|
60
|
+
*/
|
|
54
61
|
protected checkExtendedTypeInclusion(data: ASTNode, type?: "alias" | "union" | "d-union"): boolean;
|
|
55
62
|
protected getAnyType: () => string;
|
|
56
63
|
protected getBooleanType: () => string;
|
|
@@ -14,6 +14,7 @@ class Zod2Py extends core_1.Zod2X {
|
|
|
14
14
|
this.commentKey = "#";
|
|
15
15
|
this.baseSchemaAdded = false;
|
|
16
16
|
this.typeVars = new Set();
|
|
17
|
+
this.pendingTypeVars = new Set();
|
|
17
18
|
this.getAnyType = () => {
|
|
18
19
|
this.imports.add(this.lib.anyType);
|
|
19
20
|
return "Any";
|
|
@@ -51,6 +52,9 @@ class Zod2Py extends core_1.Zod2X {
|
|
|
51
52
|
this.lib = (0, libs_1.getLibs)();
|
|
52
53
|
}
|
|
53
54
|
runAfter() {
|
|
55
|
+
if (!this.baseSchemaAdded) {
|
|
56
|
+
this._flushPendingTypeVars(true);
|
|
57
|
+
}
|
|
54
58
|
this._consolidateImports();
|
|
55
59
|
}
|
|
56
60
|
runBefore() { }
|
|
@@ -76,6 +80,22 @@ class Zod2Py extends core_1.Zod2X {
|
|
|
76
80
|
this.push2("use_enum_values=True");
|
|
77
81
|
this.push1(")");
|
|
78
82
|
this.push0("");
|
|
83
|
+
this._flushPendingTypeVars(false);
|
|
84
|
+
}
|
|
85
|
+
_flushPendingTypeVars(prepend) {
|
|
86
|
+
if (this.pendingTypeVars.size === 0)
|
|
87
|
+
return;
|
|
88
|
+
const pending = Array.from(this.pendingTypeVars);
|
|
89
|
+
const lines = pending.map((typeVar) => `${typeVar} = TypeVar('${typeVar}')`);
|
|
90
|
+
if (prepend) {
|
|
91
|
+
this.output = [...lines, "", ...this.output];
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
lines.forEach((line) => this.push0(line));
|
|
95
|
+
this.push0("");
|
|
96
|
+
}
|
|
97
|
+
pending.forEach((typeVar) => this.typeVars.add(typeVar));
|
|
98
|
+
this.pendingTypeVars.clear();
|
|
79
99
|
}
|
|
80
100
|
/**
|
|
81
101
|
* Declares TypeVars that haven't been declared yet.
|
|
@@ -83,10 +103,14 @@ class Zod2Py extends core_1.Zod2X {
|
|
|
83
103
|
* Ex: T = TypeVar('T')
|
|
84
104
|
*/
|
|
85
105
|
_declareNewTypeVars(templates) {
|
|
86
|
-
const newTypeVars = Array.from(templates).filter((t) => !this.typeVars.has(t));
|
|
106
|
+
const newTypeVars = Array.from(templates).filter((t) => !this.typeVars.has(t) && !this.pendingTypeVars.has(t));
|
|
87
107
|
if (newTypeVars.length === 0)
|
|
88
108
|
return;
|
|
89
109
|
this.imports.add(this.lib.typeVarType);
|
|
110
|
+
if (!this.baseSchemaAdded) {
|
|
111
|
+
newTypeVars.forEach((typeVar) => this.pendingTypeVars.add(typeVar));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
90
114
|
newTypeVars.forEach((typeVar) => {
|
|
91
115
|
this.push0(`${typeVar} = TypeVar('${typeVar}')`);
|
|
92
116
|
this.typeVars.add(typeVar);
|
|
@@ -183,15 +207,28 @@ class Zod2Py extends core_1.Zod2X {
|
|
|
183
207
|
"]");
|
|
184
208
|
}
|
|
185
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Emits an alias/extension declaration early for layered references.
|
|
212
|
+
* It keeps concrete template translations and falls back to declared templates (e.g. [T])
|
|
213
|
+
* for aliases of generic templates.
|
|
214
|
+
*/
|
|
186
215
|
checkExtendedTypeInclusion(data, type) {
|
|
187
216
|
// Determine if the aliased type is a class (ASTObject or ASTIntersection with newObject)
|
|
188
217
|
const isClass = data instanceof core_1.ASTObject ||
|
|
189
218
|
(data instanceof core_1.ASTIntersection && data.newObject !== undefined);
|
|
219
|
+
const declaredTemplatesFallback = data instanceof core_1.ASTObject && data.templates.size > 0 && this.isExternalTypeImport(data)
|
|
220
|
+
? `[${[...data.templates].join(", ")}]`
|
|
221
|
+
: undefined;
|
|
222
|
+
const translatedTemplates = this.getGenericTemplatesTranslation(data);
|
|
223
|
+
const templates = translatedTemplates || declaredTemplatesFallback;
|
|
224
|
+
if (!translatedTemplates && data instanceof core_1.ASTObject && data.templates.size > 0) {
|
|
225
|
+
this._declareNewTypeVars(data.templates);
|
|
226
|
+
}
|
|
190
227
|
if (this.isExternalTypeImport(data)) {
|
|
191
228
|
if (data.aliasOf) {
|
|
192
229
|
this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
|
|
193
230
|
type,
|
|
194
|
-
templates
|
|
231
|
+
templates,
|
|
195
232
|
isClass,
|
|
196
233
|
});
|
|
197
234
|
this.addExternalTypeImport(data);
|
|
@@ -202,7 +239,7 @@ class Zod2Py extends core_1.Zod2X {
|
|
|
202
239
|
this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
|
|
203
240
|
type,
|
|
204
241
|
isInternal: true,
|
|
205
|
-
templates
|
|
242
|
+
templates,
|
|
206
243
|
isClass,
|
|
207
244
|
});
|
|
208
245
|
return true;
|
|
@@ -11,8 +11,14 @@ export declare class Zod2Ts extends Zod2X<IZod2TsOpt> {
|
|
|
11
11
|
type?: "union" | "d-union" | "alias";
|
|
12
12
|
isInternal?: boolean;
|
|
13
13
|
templates?: string;
|
|
14
|
+
declaredTemplates?: string;
|
|
14
15
|
}): void;
|
|
15
16
|
protected getGenericTemplatesTranslation(data: ASTNode): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Emits an alias/extension declaration early when a node references another layered type.
|
|
19
|
+
* It preserves concrete template translations and falls back to declared templates (e.g. <T>)
|
|
20
|
+
* for aliases of generic templates.
|
|
21
|
+
*/
|
|
16
22
|
protected checkExtendedTypeInclusion(data: ASTNode, type?: "alias" | "union" | "d-union"): boolean;
|
|
17
23
|
protected getAnyType: () => string;
|
|
18
24
|
protected getBooleanType: () => string;
|