@typra/emitter 0.2.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.
Files changed (82) hide show
  1. package/dist/src/cleanup/generated-file.d.ts +6 -0
  2. package/dist/src/cleanup/generated-file.js +61 -0
  3. package/dist/src/cli.d.ts +2 -0
  4. package/dist/src/cli.js +110 -0
  5. package/dist/src/decorators.d.ts +56 -0
  6. package/dist/src/decorators.js +177 -0
  7. package/dist/src/emitter.d.ts +13 -0
  8. package/dist/src/emitter.js +137 -0
  9. package/dist/src/generate.d.ts +86 -0
  10. package/dist/src/generate.js +104 -0
  11. package/dist/src/index.d.ts +4 -0
  12. package/dist/src/index.js +5 -0
  13. package/dist/src/ir/ast.d.ts +235 -0
  14. package/dist/src/ir/ast.js +589 -0
  15. package/dist/src/ir/declarations.d.ts +364 -0
  16. package/dist/src/ir/declarations.js +23 -0
  17. package/dist/src/ir/expansion.d.ts +140 -0
  18. package/dist/src/ir/expansion.js +407 -0
  19. package/dist/src/ir/lower.d.ts +53 -0
  20. package/dist/src/ir/lower.js +480 -0
  21. package/dist/src/ir/utilities.d.ts +12 -0
  22. package/dist/src/ir/utilities.js +39 -0
  23. package/dist/src/ir/visitor.d.ts +29 -0
  24. package/dist/src/ir/visitor.js +48 -0
  25. package/dist/src/languages/csharp/driver.d.ts +5 -0
  26. package/dist/src/languages/csharp/driver.js +315 -0
  27. package/dist/src/languages/csharp/emitter.d.ts +33 -0
  28. package/dist/src/languages/csharp/emitter.js +1140 -0
  29. package/dist/src/languages/csharp/scaffolding.d.ts +18 -0
  30. package/dist/src/languages/csharp/scaffolding.js +591 -0
  31. package/dist/src/languages/csharp/test-emitter.d.ts +43 -0
  32. package/dist/src/languages/csharp/test-emitter.js +274 -0
  33. package/dist/src/languages/csharp/visitor.d.ts +14 -0
  34. package/dist/src/languages/csharp/visitor.js +79 -0
  35. package/dist/src/languages/go/driver.d.ts +12 -0
  36. package/dist/src/languages/go/driver.js +128 -0
  37. package/dist/src/languages/go/emitter.d.ts +33 -0
  38. package/dist/src/languages/go/emitter.js +879 -0
  39. package/dist/src/languages/go/scaffolding.d.ts +18 -0
  40. package/dist/src/languages/go/scaffolding.js +53 -0
  41. package/dist/src/languages/go/test-emitter.d.ts +20 -0
  42. package/dist/src/languages/go/test-emitter.js +300 -0
  43. package/dist/src/languages/go/visitor.d.ts +14 -0
  44. package/dist/src/languages/go/visitor.js +78 -0
  45. package/dist/src/languages/markdown/driver.d.ts +19 -0
  46. package/dist/src/languages/markdown/driver.js +408 -0
  47. package/dist/src/languages/python/driver.d.ts +14 -0
  48. package/dist/src/languages/python/driver.js +372 -0
  49. package/dist/src/languages/python/emitter.d.ts +31 -0
  50. package/dist/src/languages/python/emitter.js +856 -0
  51. package/dist/src/languages/python/scaffolding.d.ts +33 -0
  52. package/dist/src/languages/python/scaffolding.js +279 -0
  53. package/dist/src/languages/python/test-emitter.d.ts +29 -0
  54. package/dist/src/languages/python/test-emitter.js +388 -0
  55. package/dist/src/languages/python/visitor.d.ts +14 -0
  56. package/dist/src/languages/python/visitor.js +65 -0
  57. package/dist/src/languages/rust/driver.d.ts +13 -0
  58. package/dist/src/languages/rust/driver.js +624 -0
  59. package/dist/src/languages/rust/emitter.d.ts +45 -0
  60. package/dist/src/languages/rust/emitter.js +1596 -0
  61. package/dist/src/languages/rust/visitor.d.ts +25 -0
  62. package/dist/src/languages/rust/visitor.js +153 -0
  63. package/dist/src/languages/typescript/driver.d.ts +8 -0
  64. package/dist/src/languages/typescript/driver.js +209 -0
  65. package/dist/src/languages/typescript/emitter.d.ts +42 -0
  66. package/dist/src/languages/typescript/emitter.js +904 -0
  67. package/dist/src/languages/typescript/scaffolding.d.ts +32 -0
  68. package/dist/src/languages/typescript/scaffolding.js +303 -0
  69. package/dist/src/languages/typescript/test-emitter.d.ts +23 -0
  70. package/dist/src/languages/typescript/test-emitter.js +204 -0
  71. package/dist/src/languages/typescript/visitor.d.ts +14 -0
  72. package/dist/src/languages/typescript/visitor.js +64 -0
  73. package/dist/src/lib.d.ts +33 -0
  74. package/dist/src/lib.js +101 -0
  75. package/dist/src/testing/index.d.ts +2 -0
  76. package/dist/src/testing/index.js +8 -0
  77. package/dist/src/testing/test-context.d.ts +63 -0
  78. package/dist/src/testing/test-context.js +355 -0
  79. package/fixtures/shapes/main.tsp +43 -0
  80. package/fixtures/tspconfig.yaml +13 -0
  81. package/package.json +76 -0
  82. package/src/lib/main.tsp +110 -0
@@ -0,0 +1,1596 @@
1
+ /**
2
+ * Rust code emitter — Declaration IR → Rust source code.
3
+ *
4
+ * Replaces `file.rs.njk` + `_macros.njk` (~1,072 lines of Nunjucks templates)
5
+ * with a typed TypeScript function that walks the FileDecl tree.
6
+ *
7
+ * The emitter produces Rust code using the struct + enum pattern for
8
+ * polymorphic types. Output is post-processed by `cargo fmt`.
9
+ *
10
+ * Key Rust-specific patterns:
11
+ * - Polymorphic types use struct + XxxKind enum (not inheritance)
12
+ * - Variant-specific fields live on enum variants, not child classes
13
+ * - Polymorphic single-ref fields → serde_json::Value
14
+ * - Ownership: String, Option<T>, Vec<T>
15
+ * - Pattern matching for load/save of variant fields
16
+ *
17
+ * Structural blocks emitted (in order):
18
+ * 1. Header comment (auto-generated warning)
19
+ * 2. Imports (context, referenced types)
20
+ * 3. For each polymorphic type:
21
+ * a. XxxKind enum with inline struct variants
22
+ * b. impl Default for XxxKind
23
+ * 4. For each type:
24
+ * a. Struct definition with #[derive(Debug, Clone, Default)]
25
+ * b. impl block:
26
+ * - new(), from_json(), from_yaml()
27
+ * - load_from_value()
28
+ * - kind_str() (polymorphic only)
29
+ * - to_value(), to_json(), to_yaml()
30
+ * - to_wire() (when wire mappings exist)
31
+ * - Collection helpers
32
+ * - Factory methods
33
+ * - Method stubs (as trait)
34
+ */
35
+ import { toSnakeCase } from "../../ir/utilities.js";
36
+ /**
37
+ * Emit a description as a single-line `///` doc comment.
38
+ * Multi-line descriptions are collapsed to a single line.
39
+ */
40
+ function emitDocComment(description, indent, lines) {
41
+ // Collapse multi-line descriptions to a single line
42
+ const oneLine = description.replace(/\r?\n/g, " ").replace(/\s+/g, " ").trim();
43
+ lines.push(`${indent}/// ${oneLine}`);
44
+ }
45
+ function renderLines(lines) {
46
+ while (lines.length > 0 && lines[lines.length - 1] === "") {
47
+ lines.pop();
48
+ }
49
+ return `${lines.join("\n")}\n`;
50
+ }
51
+ /**
52
+ * Convert a string literal value to a PascalCase Rust variant name.
53
+ * e.g., "system" → "System", "tool" → "Tool", "text" → "Text"
54
+ */
55
+ function toPascalCase(s) {
56
+ return s.charAt(0).toUpperCase() + s.slice(1);
57
+ }
58
+ const RUST_KEYWORDS = new Set([
59
+ "as", "break", "const", "continue", "crate", "else", "enum", "extern",
60
+ "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod",
61
+ "move", "mut", "pub", "ref", "return", "self", "Self", "static", "struct",
62
+ "super", "trait", "true", "type", "unsafe", "use", "where", "while",
63
+ "async", "await", "dyn",
64
+ ]);
65
+ function rustFieldName(name) {
66
+ const snake = toSnakeCase(name);
67
+ return RUST_KEYWORDS.has(snake) ? `r#${snake}` : snake;
68
+ }
69
+ /**
70
+ * Emit a Rust enum for a named string-literal type.
71
+ * Uses serde rename for string ↔ enum round-tripping.
72
+ */
73
+ function emitStringEnum(enumDef, lines) {
74
+ const firstVariant = toPascalCase(enumDef.values[0]);
75
+ if (enumDef.isOpen) {
76
+ // Open enums carry String data in Other variant — no Copy
77
+ lines.push("#[derive(Debug, Clone, PartialEq, Eq, Hash)]");
78
+ }
79
+ else {
80
+ lines.push("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]");
81
+ }
82
+ lines.push(`pub enum ${enumDef.name} {`);
83
+ for (const value of enumDef.values) {
84
+ const variant = toPascalCase(value);
85
+ lines.push(` ${variant},`);
86
+ }
87
+ if (enumDef.isOpen) {
88
+ lines.push(" /// Unknown variant (open enum — accepts any string).");
89
+ lines.push(" Other(String),");
90
+ }
91
+ lines.push("}");
92
+ lines.push("");
93
+ // impl Default — first variant (only for closed enums; open enums use first variant too)
94
+ lines.push(`impl Default for ${enumDef.name} {`);
95
+ lines.push(" fn default() -> Self {");
96
+ lines.push(` Self::${firstVariant}`);
97
+ lines.push(" }");
98
+ lines.push("}");
99
+ lines.push("");
100
+ // impl Display
101
+ lines.push(`impl std::fmt::Display for ${enumDef.name} {`);
102
+ lines.push(" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {");
103
+ lines.push(" match self {");
104
+ for (const value of enumDef.values) {
105
+ lines.push(` Self::${toPascalCase(value)} => write!(f, "${value}"),`);
106
+ }
107
+ if (enumDef.isOpen) {
108
+ lines.push(" Self::Other(s) => write!(f, \"{}\", s),");
109
+ }
110
+ lines.push(" }");
111
+ lines.push(" }");
112
+ lines.push("}");
113
+ lines.push("");
114
+ // impl From<&str>
115
+ lines.push(`impl ${enumDef.name} {`);
116
+ lines.push(" pub fn from_str_opt(s: &str) -> Option<Self> {");
117
+ lines.push(" match s {");
118
+ for (const value of enumDef.values) {
119
+ lines.push(` "${value}" => Some(Self::${toPascalCase(value)}),`);
120
+ }
121
+ if (enumDef.isOpen) {
122
+ lines.push(" other => Some(Self::Other(other.to_string())),");
123
+ }
124
+ else {
125
+ lines.push(" _ => None,");
126
+ }
127
+ lines.push(" }");
128
+ lines.push(" }");
129
+ lines.push("");
130
+ lines.push(" pub fn as_str(&self) -> &str {");
131
+ lines.push(" match self {");
132
+ for (const value of enumDef.values) {
133
+ lines.push(` Self::${toPascalCase(value)} => "${value}",`);
134
+ }
135
+ if (enumDef.isOpen) {
136
+ lines.push(" Self::Other(s) => s.as_str(),");
137
+ }
138
+ lines.push(" }");
139
+ lines.push(" }");
140
+ lines.push("}");
141
+ lines.push("");
142
+ }
143
+ /**
144
+ * Type mapping from TypeSpec scalar types to Rust types.
145
+ */
146
+ const RUST_TYPE_MAP = {
147
+ string: "String",
148
+ boolean: "bool",
149
+ int32: "i32",
150
+ int64: "i64",
151
+ float32: "f32",
152
+ float64: "f64",
153
+ integer: "i64",
154
+ float: "f64",
155
+ numeric: "f64",
156
+ number: "f64",
157
+ any: "serde_json::Value",
158
+ unknown: "serde_json::Value",
159
+ object: "serde_json::Value",
160
+ dictionary: "serde_json::Value",
161
+ array: "Vec<serde_json::Value>",
162
+ };
163
+ /**
164
+ * Emit a complete Rust source file from a FileDecl.
165
+ *
166
+ * @param file - File declaration to emit
167
+ * @param visitor - Expression visitor
168
+ * @param polymorphicTypeNames - Set of type names that have polymorphic dispatch
169
+ * @param childToParent - Map from child variant name to parent type name
170
+ */
171
+ export function emitRustFile(file, visitor, polymorphicTypeNames, childToParent = new Map()) {
172
+ const lines = [];
173
+ const hasNonProtocol = file.types.some(t => !t.isProtocol);
174
+ const group = file.group || "";
175
+ // Header
176
+ lines.push("// Code generated by Typra emitter; DO NOT EDIT.");
177
+ lines.push("");
178
+ lines.push("#![allow(unused_imports, dead_code, non_camel_case_types, unused_variables, clippy::all)]");
179
+ lines.push("");
180
+ if (hasNonProtocol) {
181
+ // Context is always at the model root.
182
+ // From inside a group subfolder, we need to go up two levels: group → model root.
183
+ const contextPath = group ? "super::super::context" : "super::context";
184
+ lines.push(`use ${contextPath}::{LoadContext, SaveContext};`);
185
+ }
186
+ // Imports — post-process for Rust specifics
187
+ // In Rust, polymorphic child types are enum variants (not standalone structs),
188
+ // so we import ParentKind instead.
189
+ for (const imp of file.imports) {
190
+ const processedNames = [];
191
+ for (const name of imp.names) {
192
+ // Check if this name is a polymorphic child → import ParentKind
193
+ const parentName = childToParent.get(name);
194
+ if (parentName) {
195
+ const kindName = parentName + "Kind";
196
+ if (!processedNames.includes(kindName)) {
197
+ processedNames.push(kindName);
198
+ }
199
+ continue;
200
+ }
201
+ processedNames.push(name);
202
+ }
203
+ if (processedNames.length === 0)
204
+ continue;
205
+ lines.push("");
206
+ const names = processedNames.sort().join(", ");
207
+ // Build the Rust module path based on group relationship
208
+ let modPath;
209
+ if (imp.group === group) {
210
+ // Same group (or both root): sibling module via super
211
+ modPath = `super::${toSnakeCase(imp.module)}`;
212
+ }
213
+ else if (imp.group) {
214
+ // Different non-empty group: go up to model root, then into the group
215
+ modPath = `super::super::${imp.group}::${toSnakeCase(imp.module)}`;
216
+ }
217
+ else {
218
+ // Root-level module accessed from inside a group subfolder
219
+ modPath = `super::super::${toSnakeCase(imp.module)}`;
220
+ }
221
+ if (processedNames.length === 1) {
222
+ lines.push(`use ${modPath}::${names};`);
223
+ }
224
+ else {
225
+ lines.push(`use ${modPath}::{${names}};`);
226
+ }
227
+ }
228
+ lines.push("");
229
+ // String-literal enum types
230
+ for (const enumDef of file.enums) {
231
+ emitStringEnum(enumDef, lines);
232
+ }
233
+ // Find the base type (the one that owns the polymorphic dispatch)
234
+ const baseType = file.types.find(t => t.polymorphicDispatch) || file.types[0];
235
+ const childTypes = file.types.filter(t => t !== baseType);
236
+ // Protocol types → emit as trait (no struct, no impl)
237
+ if (baseType.isProtocol) {
238
+ emitProtocolTrait(baseType, lines);
239
+ lines.push("");
240
+ return renderLines(lines);
241
+ }
242
+ // Collect base field names for variant field extraction
243
+ const baseFieldNames = new Set(baseType.fields.map(f => f.name));
244
+ // Emit enum for polymorphic types
245
+ if (baseType.polymorphicDispatch) {
246
+ emitKindEnum(baseType, childTypes, baseFieldNames, polymorphicTypeNames, lines);
247
+ }
248
+ // Emit struct + impl for the base type (only one struct per file in Rust)
249
+ emitStruct(baseType, polymorphicTypeNames, lines);
250
+ emitImpl(baseType, childTypes, baseFieldNames, visitor, polymorphicTypeNames, lines);
251
+ // Method stubs as trait
252
+ if (baseType.methods.length > 0) {
253
+ emitMethodTrait(baseType, lines);
254
+ }
255
+ // Trailing newlines for child types that were folded into the enum
256
+ for (const _ of childTypes) {
257
+ lines.push("");
258
+ }
259
+ lines.push("");
260
+ return renderLines(lines);
261
+ }
262
+ // ============================================================================
263
+ // Kind Enum
264
+ // ============================================================================
265
+ function emitKindEnum(baseType, childTypes, baseFieldNames, polymorphicTypeNames, lines) {
266
+ const dispatch = baseType.polymorphicDispatch;
267
+ const enumName = baseType.typeName.name + "Kind";
268
+ lines.push("");
269
+ lines.push(`/// Variant-specific data for [\`${baseType.typeName.name}\`], discriminated by \`${dispatch.discriminatorField}\`.`);
270
+ lines.push("#[derive(Debug, Clone)]");
271
+ lines.push(`pub enum ${enumName} {`);
272
+ // Named variants
273
+ for (const variant of dispatch.variants) {
274
+ const childType = childTypes.find(ct => ct.typeName.name === variant.typeName.name);
275
+ const variantName = variant.typeName.name.replace(baseType.typeName.name, "") || variant.typeName.name;
276
+ const variantFields = childType
277
+ ? childType.fields.filter(f => f.name !== dispatch.discriminatorField && !baseFieldNames.has(f.name))
278
+ : [];
279
+ lines.push(` /// \`${dispatch.discriminatorField}\` = \`"${variant.value}"\``);
280
+ if (variantFields.length === 0) {
281
+ lines.push(` ${variantName},`);
282
+ }
283
+ else {
284
+ lines.push(` ${variantName} {`);
285
+ for (const field of variantFields) {
286
+ if (field.description) {
287
+ emitDocComment(field.description, " ", lines);
288
+ }
289
+ lines.push(` ${rustFieldName(field.name)}: ${fieldType(field, polymorphicTypeNames)},`);
290
+ }
291
+ lines.push(` },`);
292
+ }
293
+ }
294
+ // Wildcard/default variant
295
+ if (dispatch.defaultVariant) {
296
+ const defaultType = childTypes.find(ct => ct.typeName.name === dispatch.defaultVariant.typeName.name);
297
+ const isSelfRef = dispatch.defaultVariant.isSelfReference;
298
+ const variantName = isSelfRef
299
+ ? "Custom"
300
+ : (dispatch.defaultVariant.typeName.name.replace(baseType.typeName.name, "") || "Custom");
301
+ const variantFields = isSelfRef
302
+ ? []
303
+ : (defaultType
304
+ ? defaultType.fields.filter(f => f.name !== dispatch.discriminatorField && !baseFieldNames.has(f.name))
305
+ : []);
306
+ lines.push(` /// Wildcard / catch-all variant for unrecognized \`${dispatch.discriminatorField}\` values.`);
307
+ // Wildcard always has kind_name field
308
+ if (variantFields.length === 0) {
309
+ lines.push(` ${variantName} {`);
310
+ lines.push(` /// The raw \`${dispatch.discriminatorField}\` string for this unknown variant.`);
311
+ lines.push(` ${toSnakeCase(dispatch.discriminatorField)}_name: String,`);
312
+ lines.push(` },`);
313
+ }
314
+ else {
315
+ lines.push(` ${variantName} {`);
316
+ for (const field of variantFields) {
317
+ if (field.description) {
318
+ emitDocComment(field.description, " ", lines);
319
+ }
320
+ lines.push(` ${rustFieldName(field.name)}: ${fieldType(field, polymorphicTypeNames)},`);
321
+ }
322
+ lines.push(` /// The raw \`${dispatch.discriminatorField}\` string for this unknown variant.`);
323
+ lines.push(` ${toSnakeCase(dispatch.discriminatorField)}_name: String,`);
324
+ lines.push(` },`);
325
+ }
326
+ }
327
+ lines.push("}");
328
+ lines.push("");
329
+ // impl Default for the enum
330
+ emitEnumDefault(baseType, childTypes, baseFieldNames, dispatch, polymorphicTypeNames, lines);
331
+ }
332
+ function emitEnumDefault(baseType, childTypes, baseFieldNames, dispatch, polymorphicTypeNames, lines) {
333
+ const enumName = baseType.typeName.name + "Kind";
334
+ lines.push(`impl Default for ${enumName} {`);
335
+ lines.push(" fn default() -> Self {");
336
+ if (dispatch.defaultVariant) {
337
+ // Default to the wildcard/custom variant
338
+ const isSelfRef = dispatch.defaultVariant.isSelfReference;
339
+ const variantName = isSelfRef
340
+ ? "Custom"
341
+ : (dispatch.defaultVariant.typeName.name.replace(baseType.typeName.name, "") || "Custom");
342
+ const defaultType = childTypes.find(ct => ct.typeName.name === dispatch.defaultVariant.typeName.name);
343
+ const variantFields = isSelfRef
344
+ ? []
345
+ : (defaultType
346
+ ? defaultType.fields.filter(f => f.name !== dispatch.discriminatorField && !baseFieldNames.has(f.name))
347
+ : []);
348
+ if (variantFields.length === 0) {
349
+ lines.push(` ${enumName}::${variantName} {`);
350
+ lines.push(` ${toSnakeCase(dispatch.discriminatorField)}_name: String::new(),`);
351
+ lines.push(" }");
352
+ }
353
+ else {
354
+ lines.push(` ${enumName}::${variantName} {`);
355
+ for (const field of variantFields) {
356
+ lines.push(` ${rustFieldName(field.name)}: ${fieldDefault(field, polymorphicTypeNames)},`);
357
+ }
358
+ lines.push(` ${toSnakeCase(dispatch.discriminatorField)}_name: String::new(),`);
359
+ lines.push(" }");
360
+ }
361
+ }
362
+ else if (dispatch.variants.length > 0) {
363
+ // Default to the first named variant
364
+ const firstVariant = dispatch.variants[0];
365
+ const childType = childTypes.find(ct => ct.typeName.name === firstVariant.typeName.name);
366
+ const variantName = firstVariant.typeName.name.replace(baseType.typeName.name, "") || firstVariant.typeName.name;
367
+ const variantFields = childType
368
+ ? childType.fields.filter(f => f.name !== dispatch.discriminatorField && !baseFieldNames.has(f.name))
369
+ : [];
370
+ if (variantFields.length === 0) {
371
+ lines.push(` ${enumName}::${variantName}`);
372
+ }
373
+ else {
374
+ lines.push(` ${enumName}::${variantName} {`);
375
+ for (const field of variantFields) {
376
+ lines.push(` ${rustFieldName(field.name)}: ${fieldDefault(field, polymorphicTypeNames)},`);
377
+ }
378
+ lines.push(" }");
379
+ }
380
+ }
381
+ lines.push(" }");
382
+ lines.push("}");
383
+ }
384
+ // ============================================================================
385
+ // Struct Definition
386
+ // ============================================================================
387
+ function emitStruct(type, polymorphicTypeNames, lines) {
388
+ if (type.description) {
389
+ emitDocComment(type.description, "", lines);
390
+ }
391
+ if (type.wire) {
392
+ lines.push("#[derive(Debug, Clone, Default, serde::Serialize)]");
393
+ lines.push('#[serde(rename_all = "camelCase")]');
394
+ }
395
+ else {
396
+ lines.push("#[derive(Debug, Clone, Default)]");
397
+ }
398
+ lines.push(`pub struct ${type.typeName.name} {`);
399
+ for (const field of type.fields) {
400
+ // Skip discriminator field — it's represented by the XxxKind enum field
401
+ if (type.polymorphicDispatch && field.name === type.polymorphicDispatch.discriminatorField)
402
+ continue;
403
+ if (field.description) {
404
+ emitDocComment(field.description, " ", lines);
405
+ }
406
+ lines.push(` pub ${rustFieldName(field.name)}: ${fieldType(field, polymorphicTypeNames)},`);
407
+ }
408
+ // Add kind field for polymorphic types
409
+ if (type.polymorphicDispatch) {
410
+ lines.push(` /// Variant-specific data, discriminated by \`${type.polymorphicDispatch.discriminatorField}\`.`);
411
+ lines.push(` pub ${rustFieldName(type.polymorphicDispatch.discriminatorField)}: ${type.typeName.name}Kind,`);
412
+ }
413
+ lines.push("}");
414
+ }
415
+ // ============================================================================
416
+ // Impl Block
417
+ // ============================================================================
418
+ function emitImpl(type, childTypes, baseFieldNames, visitor, polymorphicTypeNames, lines) {
419
+ const name = type.typeName.name;
420
+ lines.push("");
421
+ lines.push(`impl ${name} {`);
422
+ // new()
423
+ lines.push(` /// Create a new ${name} with default values.`);
424
+ lines.push(" pub fn new() -> Self {");
425
+ lines.push(" Self::default()");
426
+ lines.push(" }");
427
+ lines.push("");
428
+ // from_json()
429
+ emitFromJson(name, lines);
430
+ // from_yaml()
431
+ emitFromYaml(name, lines);
432
+ // load_from_value()
433
+ emitLoadFromValue(name, type, childTypes, baseFieldNames, polymorphicTypeNames, lines);
434
+ // kind_str() for polymorphic types
435
+ if (type.polymorphicDispatch) {
436
+ emitKindStr(type, childTypes, lines);
437
+ }
438
+ // to_value()
439
+ emitToValue(name, type, childTypes, baseFieldNames, polymorphicTypeNames, lines);
440
+ // to_json() / to_yaml()
441
+ emitToJson(name, lines);
442
+ emitToYaml(name, lines);
443
+ // to_wire() (only when wire mappings exist)
444
+ if (type.wire) {
445
+ emitToWireMethod(type, lines);
446
+ }
447
+ // Dict accessor helpers (only for dict-category fields, not scalar value types)
448
+ for (const field of type.fields) {
449
+ if (field.category.kind === "dict") {
450
+ emitDictAccessor(field, lines);
451
+ }
452
+ }
453
+ // Collection helpers — include both base type and child type helpers
454
+ // (in Rust, child types are enum variants, so their helpers live on the parent impl)
455
+ const emittedHelpers = new Set();
456
+ const allHelpers = [...type.collectionHelpers];
457
+ for (const child of childTypes) {
458
+ for (const h of child.collectionHelpers) {
459
+ if (!emittedHelpers.has(h.propertyName)) {
460
+ allHelpers.push(h);
461
+ }
462
+ }
463
+ }
464
+ for (const helper of allHelpers) {
465
+ if (emittedHelpers.has(helper.propertyName))
466
+ continue;
467
+ emittedHelpers.add(helper.propertyName);
468
+ emitCollectionLoadHelper(helper, polymorphicTypeNames, lines);
469
+ emitCollectionSaveHelper(helper, lines);
470
+ }
471
+ // Factories
472
+ for (const factory of type.factories) {
473
+ emitFactory(name, factory, visitor, lines);
474
+ }
475
+ lines.push("}");
476
+ // Method trait stubs (handled separately)
477
+ }
478
+ // ============================================================================
479
+ // from_json / from_yaml
480
+ // ============================================================================
481
+ function emitFromJson(name, lines) {
482
+ lines.push(` /// Load ${name} from a JSON string.`);
483
+ lines.push(` pub fn from_json(json: &str, ctx: &LoadContext) -> Result<Self, serde_json::Error> {`);
484
+ lines.push(" let value: serde_json::Value = serde_json::from_str(json)?;");
485
+ lines.push(" Ok(Self::load_from_value(&value, ctx))");
486
+ lines.push(" }");
487
+ lines.push("");
488
+ }
489
+ function emitFromYaml(name, lines) {
490
+ lines.push(` /// Load ${name} from a YAML string.`);
491
+ lines.push(` pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result<Self, serde_yaml::Error> {`);
492
+ lines.push(" let value: serde_json::Value = serde_yaml::from_str(yaml)?;");
493
+ lines.push(" Ok(Self::load_from_value(&value, ctx))");
494
+ lines.push(" }");
495
+ lines.push("");
496
+ }
497
+ // ============================================================================
498
+ // load_from_value
499
+ // ============================================================================
500
+ function emitLoadFromValue(name, type, childTypes, baseFieldNames, polymorphicTypeNames, lines) {
501
+ lines.push(` /// Load ${name} from a \`serde_json::Value\`.`);
502
+ lines.push(" ///");
503
+ lines.push(" /// Calls `ctx.process_input` before field extraction.");
504
+ lines.push(" pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self {");
505
+ lines.push(" let value = ctx.process_input(value.clone());");
506
+ // Coercions
507
+ for (const c of type.load.coercions) {
508
+ emitCoercionBranch(name, c, type, childTypes, baseFieldNames, polymorphicTypeNames, lines);
509
+ }
510
+ // Polymorphic dispatch
511
+ if (type.polymorphicDispatch) {
512
+ emitPolymorphicLoad(name, type, childTypes, baseFieldNames, polymorphicTypeNames, lines);
513
+ }
514
+ else {
515
+ // Simple struct construction
516
+ lines.push(" Self {");
517
+ for (const a of type.load.assignments) {
518
+ lines.push(` ${rustFieldName(a.fieldName)}: ${loadExpr(a, polymorphicTypeNames)},`);
519
+ }
520
+ lines.push(" }");
521
+ }
522
+ lines.push(" }");
523
+ lines.push("");
524
+ }
525
+ function emitCoercionBranch(typeName, c, type, childTypes, baseFieldNames, polymorphicTypeNames, lines) {
526
+ const check = rustCoercionCheck(c.scalarType);
527
+ if (!check)
528
+ return;
529
+ lines.push(` ${check.ifLet} {`);
530
+ // For string coercions, rebind `s` → `value` so downstream references work
531
+ if (c.scalarType === "string") {
532
+ lines.push(" let value = s.to_string();");
533
+ }
534
+ const dispatch = type.polymorphicDispatch;
535
+ const discField = dispatch?.discriminatorField;
536
+ const enumName = typeName + "Kind";
537
+ // Build field assignments, handling discriminator → enum variant construction
538
+ const fieldAssignments = c.assignments.map(a => {
539
+ const snake = rustFieldName(a.fieldName);
540
+ if (a.isInput) {
541
+ // Check if the target field is optional or an enum
542
+ const targetField = type.fields.find(f => f.name === a.fieldName);
543
+ const isOptional = targetField?.isOptional ?? false;
544
+ // If target field is a named enum, use from_str_opt instead of .into()
545
+ if (targetField?.enumName && targetField.allowedValues.length > 0) {
546
+ const defaultVal = String(targetField.defaultValue || targetField.allowedValues[0]);
547
+ const enumExpr = `${targetField.enumName}::from_str_opt(&value).unwrap_or(${targetField.enumName}::${toPascalCase(defaultVal)})`;
548
+ return isOptional ? `${snake}: Some(${enumExpr})` : `${snake}: ${enumExpr}`;
549
+ }
550
+ const expr = isOptional ? "Some(value.into())" : "value.into()";
551
+ return `${snake}: ${expr}`;
552
+ }
553
+ // Check if this field is the discriminator — must construct enum variant
554
+ if (dispatch && a.fieldName === discField) {
555
+ const discSnake = toSnakeCase(discField);
556
+ // Find if the literal matches a named variant
557
+ const matchingVariant = dispatch.variants.find(v => v.value === a.literalValue);
558
+ if (matchingVariant) {
559
+ const variantName = matchingVariant.typeName.name.replace(typeName, "") || matchingVariant.typeName.name;
560
+ // Check if variant has fields — unit variants don't get braces
561
+ const childType = childTypes.find(ct => ct.typeName.name === matchingVariant.typeName.name);
562
+ const variantFields = childType
563
+ ? childType.fields.filter(f => f.name !== dispatch.discriminatorField && !baseFieldNames.has(f.name))
564
+ : [];
565
+ if (variantFields.length === 0) {
566
+ return `${discSnake}: ${enumName}::${variantName}`;
567
+ }
568
+ return `${discSnake}: ${enumName}::${variantName} { ..Default::default() }`;
569
+ }
570
+ // Otherwise use the wildcard/custom variant
571
+ if (dispatch.defaultVariant) {
572
+ const dvName = dispatch.defaultVariant.isSelfReference
573
+ ? "Custom"
574
+ : (dispatch.defaultVariant.typeName.name.replace(typeName, "") || "Custom");
575
+ return `${discSnake}: ${enumName}::${dvName} { ${discSnake}_name: "${a.literalValue}".to_string() }`;
576
+ }
577
+ return `${discSnake}: ${enumName}::default()`;
578
+ }
579
+ return `${snake}: "${a.literalValue}".to_string()`;
580
+ });
581
+ lines.push(` return ${typeName} { ${fieldAssignments.join(", ")}, ..Default::default() };`);
582
+ lines.push(" }");
583
+ }
584
+ /**
585
+ * Get the Rust coercion check pattern for a scalar type.
586
+ */
587
+ function rustCoercionCheck(scalarType) {
588
+ switch (scalarType) {
589
+ case "string":
590
+ return { ifLet: "if let Some(s) = value.as_str()" };
591
+ case "boolean":
592
+ return { ifLet: "if let Some(value) = value.as_bool()" };
593
+ case "float64":
594
+ case "float":
595
+ case "number":
596
+ case "numeric":
597
+ return { ifLet: "if let Some(value) = value.as_f64()" };
598
+ case "int32":
599
+ case "int64":
600
+ case "integer":
601
+ return { ifLet: "if let Some(value) = value.as_i64()" };
602
+ default:
603
+ return null;
604
+ }
605
+ }
606
+ /**
607
+ * Get the Rust expression to convert a coercion value to the target field type.
608
+ */
609
+ function coercionIntoValue(scalarType) {
610
+ // `value` is already bound in the coercion branch
611
+ // For string coercions: value is String, .into() for String → String
612
+ // For numeric/bool: value is the extracted primitive
613
+ return "value.into()";
614
+ }
615
+ function emitPolymorphicLoad(name, type, childTypes, baseFieldNames, polymorphicTypeNames, lines) {
616
+ const dispatch = type.polymorphicDispatch;
617
+ const discSnake = rustFieldName(dispatch.discriminatorField);
618
+ const enumName = name + "Kind";
619
+ lines.push(` let ${discSnake}_str = value.get("${dispatch.discriminatorField}").and_then(|v| v.as_str()).unwrap_or("");`);
620
+ lines.push(` let ${discSnake} = match ${discSnake}_str {`);
621
+ // Named variants
622
+ for (const variant of dispatch.variants) {
623
+ const childType = childTypes.find(ct => ct.typeName.name === variant.typeName.name);
624
+ const variantName = variant.typeName.name.replace(name, "") || variant.typeName.name;
625
+ const variantFields = childType
626
+ ? childType.fields.filter(f => f.name !== dispatch.discriminatorField && !baseFieldNames.has(f.name))
627
+ : [];
628
+ if (variantFields.length === 0) {
629
+ lines.push(` "${variant.value}" => ${enumName}::${variantName},`);
630
+ }
631
+ else {
632
+ lines.push(` "${variant.value}" => ${enumName}::${variantName} {`);
633
+ for (const field of variantFields) {
634
+ const assignment = variantLoadExpr(field, name, polymorphicTypeNames);
635
+ lines.push(` ${rustFieldName(field.name)}: ${assignment},`);
636
+ }
637
+ lines.push(` },`);
638
+ }
639
+ }
640
+ // Default / wildcard
641
+ if (dispatch.defaultVariant) {
642
+ const isSelfRef = dispatch.defaultVariant.isSelfReference;
643
+ const variantName = isSelfRef
644
+ ? "Custom"
645
+ : (dispatch.defaultVariant.typeName.name.replace(name, "") || "Custom");
646
+ const defaultType = childTypes.find(ct => ct.typeName.name === dispatch.defaultVariant.typeName.name);
647
+ const variantFields = isSelfRef
648
+ ? []
649
+ : (defaultType
650
+ ? defaultType.fields.filter(f => f.name !== dispatch.discriminatorField && !baseFieldNames.has(f.name))
651
+ : []);
652
+ if (variantFields.length === 0) {
653
+ lines.push(` _ => ${enumName}::${variantName} {`);
654
+ lines.push(` ${discSnake}_name: ${discSnake}_str.to_string(),`);
655
+ lines.push(` },`);
656
+ }
657
+ else {
658
+ lines.push(` _ => ${enumName}::${variantName} {`);
659
+ for (const field of variantFields) {
660
+ const assignment = variantLoadExpr(field, name, polymorphicTypeNames);
661
+ lines.push(` ${rustFieldName(field.name)}: ${assignment},`);
662
+ }
663
+ lines.push(` ${discSnake}_name: ${discSnake}_str.to_string(),`);
664
+ lines.push(` },`);
665
+ }
666
+ }
667
+ else {
668
+ lines.push(` _ => ${enumName}::default(),`);
669
+ }
670
+ lines.push(" };");
671
+ // Construct the struct with base fields + kind
672
+ lines.push(" Self {");
673
+ for (const a of type.load.assignments) {
674
+ // Skip discriminator — it's stored in the enum field
675
+ if (a.fieldName === dispatch.discriminatorField)
676
+ continue;
677
+ lines.push(` ${rustFieldName(a.fieldName)}: ${loadExpr(a, polymorphicTypeNames)},`);
678
+ }
679
+ lines.push(` ${discSnake}: ${discSnake},`);
680
+ lines.push(" }");
681
+ }
682
+ // ============================================================================
683
+ // kind_str
684
+ // ============================================================================
685
+ function emitKindStr(type, childTypes, lines) {
686
+ const dispatch = type.polymorphicDispatch;
687
+ const baseFieldNames = new Set(type.fields.map(f => f.name));
688
+ const enumName = type.typeName.name + "Kind";
689
+ const discSnake = rustFieldName(dispatch.discriminatorField);
690
+ lines.push(` /// Returns the \`${dispatch.discriminatorField}\` discriminator string for this instance.`);
691
+ lines.push(` pub fn ${discSnake}_str(&self) -> &str {`);
692
+ lines.push(` match &self.${discSnake} {`);
693
+ for (const variant of dispatch.variants) {
694
+ const variantName = variant.typeName.name.replace(type.typeName.name, "") || variant.typeName.name;
695
+ const childType = childTypes.find(ct => ct.typeName.name === variant.typeName.name);
696
+ const variantFields = childType
697
+ ? childType.fields.filter(f => f.name !== dispatch.discriminatorField && !baseFieldNames.has(f.name))
698
+ : [];
699
+ if (variantFields.length === 0) {
700
+ lines.push(` ${enumName}::${variantName} => "${variant.value}",`);
701
+ }
702
+ else {
703
+ lines.push(` ${enumName}::${variantName} { .. } => "${variant.value}",`);
704
+ }
705
+ }
706
+ if (dispatch.defaultVariant) {
707
+ const isSelfRef = dispatch.defaultVariant.isSelfReference;
708
+ const variantName = isSelfRef
709
+ ? "Custom"
710
+ : (dispatch.defaultVariant.typeName.name.replace(type.typeName.name, "") || "Custom");
711
+ lines.push(` ${enumName}::${variantName} { ${discSnake}_name, .. } => ${discSnake}_name.as_str(),`);
712
+ }
713
+ lines.push(" }");
714
+ lines.push(" }");
715
+ lines.push("");
716
+ }
717
+ // ============================================================================
718
+ // to_value
719
+ // ============================================================================
720
+ function emitToValue(name, type, childTypes, baseFieldNames, polymorphicTypeNames, lines) {
721
+ lines.push(` /// Serialize ${name} to a \`serde_json::Value\`.`);
722
+ lines.push(" ///");
723
+ lines.push(" /// Calls `ctx.process_dict` after serialization.");
724
+ lines.push(" pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value {");
725
+ lines.push(" let mut result = serde_json::Map::new();");
726
+ // Write discriminator first
727
+ if (type.polymorphicDispatch) {
728
+ const discSnake = rustFieldName(type.polymorphicDispatch.discriminatorField);
729
+ lines.push(` // Write the discriminator`);
730
+ lines.push(` result.insert("${type.polymorphicDispatch.discriminatorField}".to_string(), serde_json::Value::String(self.${discSnake}_str().to_string()));`);
731
+ }
732
+ // Write base fields
733
+ if (type.save.assignments.length > 0) {
734
+ lines.push(` // Write base fields`);
735
+ }
736
+ for (const a of type.save.assignments) {
737
+ // Skip discriminator — it's written explicitly above from kind_str()
738
+ if (type.polymorphicDispatch && a.fieldName === type.polymorphicDispatch.discriminatorField)
739
+ continue;
740
+ emitSaveField(a, "self.", polymorphicTypeNames, lines, " ");
741
+ }
742
+ // Write variant-specific fields
743
+ if (type.polymorphicDispatch) {
744
+ emitVariantSave(type, childTypes, baseFieldNames, polymorphicTypeNames, lines);
745
+ }
746
+ lines.push(" ctx.process_dict(serde_json::Value::Object(result))");
747
+ lines.push(" }");
748
+ lines.push("");
749
+ }
750
+ function emitVariantSave(type, childTypes, baseFieldNames, polymorphicTypeNames, lines) {
751
+ const dispatch = type.polymorphicDispatch;
752
+ const enumName = type.typeName.name + "Kind";
753
+ const discSnake = rustFieldName(dispatch.discriminatorField);
754
+ lines.push(" // Write variant-specific fields");
755
+ lines.push(` match &self.${discSnake} {`);
756
+ for (const variant of dispatch.variants) {
757
+ const childType = childTypes.find(ct => ct.typeName.name === variant.typeName.name);
758
+ const variantName = variant.typeName.name.replace(type.typeName.name, "") || variant.typeName.name;
759
+ const variantFields = childType
760
+ ? childType.fields.filter(f => f.name !== dispatch.discriminatorField && !baseFieldNames.has(f.name))
761
+ : [];
762
+ if (variantFields.length === 0) {
763
+ lines.push(` ${enumName}::${variantName} => {`);
764
+ lines.push(" }");
765
+ }
766
+ else {
767
+ const destructure = variantFields.map(f => rustFieldName(f.name)).join(", ");
768
+ lines.push(` ${enumName}::${variantName} { ${destructure}, .. } => {`);
769
+ for (const field of variantFields) {
770
+ emitVariantSaveField(field, polymorphicTypeNames, type.typeName.name, lines);
771
+ }
772
+ lines.push(" }");
773
+ }
774
+ }
775
+ // Wildcard variant
776
+ if (dispatch.defaultVariant) {
777
+ const isSelfRef = dispatch.defaultVariant.isSelfReference;
778
+ const variantName = isSelfRef
779
+ ? "Custom"
780
+ : (dispatch.defaultVariant.typeName.name.replace(type.typeName.name, "") || "Custom");
781
+ const defaultType = childTypes.find(ct => ct.typeName.name === dispatch.defaultVariant.typeName.name);
782
+ const variantFields = isSelfRef
783
+ ? []
784
+ : (defaultType
785
+ ? defaultType.fields.filter(f => f.name !== dispatch.discriminatorField && !baseFieldNames.has(f.name))
786
+ : []);
787
+ if (variantFields.length === 0) {
788
+ lines.push(` ${enumName}::${variantName} { ${discSnake}_name: _, .. } => {`);
789
+ lines.push(" }");
790
+ }
791
+ else {
792
+ const destructure = variantFields.map(f => rustFieldName(f.name)).join(", ");
793
+ lines.push(` ${enumName}::${variantName} { ${destructure}, ${discSnake}_name: _, .. } => {`);
794
+ for (const field of variantFields) {
795
+ emitVariantSaveField(field, polymorphicTypeNames, type.typeName.name, lines);
796
+ }
797
+ lines.push(" }");
798
+ }
799
+ }
800
+ lines.push(" }");
801
+ }
802
+ // ============================================================================
803
+ // to_json / to_yaml
804
+ // ============================================================================
805
+ function emitToJson(name, lines) {
806
+ lines.push(` /// Serialize ${name} to a JSON string.`);
807
+ lines.push(` pub fn to_json(&self, ctx: &SaveContext) -> Result<String, serde_json::Error> {`);
808
+ lines.push(" serde_json::to_string_pretty(&self.to_value(ctx))");
809
+ lines.push(" }");
810
+ lines.push("");
811
+ }
812
+ function emitToYaml(name, lines) {
813
+ lines.push(` /// Serialize ${name} to a YAML string.`);
814
+ lines.push(` pub fn to_yaml(&self, ctx: &SaveContext) -> Result<String, serde_yaml::Error> {`);
815
+ lines.push(" serde_yaml::to_string(&self.to_value(ctx))");
816
+ lines.push(" }");
817
+ }
818
+ // ============================================================================
819
+ // to_wire — provider-specific wire format conversion
820
+ // ============================================================================
821
+ /**
822
+ * Emit a `to_wire(&self, provider: &str) -> serde_json::Value` method that
823
+ * serializes the struct via `serde_json::to_value`, then remaps field names
824
+ * according to provider-specific wire mappings.
825
+ *
826
+ * Only emitted when `type.wire` is non-null.
827
+ */
828
+ function emitToWireMethod(type, lines) {
829
+ const wire = type.wire;
830
+ const name = type.typeName.name;
831
+ lines.push("");
832
+ lines.push(` /// Convert to provider-specific wire format.`);
833
+ lines.push(` pub fn to_wire(&self, provider: &str) -> serde_json::Value {`);
834
+ lines.push(` let data = serde_json::to_value(self).unwrap_or_default();`);
835
+ lines.push(` let mut result = serde_json::Map::new();`);
836
+ // Build the wire_map HashMap literal
837
+ lines.push(` let wire_map: std::collections::HashMap<&str, std::collections::HashMap<&str, &str>> = std::collections::HashMap::from([`);
838
+ for (const mapping of wire.mappings) {
839
+ const entries = Object.entries(mapping.wireNames);
840
+ const inner = entries.map(([provider, wireName]) => `("${provider}", "${wireName}")`).join(", ");
841
+ lines.push(` ("${mapping.fieldName}", std::collections::HashMap::from([${inner}])),`);
842
+ }
843
+ lines.push(` ]);`);
844
+ // Iterate over serialized keys and remap (strict: only emit mapped fields)
845
+ lines.push(` if let serde_json::Value::Object(map) = data {`);
846
+ lines.push(` for (key, value) in map {`);
847
+ lines.push(` if let Some(mapping) = wire_map.get(key.as_str()) {`);
848
+ lines.push(` if let Some(wire_name) = mapping.get(provider) {`);
849
+ lines.push(` result.insert(wire_name.to_string(), value);`);
850
+ lines.push(` }`);
851
+ lines.push(` }`);
852
+ lines.push(` }`);
853
+ lines.push(` }`);
854
+ lines.push(` serde_json::Value::Object(result)`);
855
+ lines.push(` }`);
856
+ }
857
+ // ============================================================================
858
+ // Dict accessor helpers
859
+ // ============================================================================
860
+ function emitDictAccessor(field, lines) {
861
+ const snake = rustFieldName(field.name);
862
+ lines.push(` /// Returns typed reference to the map if the field is an object.`);
863
+ lines.push(` /// Returns \`None\` if the field is null or not an object.`);
864
+ lines.push(` pub fn as_${snake}_dict(&self) -> Option<&serde_json::Map<String, serde_json::Value>> {`);
865
+ lines.push(` self.${snake}.as_object()`);
866
+ lines.push(" }");
867
+ lines.push("");
868
+ }
869
+ // ============================================================================
870
+ // Collection helpers
871
+ // ============================================================================
872
+ function emitCollectionLoadHelper(helper, polymorphicTypeNames, lines) {
873
+ const fnName = `load_${toSnakeCase(helper.propertyName)}`;
874
+ const elemType = helper.elementTypeName.name;
875
+ lines.push("");
876
+ lines.push(` /// Load a collection of ${elemType} from a JSON value.`);
877
+ if (helper.hasNameProperty) {
878
+ lines.push(` /// Handles both array format \`[{...}]\` and dict format \`{"name": {...}}\`.`);
879
+ }
880
+ else {
881
+ lines.push(` /// Handles both array format \`[{...}]\`.`);
882
+ }
883
+ lines.push(` fn ${fnName}(data: &serde_json::Value, ctx: &LoadContext) -> Vec<${elemType}> {`);
884
+ lines.push(" match data {");
885
+ lines.push(" serde_json::Value::Array(arr) => {");
886
+ lines.push(` arr.iter().map(|v| ${elemType}::load_from_value(v, ctx)).collect()`);
887
+ lines.push(" }");
888
+ lines.push("");
889
+ if (helper.hasNameProperty) {
890
+ // Dict format with name keys
891
+ const shorthandField = helper.innerFields.length > 0 ? helper.innerFields[0] : "value";
892
+ lines.push(" serde_json::Value::Object(obj) => {");
893
+ lines.push(" obj.iter()");
894
+ lines.push(" .filter_map(|(name, value)| {");
895
+ lines.push(" if value.is_array() {");
896
+ lines.push(" return None;");
897
+ lines.push(" }");
898
+ lines.push(" let mut v = if value.is_object() {");
899
+ lines.push(" value.clone()");
900
+ lines.push(" } else {");
901
+ lines.push(` serde_json::json!({ "${shorthandField}": value })`);
902
+ lines.push(" };");
903
+ lines.push(' if let serde_json::Value::Object(ref mut m) = v {');
904
+ lines.push(' m.entry("name".to_string()).or_insert_with(|| serde_json::Value::String(name.clone()));');
905
+ lines.push(" }");
906
+ lines.push(` Some(${elemType}::load_from_value(&v, ctx))`);
907
+ lines.push(" })");
908
+ lines.push(" .collect()");
909
+ lines.push(" }");
910
+ }
911
+ lines.push(" _ => Vec::new(),");
912
+ lines.push("");
913
+ lines.push(" }");
914
+ lines.push(" }");
915
+ lines.push("");
916
+ // Save helper
917
+ }
918
+ function emitCollectionSaveHelper(helper, lines) {
919
+ const fnName = `save_${toSnakeCase(helper.propertyName)}`;
920
+ const elemType = helper.elementTypeName.name;
921
+ lines.push(` /// Save a collection of ${elemType} to a JSON value.`);
922
+ lines.push(` fn ${fnName}(items: &[${elemType}], ctx: &SaveContext) -> serde_json::Value {`);
923
+ if (helper.hasNameProperty) {
924
+ lines.push("");
925
+ lines.push(' if ctx.collection_format == "array" {');
926
+ lines.push(" return serde_json::Value::Array(items.iter().map(|item| item.to_value(ctx)).collect::<Vec<_>>());");
927
+ lines.push(" }");
928
+ lines.push(" // Object format: use name as key");
929
+ lines.push(" let mut result = serde_json::Map::new();");
930
+ lines.push(" for item in items {");
931
+ lines.push(" let mut item_data = match item.to_value(ctx) {");
932
+ lines.push(' serde_json::Value::Object(m) => m,');
933
+ lines.push(' other => { let mut m = serde_json::Map::new(); m.insert("value".to_string(), other); m },');
934
+ lines.push(" };");
935
+ lines.push(' if let Some(serde_json::Value::String(name)) = item_data.remove("name") {');
936
+ lines.push(" result.insert(name, serde_json::Value::Object(item_data));");
937
+ lines.push(" }");
938
+ lines.push(" }");
939
+ lines.push(" serde_json::Value::Object(result)");
940
+ }
941
+ else {
942
+ lines.push("");
943
+ lines.push(" serde_json::Value::Array(items.iter().map(|item| item.to_value(ctx)).collect::<Vec<_>>())");
944
+ }
945
+ lines.push("");
946
+ lines.push(" }");
947
+ }
948
+ // ============================================================================
949
+ // Factory methods
950
+ // ============================================================================
951
+ function emitFactory(parentName, factory, visitor, lines) {
952
+ const params = Object.entries(factory.params)
953
+ .map(([pName, pType]) => `${toSnakeCase(pName)}: ${factoryParamType(pType)}`)
954
+ .join(", ");
955
+ lines.push(` /// Create a ${parentName} with preset field values.`);
956
+ lines.push(` pub fn ${factory.name}(${params}) -> Self {`);
957
+ lines.push(` ${visitor.visitExpr(factory.body)}`);
958
+ lines.push(" }");
959
+ }
960
+ function factoryParamType(typeStr) {
961
+ switch (typeStr) {
962
+ case "string":
963
+ return "impl Into<String>";
964
+ case "unknown":
965
+ case "any":
966
+ return "impl Into<serde_json::Value>";
967
+ case "boolean":
968
+ return "bool";
969
+ case "int32":
970
+ return "i32";
971
+ case "int64":
972
+ case "integer":
973
+ return "i64";
974
+ case "float32":
975
+ return "f32";
976
+ case "float64":
977
+ case "float":
978
+ return "f64";
979
+ default:
980
+ return `impl Into<${typeStr}>`;
981
+ }
982
+ }
983
+ // ============================================================================
984
+ // Method stub trait
985
+ // ============================================================================
986
+ function emitMethodTrait(type, lines) {
987
+ lines.push(`/// Helpers for [\`${type.typeName.name}\`]. Implement in a separate file.`);
988
+ lines.push(`pub trait ${type.typeName.name}Helpers {`);
989
+ for (const method of type.methods) {
990
+ if (method.description) {
991
+ emitDocComment(method.description, " ", lines);
992
+ }
993
+ lines.push(` fn ${toSnakeCase(method.name)}(&self) -> ${methodReturnType(method)};`);
994
+ }
995
+ lines.push("}");
996
+ }
997
+ function methodReturnType(method) {
998
+ if (method.returns === "string")
999
+ return "String";
1000
+ return RUST_TYPE_MAP[method.returns] || method.returns;
1001
+ }
1002
+ // ============================================================================
1003
+ // Protocol trait emission
1004
+ // ============================================================================
1005
+ /** Map a protocol type string to a Rust type. */
1006
+ function protocolRustType(typeStr) {
1007
+ // Handle nullable types
1008
+ if (typeStr.endsWith("?")) {
1009
+ const inner = typeStr.slice(0, -1);
1010
+ return `Option<${protocolRustType(inner)}>`;
1011
+ }
1012
+ // Handle array types
1013
+ if (typeStr.endsWith("[]")) {
1014
+ const inner = typeStr.slice(0, -2);
1015
+ return `Vec<${protocolRustType(inner)}>`;
1016
+ }
1017
+ if (typeStr === "Record<unknown>" || typeStr === "dictionary")
1018
+ return "serde_json::Value";
1019
+ if (typeStr === "unknown" || typeStr === "any")
1020
+ return "serde_json::Value";
1021
+ if (typeStr === "string")
1022
+ return "String";
1023
+ return RUST_TYPE_MAP[typeStr] || typeStr;
1024
+ }
1025
+ /**
1026
+ * Emit a Rust trait for a protocol type.
1027
+ * Uses #[async_trait] for async method support.
1028
+ */
1029
+ function emitProtocolTrait(type, lines) {
1030
+ const name = type.typeName.name;
1031
+ if (type.description) {
1032
+ emitDocComment(type.description, "", lines);
1033
+ }
1034
+ lines.push("#[async_trait::async_trait]");
1035
+ lines.push(`pub trait ${name}: Send + Sync {`);
1036
+ for (const method of type.methods) {
1037
+ if (method.description) {
1038
+ emitDocComment(method.description, " ", lines);
1039
+ }
1040
+ const params = Object.entries(method.params)
1041
+ .map(([pName, pType]) => `${toSnakeCase(pName)}: &${protocolRustType(pType)}`)
1042
+ .join(", ");
1043
+ const ret = protocolRustType(method.returns);
1044
+ if (method.sync) {
1045
+ // Synchronous method
1046
+ if (method.optional) {
1047
+ // Return type already includes nullability from ? suffix — don't double-wrap
1048
+ lines.push(` fn ${toSnakeCase(method.name)}(&self, ${params}) -> ${ret} {`);
1049
+ lines.push(" None");
1050
+ lines.push(" }");
1051
+ }
1052
+ else {
1053
+ lines.push(` fn ${toSnakeCase(method.name)}(&self, ${params}) -> ${ret};`);
1054
+ }
1055
+ }
1056
+ else {
1057
+ // Async method
1058
+ if (method.optional) {
1059
+ // Default implementation returns an error — providers override with real streaming
1060
+ lines.push(` async fn ${toSnakeCase(method.name)}(&self, ${params}) -> Result<${ret}, Box<dyn std::error::Error + Send + Sync>> {`);
1061
+ lines.push(` Err("not supported".into())`);
1062
+ lines.push(" }");
1063
+ }
1064
+ else {
1065
+ lines.push(` async fn ${toSnakeCase(method.name)}(&self, ${params}) -> Result<${ret}, Box<dyn std::error::Error + Send + Sync>>;`);
1066
+ }
1067
+ }
1068
+ }
1069
+ lines.push("}");
1070
+ }
1071
+ // ============================================================================
1072
+ // Field type rendering
1073
+ // ============================================================================
1074
+ function fieldType(field, polymorphicTypeNames) {
1075
+ // Named enum field — use the enum type
1076
+ if (field.enumName && field.allowedValues.length > 0) {
1077
+ return field.isOptional ? `Option<${field.enumName}>` : field.enumName;
1078
+ }
1079
+ const cat = field.category;
1080
+ switch (cat.kind) {
1081
+ case "scalar": {
1082
+ const rustType = RUST_TYPE_MAP[cat.scalarType] || cat.scalarType;
1083
+ // dict category handles always-Value separately; for scalar value types
1084
+ // (any, object, unknown), optional fields are Option<Value>, required are Value.
1085
+ // dictionary scalar is always Value (never Option) since it's a map type.
1086
+ if (cat.scalarType === "dictionary") {
1087
+ return "serde_json::Value";
1088
+ }
1089
+ return field.isOptional ? `Option<${rustType}>` : rustType;
1090
+ }
1091
+ case "complex": {
1092
+ // Polymorphic references and "unknown" become serde_json::Value
1093
+ // Always non-optional — Value::Null serves as the "absent" sentinel
1094
+ if (polymorphicTypeNames.has(cat.typeName) || cat.typeName === "unknown") {
1095
+ return "serde_json::Value";
1096
+ }
1097
+ return field.isOptional ? `Option<${cat.typeName}>` : cat.typeName;
1098
+ }
1099
+ case "collection_scalar": {
1100
+ const elemType = RUST_TYPE_MAP[cat.scalarType] || cat.scalarType;
1101
+ if (isValueType(cat.scalarType)) {
1102
+ return field.isOptional ? "Option<Vec<serde_json::Value>>" : "Vec<serde_json::Value>";
1103
+ }
1104
+ return field.isOptional ? `Option<Vec<${elemType}>>` : `Vec<${elemType}>`;
1105
+ }
1106
+ case "collection_complex": {
1107
+ if (cat.typeName === "unknown") {
1108
+ return "Vec<serde_json::Value>";
1109
+ }
1110
+ return `Vec<${cat.typeName}>`;
1111
+ }
1112
+ case "dict": {
1113
+ return "serde_json::Value";
1114
+ }
1115
+ }
1116
+ }
1117
+ function isValueType(scalarType) {
1118
+ return scalarType === "any" || scalarType === "object" || scalarType === "dictionary" || scalarType === "unknown";
1119
+ }
1120
+ function fieldDefault(field, polymorphicTypeNames) {
1121
+ // Named enum — default to field default value or first variant
1122
+ if (field.enumName && field.allowedValues.length > 0) {
1123
+ if (field.isOptional)
1124
+ return "None";
1125
+ const dv = typeof field.defaultValue === "string" ? field.defaultValue : null;
1126
+ const defaultVal = dv && field.allowedValues.includes(dv) ? dv : field.allowedValues[0];
1127
+ return `${field.enumName}::${toPascalCase(defaultVal)}`;
1128
+ }
1129
+ const cat = field.category;
1130
+ switch (cat.kind) {
1131
+ case "scalar": {
1132
+ if (isValueType(cat.scalarType)) {
1133
+ // dictionary is always Value (never Option); other value types may be optional
1134
+ if (cat.scalarType !== "dictionary" && field.isOptional)
1135
+ return "None";
1136
+ return "serde_json::Value::Null";
1137
+ }
1138
+ if (field.isOptional)
1139
+ return "None";
1140
+ const rustType = RUST_TYPE_MAP[cat.scalarType] || cat.scalarType;
1141
+ if (rustType === "String")
1142
+ return 'String::from("")';
1143
+ if (rustType === "bool")
1144
+ return "false";
1145
+ if (rustType === "i32" || rustType === "i64")
1146
+ return "0";
1147
+ if (rustType === "f32" || rustType === "f64")
1148
+ return "0.0";
1149
+ return "Default::default()";
1150
+ }
1151
+ case "complex": {
1152
+ // Polymorphic refs are always serde_json::Value, Null is the "absent" sentinel
1153
+ if (polymorphicTypeNames.has(cat.typeName))
1154
+ return "serde_json::Value::Null";
1155
+ return field.isOptional ? "None" : "Default::default()";
1156
+ }
1157
+ case "collection_scalar":
1158
+ case "collection_complex":
1159
+ return field.isOptional ? "None" : "Vec::new()";
1160
+ case "dict":
1161
+ return "serde_json::Value::Null";
1162
+ }
1163
+ }
1164
+ // ============================================================================
1165
+ // Load expression rendering (per-field)
1166
+ // ============================================================================
1167
+ function loadExpr(a, polymorphicTypeNames) {
1168
+ const key = a.sourceName;
1169
+ const cat = a.category;
1170
+ // Named enum — parse from string via from_str_opt
1171
+ if (a.enumName && a.allowedValues.length > 0) {
1172
+ const dv = typeof a.defaultValue === "string" ? a.defaultValue : null;
1173
+ const defaultVal = dv && a.allowedValues.includes(dv) ? dv : a.allowedValues[0];
1174
+ if (a.isOptional) {
1175
+ return `value.get("${key}").and_then(|v| v.as_str()).and_then(|s| ${a.enumName}::from_str_opt(s))`;
1176
+ }
1177
+ return `value.get("${key}").and_then(|v| v.as_str()).and_then(|s| ${a.enumName}::from_str_opt(s)).unwrap_or(${a.enumName}::${toPascalCase(defaultVal)})`;
1178
+ }
1179
+ switch (cat.kind) {
1180
+ case "scalar":
1181
+ return scalarLoadExpr(key, cat.scalarType, a.isOptional);
1182
+ case "complex": {
1183
+ if (polymorphicTypeNames.has(cat.typeName)) {
1184
+ // Polymorphic ref → always serde_json::Value, Null is the "absent" sentinel
1185
+ return `value.get("${key}").cloned().unwrap_or(serde_json::Value::Null)`;
1186
+ }
1187
+ if (a.isOptional) {
1188
+ return `value.get("${key}").filter(|v| v.is_object() || v.is_array() || v.is_string()).map(|v| ${cat.typeName}::load_from_value(v, ctx))`;
1189
+ }
1190
+ return `value.get("${key}").filter(|v| v.is_object() || v.is_array() || v.is_string()).map(|v| ${cat.typeName}::load_from_value(v, ctx)).unwrap_or_default()`;
1191
+ }
1192
+ case "collection_scalar":
1193
+ return collectionScalarLoadExpr(key, cat.scalarType, a.isOptional);
1194
+ case "collection_complex":
1195
+ return `value.get("${key}").map(|v| Self::load_${toSnakeCase(a.fieldName)}(v, ctx)).unwrap_or_default()`;
1196
+ case "dict":
1197
+ return `value.get("${key}").cloned().unwrap_or(serde_json::Value::Null)`;
1198
+ }
1199
+ }
1200
+ function scalarLoadExpr(key, scalarType, isOptional) {
1201
+ if (isValueType(scalarType)) {
1202
+ // dictionary is never optional; any/object/unknown may be optional
1203
+ if (scalarType === "dictionary") {
1204
+ return `value.get("${key}").cloned().unwrap_or(serde_json::Value::Null)`;
1205
+ }
1206
+ return isOptional
1207
+ ? `value.get("${key}").cloned()`
1208
+ : `value.get("${key}").cloned().unwrap_or(serde_json::Value::Null)`;
1209
+ }
1210
+ switch (scalarType) {
1211
+ case "string":
1212
+ return isOptional
1213
+ ? `value.get("${key}").and_then(|v| v.as_str()).map(|s| s.to_string())`
1214
+ : `value.get("${key}").and_then(|v| v.as_str()).unwrap_or_default().to_string()`;
1215
+ case "boolean":
1216
+ return isOptional
1217
+ ? `value.get("${key}").and_then(|v| v.as_bool())`
1218
+ : `value.get("${key}").and_then(|v| v.as_bool()).unwrap_or(false)`;
1219
+ case "int32":
1220
+ return isOptional
1221
+ ? `value.get("${key}").and_then(|v| v.as_i64()).map(|v| v as i32)`
1222
+ : `value.get("${key}").and_then(|v| v.as_i64()).unwrap_or(0) as i32`;
1223
+ case "int64":
1224
+ case "integer":
1225
+ return isOptional
1226
+ ? `value.get("${key}").and_then(|v| v.as_i64())`
1227
+ : `value.get("${key}").and_then(|v| v.as_i64()).unwrap_or(0)`;
1228
+ case "float64":
1229
+ case "float":
1230
+ case "number":
1231
+ case "numeric":
1232
+ return isOptional
1233
+ ? `value.get("${key}").and_then(|v| v.as_f64())`
1234
+ : `value.get("${key}").and_then(|v| v.as_f64()).unwrap_or(0.0)`;
1235
+ case "float32":
1236
+ return isOptional
1237
+ ? `value.get("${key}").and_then(|v| v.as_f64()).map(|v| v as f32)`
1238
+ : `value.get("${key}").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32`;
1239
+ default:
1240
+ return `value.get("${key}").cloned().unwrap_or(serde_json::Value::Null)`;
1241
+ }
1242
+ }
1243
+ function collectionScalarLoadExpr(key, scalarType, isOptional) {
1244
+ if (isValueType(scalarType)) {
1245
+ return isOptional
1246
+ ? `value.get("${key}").and_then(|v| v.as_array()).map(|arr| arr.to_vec())`
1247
+ : `value.get("${key}").and_then(|v| v.as_array()).map(|arr| arr.to_vec()).unwrap_or_default()`;
1248
+ }
1249
+ switch (scalarType) {
1250
+ case "string":
1251
+ return isOptional
1252
+ ? `value.get("${key}").and_then(|v| v.as_array()).map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())`
1253
+ : `value.get("${key}").and_then(|v| v.as_array()).map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect()).unwrap_or_default()`;
1254
+ default:
1255
+ return isOptional
1256
+ ? `value.get("${key}").and_then(|v| v.as_array()).map(|arr| arr.to_vec())`
1257
+ : `value.get("${key}").and_then(|v| v.as_array()).map(|arr| arr.to_vec()).unwrap_or_default()`;
1258
+ }
1259
+ }
1260
+ /**
1261
+ * Load expression for a variant field (used inside match arms).
1262
+ */
1263
+ function variantLoadExpr(field, parentTypeName, polymorphicTypeNames) {
1264
+ // Delegate to the same patterns as base field loading, but with "value" as the source
1265
+ const key = field.name;
1266
+ const cat = field.category;
1267
+ // Named enum — parse from string via from_str_opt
1268
+ if (field.enumName && field.allowedValues.length > 0) {
1269
+ const defaultVal = String(field.defaultValue || field.allowedValues[0]);
1270
+ if (field.isOptional) {
1271
+ return `value.get("${key}").and_then(|v| v.as_str()).and_then(|s| ${field.enumName}::from_str_opt(s))`;
1272
+ }
1273
+ return `value.get("${key}").and_then(|v| v.as_str()).and_then(|s| ${field.enumName}::from_str_opt(s)).unwrap_or(${field.enumName}::${toPascalCase(defaultVal)})`;
1274
+ }
1275
+ switch (cat.kind) {
1276
+ case "scalar":
1277
+ return scalarLoadExpr(key, cat.scalarType, field.isOptional);
1278
+ case "complex": {
1279
+ if (polymorphicTypeNames.has(cat.typeName)) {
1280
+ return field.isOptional
1281
+ ? `value.get("${key}").cloned()`
1282
+ : `value.get("${key}").cloned().unwrap_or(serde_json::Value::Null)`;
1283
+ }
1284
+ if (field.isOptional) {
1285
+ return `value.get("${key}").filter(|v| v.is_object() || v.is_array() || v.is_string()).map(|v| ${cat.typeName}::load_from_value(v, ctx))`;
1286
+ }
1287
+ return `value.get("${key}").filter(|v| v.is_object() || v.is_array() || v.is_string()).map(|v| ${cat.typeName}::load_from_value(v, ctx)).unwrap_or_default()`;
1288
+ }
1289
+ case "collection_scalar":
1290
+ return collectionScalarLoadExpr(key, cat.scalarType, field.isOptional);
1291
+ case "collection_complex":
1292
+ // Collection in a variant — use the parent type's helper
1293
+ return `value.get("${key}").map(|v| Self::load_${toSnakeCase(field.name)}(v, ctx)).unwrap_or_default()`;
1294
+ case "dict":
1295
+ return `value.get("${key}").cloned().unwrap_or(serde_json::Value::Null)`;
1296
+ }
1297
+ }
1298
+ // ============================================================================
1299
+ // Save expression rendering (per-field)
1300
+ // ============================================================================
1301
+ function emitSaveField(a, prefix, polymorphicTypeNames, lines, indent) {
1302
+ const key = a.targetName;
1303
+ const fieldRef = `${prefix}${rustFieldName(a.fieldName)}`;
1304
+ const cat = a.category;
1305
+ // Named enum — serialize via .to_string()
1306
+ if (a.enumName) {
1307
+ if (a.isOptional) {
1308
+ lines.push(`${indent}if let Some(ref val) = ${fieldRef} {`);
1309
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::String(val.to_string()));`);
1310
+ lines.push(`${indent}}`);
1311
+ }
1312
+ else {
1313
+ lines.push(`${indent}result.insert("${key}".to_string(), serde_json::Value::String(${fieldRef}.to_string()));`);
1314
+ }
1315
+ return;
1316
+ }
1317
+ switch (cat.kind) {
1318
+ case "scalar":
1319
+ emitScalarSave(key, fieldRef, cat.scalarType, a.isOptional, lines, indent);
1320
+ return;
1321
+ case "complex": {
1322
+ if (polymorphicTypeNames.has(cat.typeName)) {
1323
+ // Polymorphic ref → always serde_json::Value, check .is_null()
1324
+ lines.push(`${indent}if !${fieldRef}.is_null() {`);
1325
+ lines.push(`${indent} result.insert("${key}".to_string(), ${fieldRef}.clone());`);
1326
+ lines.push(`${indent}}`);
1327
+ return;
1328
+ }
1329
+ if (a.isOptional) {
1330
+ lines.push(`${indent}if let Some(ref val) = ${fieldRef} {`);
1331
+ lines.push(`${indent} let nested = val.to_value(ctx);`);
1332
+ lines.push(`${indent} if !nested.is_null() {`);
1333
+ lines.push(`${indent} result.insert("${key}".to_string(), nested);`);
1334
+ lines.push(`${indent} }`);
1335
+ lines.push(`${indent}}`);
1336
+ }
1337
+ else {
1338
+ lines.push(`${indent}{`);
1339
+ lines.push(`${indent} let nested = ${fieldRef}.to_value(ctx);`);
1340
+ lines.push(`${indent} if !nested.is_null() {`);
1341
+ lines.push(`${indent} result.insert("${key}".to_string(), nested);`);
1342
+ lines.push(`${indent} }`);
1343
+ lines.push(`${indent}}`);
1344
+ }
1345
+ return;
1346
+ }
1347
+ case "collection_scalar": {
1348
+ if (a.isOptional) {
1349
+ lines.push(`${indent}if let Some(ref items) = ${fieldRef} {`);
1350
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::to_value(items).unwrap_or(serde_json::Value::Null));`);
1351
+ lines.push(`${indent}}`);
1352
+ }
1353
+ else {
1354
+ lines.push(`${indent}if !${fieldRef}.is_empty() {`);
1355
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::to_value(&${fieldRef}).unwrap_or(serde_json::Value::Null));`);
1356
+ lines.push(`${indent}}`);
1357
+ }
1358
+ return;
1359
+ }
1360
+ case "collection_complex": {
1361
+ lines.push(`${indent}if !${fieldRef}.is_empty() {`);
1362
+ lines.push(`${indent} result.insert("${key}".to_string(), Self::save_${toSnakeCase(a.fieldName)}(&${fieldRef}, ctx));`);
1363
+ lines.push(`${indent}}`);
1364
+ return;
1365
+ }
1366
+ case "dict": {
1367
+ lines.push(`${indent}if !${fieldRef}.is_null() {`);
1368
+ lines.push(`${indent} result.insert("${key}".to_string(), ${fieldRef}.clone());`);
1369
+ lines.push(`${indent}}`);
1370
+ return;
1371
+ }
1372
+ }
1373
+ }
1374
+ function emitScalarSave(key, fieldRef, scalarType, isOptional, lines, indent) {
1375
+ if (isValueType(scalarType)) {
1376
+ if (scalarType !== "dictionary" && isOptional) {
1377
+ // Optional value types (any, object, unknown) are Option<Value>
1378
+ lines.push(`${indent}if let Some(ref val) = ${fieldRef} {`);
1379
+ lines.push(`${indent} result.insert("${key}".to_string(), val.clone());`);
1380
+ lines.push(`${indent}}`);
1381
+ }
1382
+ else {
1383
+ // Required value types or dictionary (always Value, never Option)
1384
+ lines.push(`${indent}if !${fieldRef}.is_null() {`);
1385
+ lines.push(`${indent} result.insert("${key}".to_string(), ${fieldRef}.clone());`);
1386
+ lines.push(`${indent}}`);
1387
+ }
1388
+ return;
1389
+ }
1390
+ switch (scalarType) {
1391
+ case "string":
1392
+ if (isOptional) {
1393
+ lines.push(`${indent}if let Some(ref val) = ${fieldRef} {`);
1394
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::String(val.clone()));`);
1395
+ lines.push(`${indent}}`);
1396
+ }
1397
+ else {
1398
+ lines.push(`${indent}if !${fieldRef}.is_empty() {`);
1399
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::String(${fieldRef}.clone()));`);
1400
+ lines.push(`${indent}}`);
1401
+ }
1402
+ return;
1403
+ case "boolean":
1404
+ if (isOptional) {
1405
+ lines.push(`${indent}if let Some(val) = ${fieldRef} {`);
1406
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::Bool(val));`);
1407
+ lines.push(`${indent}}`);
1408
+ }
1409
+ else {
1410
+ // Always write booleans
1411
+ lines.push(`${indent}result.insert("${key}".to_string(), serde_json::Value::Bool(${fieldRef}));`);
1412
+ }
1413
+ return;
1414
+ case "int32":
1415
+ case "int64":
1416
+ case "integer":
1417
+ if (isOptional) {
1418
+ lines.push(`${indent}if let Some(val) = ${fieldRef} {`);
1419
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::Number(serde_json::Number::from(val)));`);
1420
+ lines.push(`${indent}}`);
1421
+ }
1422
+ else {
1423
+ lines.push(`${indent}if ${fieldRef} != 0 {`);
1424
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::Number(serde_json::Number::from(${fieldRef})));`);
1425
+ lines.push(`${indent}}`);
1426
+ }
1427
+ return;
1428
+ case "float32":
1429
+ case "float64":
1430
+ case "float":
1431
+ case "number":
1432
+ case "numeric":
1433
+ if (isOptional) {
1434
+ lines.push(`${indent}if let Some(val) = ${fieldRef} {`);
1435
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Number::from_f64(val as f64).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null));`);
1436
+ lines.push(`${indent}}`);
1437
+ }
1438
+ else {
1439
+ lines.push(`${indent}if ${fieldRef} != 0.0 {`);
1440
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Number::from_f64(${fieldRef} as f64).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null));`);
1441
+ lines.push(`${indent}}`);
1442
+ }
1443
+ return;
1444
+ }
1445
+ }
1446
+ /**
1447
+ * Emit a save expression for a variant field (destructured in a match arm).
1448
+ */
1449
+ function emitVariantSaveField(field, polymorphicTypeNames, parentTypeName, lines) {
1450
+ const key = field.name;
1451
+ const fieldRef = rustFieldName(field.name);
1452
+ const cat = field.category;
1453
+ const indent = " ";
1454
+ // Named enum — serialize via .to_string()
1455
+ if (field.enumName && field.allowedValues.length > 0) {
1456
+ if (field.isOptional) {
1457
+ lines.push(`${indent}if let Some(ref val) = ${fieldRef} {`);
1458
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::String(val.to_string()));`);
1459
+ lines.push(`${indent}}`);
1460
+ }
1461
+ else {
1462
+ lines.push(`${indent}result.insert("${key}".to_string(), serde_json::Value::String(${fieldRef}.to_string()));`);
1463
+ }
1464
+ return;
1465
+ }
1466
+ switch (cat.kind) {
1467
+ case "scalar":
1468
+ emitVariantScalarSave(key, fieldRef, cat.scalarType, field.isOptional, lines, indent);
1469
+ return;
1470
+ case "complex": {
1471
+ if (polymorphicTypeNames.has(cat.typeName)) {
1472
+ if (field.isOptional) {
1473
+ lines.push(`${indent}if let Some(val) = ${fieldRef} {`);
1474
+ lines.push(`${indent} result.insert("${key}".to_string(), val.clone());`);
1475
+ lines.push(`${indent}}`);
1476
+ }
1477
+ else {
1478
+ lines.push(`${indent}if !${fieldRef}.is_null() {`);
1479
+ lines.push(`${indent} result.insert("${key}".to_string(), ${fieldRef}.clone());`);
1480
+ lines.push(`${indent}}`);
1481
+ }
1482
+ return;
1483
+ }
1484
+ if (field.isOptional) {
1485
+ lines.push(`${indent}if let Some(val) = ${fieldRef} {`);
1486
+ lines.push(`${indent} result.insert("${key}".to_string(), val.to_value(ctx));`);
1487
+ lines.push(`${indent}}`);
1488
+ }
1489
+ else {
1490
+ lines.push(`${indent}{`);
1491
+ lines.push(`${indent} let nested = ${fieldRef}.to_value(ctx);`);
1492
+ lines.push(`${indent} if !nested.is_null() {`);
1493
+ lines.push(`${indent} result.insert("${key}".to_string(), nested);`);
1494
+ lines.push(`${indent} }`);
1495
+ lines.push(`${indent}}`);
1496
+ }
1497
+ return;
1498
+ }
1499
+ case "collection_scalar": {
1500
+ if (field.isOptional) {
1501
+ lines.push(`${indent}if let Some(items) = ${fieldRef} {`);
1502
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::to_value(items).unwrap_or(serde_json::Value::Null));`);
1503
+ lines.push(`${indent}}`);
1504
+ }
1505
+ else {
1506
+ lines.push(`${indent}if !${fieldRef}.is_empty() {`);
1507
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::to_value(${fieldRef}).unwrap_or(serde_json::Value::Null));`);
1508
+ lines.push(`${indent}}`);
1509
+ }
1510
+ return;
1511
+ }
1512
+ case "collection_complex": {
1513
+ lines.push(`${indent}if !${fieldRef}.is_empty() {`);
1514
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::Array(${fieldRef}.iter().map(|item| item.to_value(ctx)).collect()));`);
1515
+ lines.push(`${indent}}`);
1516
+ return;
1517
+ }
1518
+ case "dict": {
1519
+ lines.push(`${indent}if !${fieldRef}.is_null() {`);
1520
+ lines.push(`${indent} result.insert("${key}".to_string(), ${fieldRef}.clone());`);
1521
+ lines.push(`${indent}}`);
1522
+ return;
1523
+ }
1524
+ }
1525
+ }
1526
+ function emitVariantScalarSave(key, fieldRef, scalarType, isOptional, lines, indent) {
1527
+ if (isValueType(scalarType)) {
1528
+ if (scalarType !== "dictionary" && isOptional) {
1529
+ // Optional value types — variant fields are references, use direct pattern
1530
+ lines.push(`${indent}if let Some(val) = ${fieldRef} {`);
1531
+ lines.push(`${indent} result.insert("${key}".to_string(), val.clone());`);
1532
+ lines.push(`${indent}}`);
1533
+ }
1534
+ else {
1535
+ lines.push(`${indent}if !${fieldRef}.is_null() {`);
1536
+ lines.push(`${indent} result.insert("${key}".to_string(), ${fieldRef}.clone());`);
1537
+ lines.push(`${indent}}`);
1538
+ }
1539
+ return;
1540
+ }
1541
+ switch (scalarType) {
1542
+ case "string":
1543
+ if (isOptional) {
1544
+ lines.push(`${indent}if let Some(val) = ${fieldRef} {`);
1545
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::String(val.clone()));`);
1546
+ lines.push(`${indent}}`);
1547
+ }
1548
+ else {
1549
+ lines.push(`${indent}if !${fieldRef}.is_empty() {`);
1550
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::String(${fieldRef}.clone()));`);
1551
+ lines.push(`${indent}}`);
1552
+ }
1553
+ return;
1554
+ case "boolean":
1555
+ if (isOptional) {
1556
+ lines.push(`${indent}if let Some(val) = ${fieldRef} {`);
1557
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::Bool(*val));`);
1558
+ lines.push(`${indent}}`);
1559
+ }
1560
+ else {
1561
+ lines.push(`${indent}result.insert("${key}".to_string(), serde_json::Value::Bool(*${fieldRef}));`);
1562
+ }
1563
+ return;
1564
+ case "int32":
1565
+ case "int64":
1566
+ case "integer":
1567
+ if (isOptional) {
1568
+ lines.push(`${indent}if let Some(val) = ${fieldRef} {`);
1569
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::Number(serde_json::Number::from(*val)));`);
1570
+ lines.push(`${indent}}`);
1571
+ }
1572
+ else {
1573
+ lines.push(`${indent}if *${fieldRef} != 0 {`);
1574
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Value::Number(serde_json::Number::from(*${fieldRef})));`);
1575
+ lines.push(`${indent}}`);
1576
+ }
1577
+ return;
1578
+ case "float32":
1579
+ case "float64":
1580
+ case "float":
1581
+ case "number":
1582
+ case "numeric":
1583
+ if (isOptional) {
1584
+ lines.push(`${indent}if let Some(val) = ${fieldRef} {`);
1585
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Number::from_f64(*val as f64).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null));`);
1586
+ lines.push(`${indent}}`);
1587
+ }
1588
+ else {
1589
+ lines.push(`${indent}if *${fieldRef} != 0.0 {`);
1590
+ lines.push(`${indent} result.insert("${key}".to_string(), serde_json::Number::from_f64(*${fieldRef} as f64).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null));`);
1591
+ lines.push(`${indent}}`);
1592
+ }
1593
+ return;
1594
+ }
1595
+ }
1596
+ //# sourceMappingURL=emitter.js.map