@xrpckit/target-go-server 0.0.2 → 0.0.3

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/dist/index.js CHANGED
@@ -1,8 +1,18 @@
1
1
  // src/generator.ts
2
- import { BaseCodeGenerator } from "@xrpckit/codegen";
2
+ import {
3
+ TYPE_KINDS,
4
+ toPascalCase as toPascalCase7,
5
+ VALIDATION_KINDS,
6
+ validateSupport
7
+ } from "@xrpckit/sdk";
8
+
9
+ // src/server-generator.ts
10
+ import {
11
+ toPascalCase
12
+ } from "@xrpckit/sdk";
3
13
 
4
14
  // src/go-builder.ts
5
- import { CodeWriter } from "@xrpckit/codegen";
15
+ import { CodeWriter } from "@xrpckit/sdk";
6
16
  var GoBuilder = class extends CodeWriter {
7
17
  // Short aliases
8
18
  l(text) {
@@ -78,9 +88,11 @@ var GoBuilder = class extends CodeWriter {
78
88
  var(name, type, value) {
79
89
  if (type && value) {
80
90
  return this.l(`var ${name} ${type} = ${value}`);
81
- } else if (type) {
91
+ }
92
+ if (type) {
82
93
  return this.l(`var ${name} ${type}`);
83
- } else if (value) {
94
+ }
95
+ if (value) {
84
96
  return this.l(`${name} := ${value}`);
85
97
  }
86
98
  return this.l(`var ${name}`);
@@ -115,63 +127,532 @@ var GoBuilder = class extends CodeWriter {
115
127
  }
116
128
  };
117
129
 
118
- // src/type-mapper.ts
119
- import { toPascalCase } from "@xrpckit/codegen";
120
- var GoTypeMapper = class {
121
- mapType(typeRef) {
130
+ // src/server-generator.ts
131
+ function toMethodName(fullName) {
132
+ return fullName.split(".").map((part) => toPascalCase(part)).join("");
133
+ }
134
+ function toFieldName(fullName) {
135
+ return fullName.split(".").map((part, i) => i === 0 ? part : toPascalCase(part)).join("");
136
+ }
137
+ var GoServerGenerator = class {
138
+ w;
139
+ packageName;
140
+ constructor(packageName = "server") {
141
+ this.w = new GoBuilder();
142
+ this.packageName = packageName;
143
+ }
144
+ generateServer(contract) {
145
+ const w = this.w.reset();
146
+ w.package(this.packageName).import("encoding/json", "net/http", "fmt");
147
+ w.struct("Router", (b) => {
148
+ b.l("middleware []MiddlewareFunc");
149
+ for (const endpoint of contract.endpoints) {
150
+ const fieldName = toFieldName(endpoint.fullName);
151
+ const handlerType = `${toMethodName(endpoint.fullName)}Handler`;
152
+ b.l(`${fieldName} ${handlerType}`);
153
+ }
154
+ });
155
+ w.func("NewRouter() *Router", (b) => {
156
+ b.l("return &Router{").i().l("middleware: make([]MiddlewareFunc, 0),").u().l("}");
157
+ });
158
+ for (const endpoint of contract.endpoints) {
159
+ const methodName = toMethodName(endpoint.fullName);
160
+ const fieldName = toFieldName(endpoint.fullName);
161
+ const handlerType = `${methodName}Handler`;
162
+ w.method(
163
+ "r *Router",
164
+ methodName,
165
+ `handler ${handlerType}`,
166
+ "*Router",
167
+ (b) => {
168
+ b.l(`r.${fieldName} = handler`).return("r");
169
+ }
170
+ );
171
+ }
172
+ w.method(
173
+ "r *Router",
174
+ "Use",
175
+ "middleware MiddlewareFunc",
176
+ "*Router",
177
+ (b) => {
178
+ b.l("r.middleware = append(r.middleware, middleware)").return("r");
179
+ }
180
+ );
181
+ this.generateServeHTTP(contract.endpoints, w);
182
+ return w.toString();
183
+ }
184
+ generateServeHTTP(endpoints, w) {
185
+ w.method(
186
+ "r *Router",
187
+ "ServeHTTP",
188
+ "w http.ResponseWriter, req *http.Request",
189
+ "",
190
+ (b) => {
191
+ b.if("req.Method != http.MethodPost", (b2) => {
192
+ b2.l(
193
+ 'http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)'
194
+ ).return();
195
+ }).n();
196
+ b.var("request", "struct {").i().l('Method string `json:"method"`').l('Params json.RawMessage `json:"params"`').u().l("}").n();
197
+ b.if(
198
+ "err := json.NewDecoder(req.Body).Decode(&request); err != nil",
199
+ (b2) => {
200
+ b2.l(
201
+ 'http.Error(w, fmt.Sprintf("Invalid request: %v", err), http.StatusBadRequest)'
202
+ ).return();
203
+ }
204
+ ).n();
205
+ b.decl("ctx", "&Context{").i().l("Request: req,").l("ResponseWriter: w,").l("Data: make(map[string]interface{}),").u().l("}").n();
206
+ b.comment("Execute middleware chain").l("for _, middleware := range r.middleware {").i().decl("result", "middleware(ctx)").if("result.Error != nil", (b2) => {
207
+ b2.l(
208
+ 'http.Error(w, fmt.Sprintf("Middleware error: %v", result.Error), http.StatusInternalServerError)'
209
+ ).return();
210
+ }).if("result.Response != nil", (b2) => {
211
+ b2.comment("Middleware short-circuited with response").return();
212
+ }).l("ctx = result.Context").u().l("}").n();
213
+ const cases = endpoints.map((endpoint) => ({
214
+ value: `"${endpoint.fullName}"`,
215
+ fn: (b2) => {
216
+ const fieldName = toFieldName(endpoint.fullName);
217
+ b2.if(`r.${fieldName} == nil`, (b3) => {
218
+ b3.l(
219
+ 'http.Error(w, "Handler not registered", http.StatusNotFound)'
220
+ ).return();
221
+ }).n();
222
+ const inputTypeName = toPascalCase(endpoint.input.name);
223
+ b2.var("input", inputTypeName);
224
+ b2.if(
225
+ "err := json.Unmarshal(request.Params, &input); err != nil",
226
+ (b3) => {
227
+ b3.l(
228
+ 'http.Error(w, fmt.Sprintf("Invalid params: %v", err), http.StatusBadRequest)'
229
+ ).return();
230
+ }
231
+ ).n();
232
+ const validationFuncName = `Validate${inputTypeName}`;
233
+ b2.if(`err := ${validationFuncName}(input); err != nil`, (b3) => {
234
+ b3.l('w.Header().Set("Content-Type", "application/json")').l(
235
+ "w.WriteHeader(http.StatusBadRequest)"
236
+ );
237
+ b3.l("if validationErrs, ok := err.(ValidationErrors); ok {").i().l("json.NewEncoder(w).Encode(map[string]interface{}{").i().l('"error": "Validation failed",').l('"errors": validationErrs,').u().l("})").u().l("} else {").i().l("json.NewEncoder(w).Encode(map[string]interface{}{").i().l('"error": err.Error(),').u().l("})").u().l("}");
238
+ b3.return();
239
+ }).n();
240
+ b2.decl("result, err", `r.${fieldName}(ctx, input)`);
241
+ b2.ifErr((b3) => {
242
+ b3.l('w.Header().Set("Content-Type", "application/json")').l(
243
+ 'json.NewEncoder(w).Encode(map[string]interface{}{"error": err.Error()})'
244
+ ).return();
245
+ }).n();
246
+ b2.l('w.Header().Set("Content-Type", "application/json")').l(
247
+ 'json.NewEncoder(w).Encode(map[string]interface{}{"result": result})'
248
+ ).return();
249
+ }
250
+ }));
251
+ w.switch("request.Method", cases, (b2) => {
252
+ b2.l(
253
+ 'http.Error(w, "Method not found", http.StatusNotFound)'
254
+ ).return();
255
+ });
256
+ }
257
+ );
258
+ }
259
+ };
260
+
261
+ // src/type-collector.ts
262
+ import {
263
+ toPascalCase as toPascalCase2
264
+ } from "@xrpckit/sdk";
265
+ var GoTypeCollector = class {
266
+ collectedTypes = /* @__PURE__ */ new Map();
267
+ usedNames = /* @__PURE__ */ new Set();
268
+ /**
269
+ * Collect all types from a contract that need Go struct generation.
270
+ * This includes:
271
+ * - Named types from contract.types
272
+ * - Anonymous inline objects nested in properties
273
+ * - Array element types that are inline objects
274
+ * - Optional/nullable wrapped inline objects
275
+ */
276
+ collectTypes(contract) {
277
+ this.collectedTypes.clear();
278
+ this.usedNames.clear();
279
+ for (const type of contract.types) {
280
+ const pascalName = toPascalCase2(type.name);
281
+ this.usedNames.add(pascalName);
282
+ }
283
+ for (const type of contract.types) {
284
+ this.processTypeDefinition(type, type.name);
285
+ }
286
+ return Array.from(this.collectedTypes.values());
287
+ }
288
+ /**
289
+ * Process a type definition and extract any nested inline types
290
+ */
291
+ processTypeDefinition(type, contextName) {
292
+ if (type.properties) {
293
+ for (const prop of type.properties) {
294
+ this.processProperty(prop, contextName);
295
+ }
296
+ }
297
+ if (type.kind === "array" && type.elementType) {
298
+ this.processTypeReference(
299
+ type.elementType,
300
+ `${contextName}Item`,
301
+ `${contextName}.elementType`
302
+ );
303
+ }
304
+ }
305
+ /**
306
+ * Process a property and extract any nested inline types
307
+ */
308
+ processProperty(prop, parentContext) {
309
+ const propContext = `${parentContext}${toPascalCase2(prop.name)}`;
310
+ this.processTypeReference(
311
+ prop.type,
312
+ propContext,
313
+ `${parentContext}.${prop.name}`
314
+ );
315
+ }
316
+ /**
317
+ * Process a type reference and extract any nested inline types.
318
+ * This is the core function that handles all the different type kinds.
319
+ */
320
+ processTypeReference(typeRef, suggestedName, source) {
122
321
  if (typeRef.kind === "optional") {
123
322
  if (typeof typeRef.baseType === "object") {
124
- return this.mapType(typeRef.baseType);
323
+ this.processTypeReference(
324
+ typeRef.baseType,
325
+ suggestedName,
326
+ `${source}.optional`
327
+ );
125
328
  }
126
- return "interface{}";
329
+ return;
127
330
  }
128
331
  if (typeRef.kind === "nullable") {
129
332
  if (typeof typeRef.baseType === "object") {
130
- return `*${this.mapType(typeRef.baseType)}`;
333
+ this.processTypeReference(
334
+ typeRef.baseType,
335
+ suggestedName,
336
+ `${source}.nullable`
337
+ );
131
338
  }
132
- return "*interface{}";
339
+ return;
133
340
  }
134
- if (typeRef.kind === "array") {
135
- return `[]${this.mapType(typeRef.elementType)}`;
341
+ if (typeRef.kind === "array" && typeRef.elementType) {
342
+ const elementName = suggestedName.endsWith("Item") ? suggestedName : `${suggestedName}Item`;
343
+ this.processTypeReference(
344
+ typeRef.elementType,
345
+ elementName,
346
+ `${source}.array`
347
+ );
348
+ return;
136
349
  }
137
- if (typeRef.kind === "object") {
138
- if (typeRef.name) {
139
- return toPascalCase(typeRef.name);
350
+ if (typeRef.kind === "object" && typeRef.properties && !typeRef.name) {
351
+ const assignedName = this.assignUniqueName(suggestedName);
352
+ typeRef.name = assignedName;
353
+ this.collectedTypes.set(assignedName, {
354
+ name: assignedName,
355
+ typeRef,
356
+ source
357
+ });
358
+ for (const prop of typeRef.properties) {
359
+ this.processProperty(prop, assignedName);
140
360
  }
141
- return "interface{}";
142
- }
143
- if (typeRef.kind === "record") {
144
- const valueType = typeRef.valueType ? this.mapType(typeRef.valueType) : "interface{}";
145
- return `map[string]${valueType}`;
361
+ return;
146
362
  }
147
- if (typeRef.kind === "tuple") {
148
- return "[]interface{}";
363
+ if (typeRef.kind === "record" && typeRef.valueType) {
364
+ this.processTypeReference(
365
+ typeRef.valueType,
366
+ `${suggestedName}Value`,
367
+ `${source}.record`
368
+ );
369
+ return;
149
370
  }
150
- if (typeRef.kind === "union") {
151
- return "interface{}";
371
+ if (typeRef.kind === "tuple" && typeRef.tupleElements) {
372
+ typeRef.tupleElements.forEach((elem, index) => {
373
+ this.processTypeReference(
374
+ elem,
375
+ `${suggestedName}V${index}`,
376
+ `${source}.tuple[${index}]`
377
+ );
378
+ });
379
+ return;
152
380
  }
153
- if (typeRef.kind === "enum") {
154
- return "string";
381
+ if (typeRef.kind === "union" && typeRef.unionTypes) {
382
+ typeRef.unionTypes.forEach((variant, index) => {
383
+ this.processTypeReference(
384
+ variant,
385
+ `${suggestedName}Variant${index}`,
386
+ `${source}.union[${index}]`
387
+ );
388
+ });
389
+ return;
155
390
  }
156
- if (typeRef.kind === "literal") {
157
- if (typeof typeRef.literalValue === "string") {
158
- return "string";
159
- } else if (typeof typeRef.literalValue === "number") {
160
- return "float64";
161
- } else if (typeof typeRef.literalValue === "boolean") {
162
- return "bool";
391
+ if (typeRef.kind === "object" && typeRef.properties && typeRef.name) {
392
+ for (const prop of typeRef.properties) {
393
+ this.processProperty(prop, toPascalCase2(typeRef.name));
163
394
  }
164
- return "interface{}";
165
- }
166
- if (typeRef.kind === "date") {
167
- return "time.Time";
168
395
  }
169
- if (typeRef.kind === "primitive") {
170
- const baseType = typeof typeRef.baseType === "string" ? typeRef.baseType : "unknown";
171
- return this.mapPrimitive(baseType);
396
+ }
397
+ /**
398
+ * Assign a unique name, adding numeric suffix if there's a collision
399
+ */
400
+ assignUniqueName(baseName) {
401
+ let name = baseName;
402
+ let counter = 1;
403
+ while (this.usedNames.has(name)) {
404
+ name = `${baseName}${counter}`;
405
+ counter++;
172
406
  }
173
- return "interface{}";
407
+ this.usedNames.add(name);
408
+ return name;
409
+ }
410
+ /**
411
+ * Get all collected nested types (not including the main contract types)
412
+ */
413
+ getCollectedTypes() {
414
+ return Array.from(this.collectedTypes.values());
174
415
  }
416
+ };
417
+
418
+ // src/type-generator.ts
419
+ import {
420
+ toPascalCase as toPascalCase5
421
+ } from "@xrpckit/sdk";
422
+
423
+ // src/type-mapper.ts
424
+ import {
425
+ TypeMapperBase,
426
+ toPascalCase as toPascalCase4
427
+ } from "@xrpckit/sdk";
428
+
429
+ // src/patterns.ts
430
+ import { toPascalCase as toPascalCase3 } from "@xrpckit/sdk";
431
+ function createGoEnumPattern(name, values) {
432
+ const stringValues = values.filter((v) => typeof v === "string");
433
+ const constants = stringValues.map((v) => {
434
+ const constName = `${name}${toPascalCase3(v)}`;
435
+ return { constName, value: v };
436
+ });
437
+ const code = `// ${name} enum type
438
+ type ${name} string
439
+
440
+ const (
441
+ ${constants.map((c) => ` ${c.constName} ${name} = "${c.value}"`).join("\n")}
442
+ )
443
+
444
+ // IsValid checks if the ${name} value is valid
445
+ func (e ${name}) IsValid() bool {
446
+ switch e {
447
+ case ${constants.map((c) => c.constName).join(", ")}:
448
+ return true
449
+ }
450
+ return false
451
+ }
452
+
453
+ // Parse${name} parses a string into a ${name} value
454
+ func Parse${name}(s string) (${name}, error) {
455
+ e := ${name}(s)
456
+ if !e.IsValid() {
457
+ return "", fmt.Errorf("invalid ${name}: %s", s)
458
+ }
459
+ return e, nil
460
+ }
461
+
462
+ // All${name}Values returns all valid ${name} values
463
+ func All${name}Values() []${name} {
464
+ return []${name}{${constants.map((c) => c.constName).join(", ")}}
465
+ }`;
466
+ return {
467
+ id: `enum_${name}`,
468
+ code,
469
+ imports: ["fmt"],
470
+ includeOnce: true,
471
+ priority: 100
472
+ // Enums should come early
473
+ };
474
+ }
475
+ function createGoBigIntPattern() {
476
+ const code = `// BigInt is a wrapper around big.Int for JSON serialization
477
+ type BigInt struct {
478
+ *big.Int
479
+ }
480
+
481
+ // MarshalJSON implements json.Marshaler for BigInt
482
+ func (b BigInt) MarshalJSON() ([]byte, error) {
483
+ if b.Int == nil {
484
+ return []byte("null"), nil
485
+ }
486
+ return []byte(b.String()), nil
487
+ }
488
+
489
+ // UnmarshalJSON implements json.Unmarshaler for BigInt
490
+ func (b *BigInt) UnmarshalJSON(data []byte) error {
491
+ str := string(data)
492
+ if str == "null" {
493
+ b.Int = nil
494
+ return nil
495
+ }
496
+ // Remove quotes if present
497
+ if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
498
+ str = str[1 : len(str)-1]
499
+ }
500
+ b.Int = new(big.Int)
501
+ _, ok := b.Int.SetString(str, 10)
502
+ if !ok {
503
+ return fmt.Errorf("invalid BigInt: %s", str)
504
+ }
505
+ return nil
506
+ }`;
507
+ return {
508
+ id: "bigint",
509
+ code,
510
+ imports: ["math/big", "fmt"],
511
+ includeOnce: true,
512
+ priority: 90
513
+ };
514
+ }
515
+ function createGoUnionPattern(name, variants) {
516
+ const assertions = variants.map((v) => {
517
+ const methodName = `As${toPascalCase3(v.replace(/[*[\]]/g, ""))}`;
518
+ return `// ${methodName} returns the value as ${v}, or ok=false if not that type
519
+ func (u ${name}) ${methodName}() (${v}, bool) {
520
+ val, ok := u.Value.(${v})
521
+ return val, ok
522
+ }`;
523
+ }).join("\n\n");
524
+ const code = `// ${name} represents a union type that can hold one of several types
525
+ type ${name} struct {
526
+ Value interface{}
527
+ }
528
+
529
+ // MarshalJSON implements json.Marshaler for ${name}
530
+ func (u ${name}) MarshalJSON() ([]byte, error) {
531
+ return json.Marshal(u.Value)
532
+ }
533
+
534
+ // UnmarshalJSON implements json.Unmarshaler for ${name}
535
+ func (u *${name}) UnmarshalJSON(data []byte) error {
536
+ return json.Unmarshal(data, &u.Value)
537
+ }
538
+
539
+ ${assertions}`;
540
+ return {
541
+ id: `union_${name}`,
542
+ code,
543
+ imports: ["encoding/json"],
544
+ includeOnce: true,
545
+ priority: 80
546
+ };
547
+ }
548
+ function createGoTuplePattern(name, elements) {
549
+ const fields = elements.map((el, i) => ` V${i} ${el} \`json:"${i}"\``);
550
+ const code = `// ${name} represents a tuple type with ${elements.length} elements
551
+ type ${name} struct {
552
+ ${fields.join("\n")}
553
+ }
554
+
555
+ // MarshalJSON implements json.Marshaler for ${name} to serialize as JSON array
556
+ func (t ${name}) MarshalJSON() ([]byte, error) {
557
+ return json.Marshal([]interface{}{${elements.map((_, i) => `t.V${i}`).join(", ")}})
558
+ }
559
+
560
+ // UnmarshalJSON implements json.Unmarshaler for ${name} from JSON array
561
+ func (t *${name}) UnmarshalJSON(data []byte) error {
562
+ var arr []json.RawMessage
563
+ if err := json.Unmarshal(data, &arr); err != nil {
564
+ return err
565
+ }
566
+ if len(arr) != ${elements.length} {
567
+ return fmt.Errorf("expected ${elements.length} elements, got %d", len(arr))
568
+ }
569
+ ${elements.map((_, i) => ` if err := json.Unmarshal(arr[${i}], &t.V${i}); err != nil {
570
+ return fmt.Errorf("element ${i}: %w", err)
571
+ }`).join("\n")}
572
+ return nil
573
+ }`;
574
+ return {
575
+ id: `tuple_${name}`,
576
+ code,
577
+ imports: ["encoding/json", "fmt"],
578
+ includeOnce: true,
579
+ priority: 70
580
+ };
581
+ }
582
+ function createGoDatePattern() {
583
+ const code = `// DateTime is a wrapper around time.Time with flexible JSON parsing
584
+ type DateTime struct {
585
+ time.Time
586
+ }
587
+
588
+ // Common date/time formats to try when parsing
589
+ var dateTimeFormats = []string{
590
+ time.RFC3339,
591
+ time.RFC3339Nano,
592
+ "2006-01-02T15:04:05Z07:00",
593
+ "2006-01-02T15:04:05",
594
+ "2006-01-02",
595
+ }
596
+
597
+ // UnmarshalJSON implements json.Unmarshaler for DateTime
598
+ func (d *DateTime) UnmarshalJSON(data []byte) error {
599
+ str := string(data)
600
+ if str == "null" {
601
+ return nil
602
+ }
603
+ // Remove quotes
604
+ if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
605
+ str = str[1 : len(str)-1]
606
+ }
607
+ // Try each format
608
+ for _, format := range dateTimeFormats {
609
+ if t, err := time.Parse(format, str); err == nil {
610
+ d.Time = t
611
+ return nil
612
+ }
613
+ }
614
+ return fmt.Errorf("cannot parse date: %s", str)
615
+ }
616
+
617
+ // MarshalJSON implements json.Marshaler for DateTime
618
+ func (d DateTime) MarshalJSON() ([]byte, error) {
619
+ return json.Marshal(d.Time.Format(time.RFC3339))
620
+ }`;
621
+ return {
622
+ id: "datetime",
623
+ code,
624
+ imports: ["time", "encoding/json", "fmt"],
625
+ includeOnce: true,
626
+ priority: 85
627
+ };
628
+ }
629
+
630
+ // src/type-mapper.ts
631
+ var GoTypeMapper = class extends TypeMapperBase {
632
+ // Registry of tuple type names for reference during validation
633
+ tupleTypes = /* @__PURE__ */ new Map();
634
+ // Registry of union type names
635
+ unionTypes = /* @__PURE__ */ new Map();
636
+ /**
637
+ * Complete mapping of all type kinds to Go types.
638
+ * TypeScript enforces exhaustiveness at compile time.
639
+ */
640
+ typeMapping = {
641
+ object: (ctx) => this.handleObject(ctx),
642
+ array: (ctx) => this.handleArray(ctx),
643
+ primitive: (ctx) => this.handlePrimitive(ctx),
644
+ optional: (ctx) => this.handleOptional(ctx),
645
+ nullable: (ctx) => this.handleNullable(ctx),
646
+ union: (ctx) => this.handleUnion(ctx),
647
+ enum: (ctx) => this.handleEnum(ctx),
648
+ literal: (ctx) => this.handleLiteral(ctx),
649
+ record: (ctx) => this.handleRecord(ctx),
650
+ tuple: (ctx) => this.handleTuple(ctx),
651
+ date: () => this.handleDate()
652
+ };
653
+ /**
654
+ * Map a primitive base type to Go type.
655
+ */
175
656
  mapPrimitive(type) {
176
657
  const mapping = {
177
658
  string: "string",
@@ -180,34 +661,240 @@ var GoTypeMapper = class {
180
661
  boolean: "bool",
181
662
  date: "time.Time",
182
663
  uuid: "string",
183
- email: "string"
664
+ email: "string",
665
+ any: "interface{}",
666
+ unknown: "interface{}"
667
+ };
668
+ const result = mapping[type];
669
+ if (!result) {
670
+ console.warn("[GoTypeMapper] Unknown primitive type:", type);
671
+ return "interface{}";
672
+ }
673
+ return result;
674
+ }
675
+ /**
676
+ * Get all registered tuple types that need struct generation
677
+ */
678
+ getTupleTypes() {
679
+ return this.tupleTypes;
680
+ }
681
+ /**
682
+ * Get all registered union types that need wrapper struct generation
683
+ */
684
+ getUnionTypes() {
685
+ return this.unionTypes;
686
+ }
687
+ /**
688
+ * Reset the type registries (call between generation runs)
689
+ */
690
+ reset() {
691
+ super.reset();
692
+ this.tupleTypes.clear();
693
+ this.unionTypes.clear();
694
+ }
695
+ // --- Private handler methods ---
696
+ handleObject(ctx) {
697
+ const { typeRef, name } = ctx;
698
+ if (name || typeRef.name) {
699
+ return { type: toPascalCase4(name || typeRef.name) };
700
+ }
701
+ console.warn(
702
+ "[GoTypeMapper] Object without name - falling back to map[string]interface{}:",
703
+ typeRef
704
+ );
705
+ return { type: "map[string]interface{}" };
706
+ }
707
+ handleArray(ctx) {
708
+ const { typeRef } = ctx;
709
+ if (!typeRef.elementType) {
710
+ return { type: "[]interface{}" };
711
+ }
712
+ const element = this.mapType(typeRef.elementType);
713
+ return {
714
+ type: `[]${element.type}`,
715
+ imports: element.imports
716
+ };
717
+ }
718
+ handlePrimitive(ctx) {
719
+ const { typeRef } = ctx;
720
+ const baseType = typeof typeRef.baseType === "string" ? typeRef.baseType : "unknown";
721
+ const goType = this.mapPrimitive(baseType);
722
+ if (goType === "time.Time") {
723
+ return { type: goType, imports: ["time"] };
724
+ }
725
+ return { type: goType };
726
+ }
727
+ handleOptional(ctx) {
728
+ const { typeRef } = ctx;
729
+ if (typeof typeRef.baseType === "object") {
730
+ return this.mapType(typeRef.baseType);
731
+ }
732
+ if (typeof typeRef.baseType === "string") {
733
+ return { type: this.mapPrimitive(typeRef.baseType) };
734
+ }
735
+ console.warn("[GoTypeMapper] Optional with unknown baseType:", typeRef);
736
+ return { type: "interface{}" };
737
+ }
738
+ handleNullable(ctx) {
739
+ const { typeRef } = ctx;
740
+ if (typeof typeRef.baseType === "object") {
741
+ const base = this.mapType(typeRef.baseType);
742
+ return {
743
+ type: `*${base.type}`,
744
+ imports: base.imports
745
+ };
746
+ }
747
+ if (typeof typeRef.baseType === "string") {
748
+ return { type: `*${this.mapPrimitive(typeRef.baseType)}` };
749
+ }
750
+ console.warn("[GoTypeMapper] Nullable with unknown baseType:", typeRef);
751
+ return { type: "*interface{}" };
752
+ }
753
+ handleUnion(ctx) {
754
+ const { typeRef, name } = ctx;
755
+ const unionName = name || typeRef.name;
756
+ if (unionName) {
757
+ const goName = toPascalCase4(unionName);
758
+ this.unionTypes.set(goName, typeRef);
759
+ const variants = typeRef.unionTypes?.map((v) => this.mapType(v).type) ?? [];
760
+ const utility = createGoUnionPattern(goName, variants);
761
+ return {
762
+ type: goName,
763
+ utilities: [utility],
764
+ imports: utility.imports
765
+ };
766
+ }
767
+ if (typeRef.unionTypes && typeRef.unionTypes.length > 0) {
768
+ const variantTypes = typeRef.unionTypes.map((v) => this.mapType(v));
769
+ const allSameType = variantTypes.every(
770
+ (v) => v.type === variantTypes[0].type
771
+ );
772
+ if (allSameType) {
773
+ return variantTypes[0];
774
+ }
775
+ const nonNullVariants = typeRef.unionTypes.filter(
776
+ (v) => !(v.kind === "literal" && v.literalValue === null)
777
+ );
778
+ if (nonNullVariants.length === 1) {
779
+ const base = this.mapType(nonNullVariants[0]);
780
+ return {
781
+ type: `*${base.type}`,
782
+ imports: base.imports
783
+ };
784
+ }
785
+ }
786
+ console.warn(
787
+ "[GoTypeMapper] Anonymous heterogeneous union - using interface{}:",
788
+ typeRef
789
+ );
790
+ return { type: "interface{}" };
791
+ }
792
+ handleEnum(_ctx) {
793
+ return { type: "string" };
794
+ }
795
+ handleLiteral(ctx) {
796
+ const { typeRef } = ctx;
797
+ if (typeRef.literalValue === null) {
798
+ return { type: "interface{}" };
799
+ }
800
+ if (typeof typeRef.literalValue === "string") {
801
+ return { type: "string" };
802
+ }
803
+ if (typeof typeRef.literalValue === "number") {
804
+ return { type: "float64" };
805
+ }
806
+ if (typeof typeRef.literalValue === "boolean") {
807
+ return { type: "bool" };
808
+ }
809
+ console.warn("[GoTypeMapper] Unknown literal type:", typeRef);
810
+ return { type: "interface{}" };
811
+ }
812
+ handleRecord(ctx) {
813
+ const { typeRef } = ctx;
814
+ if (!typeRef.valueType) {
815
+ return { type: "map[string]interface{}" };
816
+ }
817
+ const value = this.mapType(typeRef.valueType);
818
+ return {
819
+ type: `map[string]${value.type}`,
820
+ imports: value.imports
821
+ };
822
+ }
823
+ handleTuple(ctx) {
824
+ const { typeRef, name } = ctx;
825
+ const tupleName = name || typeRef.name;
826
+ if (tupleName) {
827
+ const goName = toPascalCase4(tupleName);
828
+ this.tupleTypes.set(goName, typeRef);
829
+ const elements = typeRef.tupleElements?.map((e) => this.mapType(e).type) ?? [];
830
+ const utility = createGoTuplePattern(goName, elements);
831
+ return {
832
+ type: goName,
833
+ utilities: [utility],
834
+ imports: utility.imports
835
+ };
836
+ }
837
+ if (typeRef.tupleElements && typeRef.tupleElements.length > 0) {
838
+ const elementTypes = typeRef.tupleElements.map((e) => this.mapType(e));
839
+ const allSameType = elementTypes.every(
840
+ (e) => e.type === elementTypes[0].type
841
+ );
842
+ if (allSameType) {
843
+ return { type: `[]${elementTypes[0].type}` };
844
+ }
845
+ }
846
+ console.warn(
847
+ "[GoTypeMapper] Anonymous heterogeneous tuple - using []interface{}:",
848
+ typeRef
849
+ );
850
+ return { type: "[]interface{}" };
851
+ }
852
+ handleDate() {
853
+ return {
854
+ type: "time.Time",
855
+ imports: ["time"]
184
856
  };
185
- return mapping[type] || "interface{}";
186
857
  }
187
858
  };
188
859
 
189
860
  // src/type-generator.ts
190
- import { toPascalCase as toPascalCase2 } from "@xrpckit/codegen";
191
- function toMethodName(fullName) {
192
- return fullName.split(".").map((part) => toPascalCase2(part)).join("");
861
+ function toMethodName2(fullName) {
862
+ return fullName.split(".").map((part) => toPascalCase5(part)).join("");
193
863
  }
194
864
  var GoTypeGenerator = class {
195
865
  w;
196
866
  typeMapper;
197
867
  packageName;
868
+ generatedTypes = /* @__PURE__ */ new Set();
198
869
  constructor(packageName = "server") {
199
870
  this.w = new GoBuilder();
200
871
  this.typeMapper = new GoTypeMapper();
201
872
  this.packageName = packageName;
202
873
  }
203
- generateTypes(contract) {
874
+ /**
875
+ * Generate Go types from a contract definition.
876
+ * @param contract - The contract definition (should have names assigned to inline types)
877
+ * @param collectedTypes - Optional pre-collected nested types from GoTypeCollector
878
+ */
879
+ generateTypes(contract, collectedTypes) {
204
880
  const w = this.w.reset();
881
+ this.typeMapper.reset();
882
+ this.generatedTypes.clear();
205
883
  w.package(this.packageName).import("net/http");
206
884
  this.generateContextType();
207
885
  this.generateMiddlewareTypes();
208
886
  for (const type of contract.types) {
209
887
  this.generateType(type);
210
888
  }
889
+ if (collectedTypes) {
890
+ for (const collected of collectedTypes) {
891
+ if (!this.generatedTypes.has(collected.name)) {
892
+ this.generateTypeFromReference(collected.name, collected.typeRef);
893
+ }
894
+ }
895
+ }
896
+ this.generateTupleTypes();
897
+ this.generateUnionTypes();
211
898
  this.generateTypedHandlers(contract);
212
899
  return w.toString();
213
900
  }
@@ -215,21 +902,11 @@ var GoTypeGenerator = class {
215
902
  this.w.struct("Context", (b) => {
216
903
  b.l("Request *http.Request").l("ResponseWriter http.ResponseWriter").l("Data map[string]interface{}");
217
904
  }).n();
218
- this.w.comment("GetUserId retrieves userId from context if set by middleware").n().func("GetUserId(ctx *Context) (string, bool)", (b) => {
219
- b.if('val, ok := ctx.Data["userId"].(string); ok', (b2) => {
220
- b2.return("val, true");
221
- });
222
- b.return('"", false');
223
- }).n();
224
- this.w.comment("GetSessionId retrieves sessionId from context if set by middleware").n().func("GetSessionId(ctx *Context) (string, bool)", (b) => {
225
- b.if('val, ok := ctx.Data["sessionId"].(string); ok', (b2) => {
226
- b2.return("val, true");
227
- });
228
- b.return('"", false');
229
- }).n();
230
905
  }
231
906
  generateMiddlewareTypes() {
232
- this.w.comment("MiddlewareFunc is a function that processes a request and extends context").n().type("MiddlewareFunc", "func(ctx *Context) *MiddlewareResult").n();
907
+ this.w.comment(
908
+ "MiddlewareFunc is a function that processes a request and extends context"
909
+ ).n().type("MiddlewareFunc", "func(ctx *Context) *MiddlewareResult").n();
233
910
  this.w.struct("MiddlewareResult", (b) => {
234
911
  b.l("Context *Context").l("Error error").l("Response *http.Response");
235
912
  }).n();
@@ -239,38 +916,157 @@ var GoTypeGenerator = class {
239
916
  this.w.comment("NewMiddlewareError creates a middleware result with an error").n().func("NewMiddlewareError(err error) *MiddlewareResult", (b) => {
240
917
  b.return("&MiddlewareResult{Error: err}");
241
918
  }).n();
242
- this.w.comment("NewMiddlewareResponse creates a middleware result that short-circuits with a response").n().func("NewMiddlewareResponse(resp *http.Response) *MiddlewareResult", (b) => {
243
- b.return("&MiddlewareResult{Response: resp}");
244
- }).n();
919
+ this.w.comment(
920
+ "NewMiddlewareResponse creates a middleware result that short-circuits with a response"
921
+ ).n().func(
922
+ "NewMiddlewareResponse(resp *http.Response) *MiddlewareResult",
923
+ (b) => {
924
+ b.return("&MiddlewareResult{Response: resp}");
925
+ }
926
+ ).n();
245
927
  }
246
928
  generateType(type) {
247
- const typeName = toPascalCase2(type.name);
929
+ const typeName = toPascalCase5(type.name);
930
+ if (this.generatedTypes.has(typeName)) {
931
+ return;
932
+ }
248
933
  if (type.kind === "array" && type.elementType) {
249
934
  if (type.elementType.kind === "object" && type.elementType.properties) {
250
- const elementTypeName = typeName + "Item";
251
- this.w.struct(elementTypeName, (b) => {
252
- for (const prop of type.elementType.properties) {
253
- const goType = this.typeMapper.mapType(prop.type);
254
- const jsonTag = this.generateJSONTag(prop);
255
- b.l(`${toPascalCase2(prop.name)} ${goType} \`${jsonTag}\``);
256
- }
257
- });
935
+ const elementTypeName = type.elementType.name ? toPascalCase5(type.elementType.name) : `${typeName}Item`;
936
+ if (!this.generatedTypes.has(elementTypeName)) {
937
+ this.generateTypeFromReference(elementTypeName, type.elementType);
938
+ }
939
+ this.generatedTypes.add(typeName);
258
940
  this.w.type(typeName, `[]${elementTypeName}`);
259
941
  } else {
260
- const elementGoType = this.typeMapper.mapType(type.elementType);
942
+ const elementGoType = this.typeMapper.mapType(type.elementType).type;
943
+ this.generatedTypes.add(typeName);
261
944
  this.w.type(typeName, `[]${elementGoType}`);
262
945
  }
263
946
  return;
264
947
  }
265
- this.w.struct(typeName, (b) => {
266
- if (type.properties) {
267
- for (const prop of type.properties) {
268
- const goType = this.typeMapper.mapType(prop.type);
948
+ if (type.kind === "object") {
949
+ this.generatedTypes.add(typeName);
950
+ this.w.struct(typeName, (b) => {
951
+ if (type.properties) {
952
+ for (const prop of type.properties) {
953
+ const goType2 = this.typeMapper.mapType(prop.type).type;
954
+ const jsonTag = this.generateJSONTag(prop);
955
+ b.l(`${toPascalCase5(prop.name)} ${goType2} \`${jsonTag}\``);
956
+ }
957
+ }
958
+ });
959
+ return;
960
+ }
961
+ if (type.kind === "union" || type.kind === "tuple") {
962
+ this.typeMapper.mapType(type);
963
+ return;
964
+ }
965
+ const goType = this.typeMapper.mapType(type).type;
966
+ this.generatedTypes.add(typeName);
967
+ this.w.type(typeName, goType);
968
+ }
969
+ /**
970
+ * Generate a type from a TypeReference (used for nested inline types)
971
+ */
972
+ generateTypeFromReference(typeName, typeRef) {
973
+ if (this.generatedTypes.has(typeName)) {
974
+ return;
975
+ }
976
+ if (typeRef.kind === "object" && typeRef.properties) {
977
+ this.generatedTypes.add(typeName);
978
+ this.w.struct(typeName, (b) => {
979
+ for (const prop of typeRef.properties) {
980
+ const goType2 = this.typeMapper.mapType(prop.type).type;
269
981
  const jsonTag = this.generateJSONTag(prop);
270
- b.l(`${toPascalCase2(prop.name)} ${goType} \`${jsonTag}\``);
982
+ b.l(`${toPascalCase5(prop.name)} ${goType2} \`${jsonTag}\``);
271
983
  }
984
+ });
985
+ return;
986
+ }
987
+ if (typeRef.kind === "array" && typeRef.elementType) {
988
+ if (typeRef.elementType.kind === "object" && typeRef.elementType.properties) {
989
+ const elementTypeName = typeRef.elementType.name ? toPascalCase5(typeRef.elementType.name) : `${typeName}Item`;
990
+ if (!this.generatedTypes.has(elementTypeName)) {
991
+ this.generateTypeFromReference(elementTypeName, typeRef.elementType);
992
+ }
993
+ this.generatedTypes.add(typeName);
994
+ this.w.type(typeName, `[]${elementTypeName}`);
995
+ } else {
996
+ const elementGoType = this.typeMapper.mapType(typeRef.elementType).type;
997
+ this.generatedTypes.add(typeName);
998
+ this.w.type(typeName, `[]${elementGoType}`);
272
999
  }
273
- });
1000
+ return;
1001
+ }
1002
+ if (typeRef.kind === "union" || typeRef.kind === "tuple") {
1003
+ this.typeMapper.mapType(typeRef, { name: typeName });
1004
+ return;
1005
+ }
1006
+ const goType = this.typeMapper.mapType(typeRef).type;
1007
+ this.generatedTypes.add(typeName);
1008
+ this.w.type(typeName, goType);
1009
+ }
1010
+ /**
1011
+ * Generate struct types for tuples
1012
+ */
1013
+ generateTupleTypes() {
1014
+ const tupleTypes = this.typeMapper.getTupleTypes();
1015
+ for (const [name, typeRef] of tupleTypes) {
1016
+ if (this.generatedTypes.has(name)) continue;
1017
+ this.generatedTypes.add(name);
1018
+ if (typeRef.tupleElements && typeRef.tupleElements.length > 0) {
1019
+ this.w.struct(name, (b) => {
1020
+ typeRef.tupleElements?.forEach((elem, index) => {
1021
+ const goType = this.typeMapper.mapType(elem).type;
1022
+ b.l(`V${index} ${goType} \`json:"v${index}"\``);
1023
+ });
1024
+ });
1025
+ }
1026
+ }
1027
+ }
1028
+ /**
1029
+ * Generate wrapper struct types for unions
1030
+ */
1031
+ generateUnionTypes() {
1032
+ const unionTypes = this.typeMapper.getUnionTypes();
1033
+ for (const [name, typeRef] of unionTypes) {
1034
+ if (this.generatedTypes.has(name)) continue;
1035
+ this.generatedTypes.add(name);
1036
+ if (typeRef.unionTypes && typeRef.unionTypes.length > 0) {
1037
+ this.w.struct(name, (b) => {
1038
+ b.l('Type string `json:"type,omitempty"`');
1039
+ const seenTypes = /* @__PURE__ */ new Set();
1040
+ typeRef.unionTypes?.forEach((variant, index) => {
1041
+ const goType = this.typeMapper.mapType(variant).type;
1042
+ if (seenTypes.has(goType)) return;
1043
+ seenTypes.add(goType);
1044
+ const fieldName = this.getUnionFieldName(variant, index);
1045
+ b.l(
1046
+ `${fieldName} *${goType} \`json:"${fieldName.toLowerCase()},omitempty"\``
1047
+ );
1048
+ });
1049
+ });
1050
+ }
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Get a descriptive field name for a union variant
1055
+ */
1056
+ getUnionFieldName(typeRef, index) {
1057
+ if (typeRef.name) {
1058
+ return toPascalCase5(typeRef.name);
1059
+ }
1060
+ if (typeRef.kind === "primitive" && typeof typeRef.baseType === "string") {
1061
+ return toPascalCase5(typeRef.baseType);
1062
+ }
1063
+ if (typeRef.kind === "literal") {
1064
+ const val = typeRef.literalValue;
1065
+ if (typeof val === "string") return `String${index}`;
1066
+ if (typeof val === "number") return `Number${index}`;
1067
+ if (typeof val === "boolean") return `Bool${index}`;
1068
+ }
1069
+ return `Variant${index}`;
274
1070
  }
275
1071
  generateJSONTag(prop) {
276
1072
  if (prop.required) {
@@ -281,128 +1077,53 @@ var GoTypeGenerator = class {
281
1077
  generateTypedHandlers(contract) {
282
1078
  this.w.comment("Typed handler types for each endpoint").n();
283
1079
  for (const endpoint of contract.endpoints) {
284
- const handlerName = toMethodName(endpoint.fullName) + "Handler";
285
- const inputType = toPascalCase2(endpoint.input.name);
286
- const outputType = toPascalCase2(endpoint.output.name);
287
- this.w.comment(`Handler type for ${endpoint.fullName}`).type(handlerName, `func(ctx *Context, input ${inputType}) (${outputType}, error)`).n();
288
- }
289
- }
290
- };
291
-
292
- // src/server-generator.ts
293
- import { toPascalCase as toPascalCase3 } from "@xrpckit/codegen";
294
- function toMethodName2(fullName) {
295
- return fullName.split(".").map((part) => toPascalCase3(part)).join("");
296
- }
297
- function toFieldName(fullName) {
298
- return fullName.split(".").map((part, i) => i === 0 ? part : toPascalCase3(part)).join("");
299
- }
300
- var GoServerGenerator = class {
301
- w;
302
- packageName;
303
- constructor(packageName = "server") {
304
- this.w = new GoBuilder();
305
- this.packageName = packageName;
306
- }
307
- generateServer(contract) {
308
- const w = this.w.reset();
309
- w.package(this.packageName).import("encoding/json", "net/http", "fmt");
310
- w.struct("Router", (b) => {
311
- b.l("middleware []MiddlewareFunc");
312
- for (const endpoint of contract.endpoints) {
313
- const fieldName = toFieldName(endpoint.fullName);
314
- const handlerType = toMethodName2(endpoint.fullName) + "Handler";
315
- b.l(`${fieldName} ${handlerType}`);
316
- }
317
- });
318
- w.func("NewRouter() *Router", (b) => {
319
- b.l("return &Router{").i().l("middleware: make([]MiddlewareFunc, 0),").u().l("}");
320
- });
321
- for (const endpoint of contract.endpoints) {
322
- const methodName = toMethodName2(endpoint.fullName);
323
- const fieldName = toFieldName(endpoint.fullName);
324
- const handlerType = methodName + "Handler";
325
- w.method("r *Router", methodName, `handler ${handlerType}`, "*Router", (b) => {
326
- b.l(`r.${fieldName} = handler`).return("r");
327
- });
1080
+ const handlerName = `${toMethodName2(endpoint.fullName)}Handler`;
1081
+ const inputType = toPascalCase5(endpoint.input.name);
1082
+ const outputType = toPascalCase5(endpoint.output.name);
1083
+ this.w.comment(`Handler type for ${endpoint.fullName}`).type(
1084
+ handlerName,
1085
+ `func(ctx *Context, input ${inputType}) (${outputType}, error)`
1086
+ ).n();
328
1087
  }
329
- w.method("r *Router", "Use", "middleware MiddlewareFunc", "*Router", (b) => {
330
- b.l("r.middleware = append(r.middleware, middleware)").return("r");
331
- });
332
- this.generateServeHTTP(contract.endpoints, w);
333
- return w.toString();
334
- }
335
- generateServeHTTP(endpoints, w) {
336
- w.method("r *Router", "ServeHTTP", "w http.ResponseWriter, req *http.Request", "", (b) => {
337
- b.if("req.Method != http.MethodPost", (b2) => {
338
- b2.l('http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)').return();
339
- }).n();
340
- b.var("request", "struct {").i().l('Method string `json:"method"`').l('Params json.RawMessage `json:"params"`').u().l("}").n();
341
- b.if("err := json.NewDecoder(req.Body).Decode(&request); err != nil", (b2) => {
342
- b2.l('http.Error(w, fmt.Sprintf("Invalid request: %v", err), http.StatusBadRequest)').return();
343
- }).n();
344
- b.decl("ctx", "&Context{").i().l("Request: req,").l("ResponseWriter: w,").l("Data: make(map[string]interface{}),").u().l("}").n();
345
- b.comment("Execute middleware chain").l("for _, middleware := range r.middleware {").i().decl("result", "middleware(ctx)").if("result.Error != nil", (b2) => {
346
- b2.l('http.Error(w, fmt.Sprintf("Middleware error: %v", result.Error), http.StatusInternalServerError)').return();
347
- }).if("result.Response != nil", (b2) => {
348
- b2.comment("Middleware short-circuited with response").return();
349
- }).l("ctx = result.Context").u().l("}").n();
350
- const cases = endpoints.map((endpoint) => ({
351
- value: `"${endpoint.fullName}"`,
352
- fn: (b2) => {
353
- const fieldName = toFieldName(endpoint.fullName);
354
- b2.if(`r.${fieldName} == nil`, (b3) => {
355
- b3.l('http.Error(w, "Handler not registered", http.StatusNotFound)').return();
356
- }).n();
357
- const inputTypeName = toPascalCase3(endpoint.input.name);
358
- b2.var("input", inputTypeName);
359
- b2.if("err := json.Unmarshal(request.Params, &input); err != nil", (b3) => {
360
- b3.l('http.Error(w, fmt.Sprintf("Invalid params: %v", err), http.StatusBadRequest)').return();
361
- }).n();
362
- const validationFuncName = `Validate${inputTypeName}`;
363
- b2.if(`err := ${validationFuncName}(input); err != nil`, (b3) => {
364
- b3.l('w.Header().Set("Content-Type", "application/json")').l("w.WriteHeader(http.StatusBadRequest)");
365
- b3.l("if validationErrs, ok := err.(ValidationErrors); ok {").i().l("json.NewEncoder(w).Encode(map[string]interface{}{").i().l('"error": "Validation failed",').l('"errors": validationErrs,').u().l("})").u().l("} else {").i().l("json.NewEncoder(w).Encode(map[string]interface{}{").i().l('"error": err.Error(),').u().l("})").u().l("}");
366
- b3.return();
367
- }).n();
368
- b2.decl("result, err", `r.${fieldName}(ctx, input)`);
369
- b2.ifErr((b3) => {
370
- b3.l('w.Header().Set("Content-Type", "application/json")').l('json.NewEncoder(w).Encode(map[string]interface{}{"error": err.Error()})').return();
371
- }).n();
372
- b2.l('w.Header().Set("Content-Type", "application/json")').l('json.NewEncoder(w).Encode(map[string]interface{}{"result": result})').return();
373
- }
374
- }));
375
- w.switch("request.Method", cases, (b2) => {
376
- b2.l('http.Error(w, "Method not found", http.StatusNotFound)').return();
377
- });
378
- });
379
1088
  }
380
1089
  };
381
1090
 
382
1091
  // src/validation-generator.ts
383
- import { toPascalCase as toPascalCase4 } from "@xrpckit/codegen";
1092
+ import {
1093
+ toPascalCase as toPascalCase6
1094
+ } from "@xrpckit/sdk";
384
1095
  var GoValidationGenerator = class {
385
1096
  w;
386
1097
  packageName;
1098
+ generatedValidations = /* @__PURE__ */ new Set();
387
1099
  constructor(packageName = "server") {
388
1100
  this.w = new GoBuilder();
389
1101
  this.packageName = packageName;
390
1102
  }
391
- generateValidation(contract) {
1103
+ /**
1104
+ * Generate Go validation functions from a contract definition.
1105
+ * @param contract - The contract definition (should have names assigned to inline types)
1106
+ * @param collectedTypes - Optional pre-collected nested types from GoTypeCollector
1107
+ */
1108
+ generateValidation(contract, collectedTypes) {
392
1109
  const w = this.w.reset();
1110
+ this.generatedValidations.clear();
393
1111
  const imports = /* @__PURE__ */ new Set(["fmt", "strings"]);
394
1112
  const needsRegex = this.hasValidationRule(
395
1113
  contract,
396
- (rules) => rules.uuid || rules.regex && !rules.email
1114
+ collectedTypes,
1115
+ (rules) => !!(rules.uuid || rules.regex && !rules.email)
397
1116
  // regex but not email (email uses mail)
398
1117
  );
399
1118
  const needsMail = this.hasValidationRule(
400
1119
  contract,
401
- (rules) => rules.email
1120
+ collectedTypes,
1121
+ (rules) => !!rules.email
402
1122
  );
403
1123
  const needsURL = this.hasValidationRule(
404
1124
  contract,
405
- (rules) => rules.url
1125
+ collectedTypes,
1126
+ (rules) => !!rules.url
406
1127
  );
407
1128
  if (needsRegex) imports.add("regexp");
408
1129
  if (needsMail) imports.add("net/mail");
@@ -416,7 +1137,7 @@ var GoValidationGenerator = class {
416
1137
  if (type.kind === "object" && type.properties) {
417
1138
  this.generateTypeValidation(type, w);
418
1139
  } else if (type.kind === "array" && type.elementType?.kind === "object" && type.elementType.properties) {
419
- const elementTypeName = toPascalCase4(type.name) + "Item";
1140
+ const elementTypeName = type.elementType.name ? toPascalCase6(type.elementType.name) : `${toPascalCase6(type.name)}Item`;
420
1141
  const elementType = {
421
1142
  name: elementTypeName,
422
1143
  kind: "object",
@@ -425,6 +1146,18 @@ var GoValidationGenerator = class {
425
1146
  this.generateTypeValidation(elementType, w);
426
1147
  }
427
1148
  }
1149
+ if (collectedTypes) {
1150
+ for (const collected of collectedTypes) {
1151
+ if (collected.typeRef.kind === "object" && collected.typeRef.properties) {
1152
+ const typeDefinition = {
1153
+ name: collected.name,
1154
+ kind: "object",
1155
+ properties: collected.typeRef.properties
1156
+ };
1157
+ this.generateTypeValidation(typeDefinition, w);
1158
+ }
1159
+ }
1160
+ }
428
1161
  this.generateHelperFunctions(w);
429
1162
  return w.toString();
430
1163
  }
@@ -443,13 +1176,17 @@ var GoValidationGenerator = class {
443
1176
  }).n();
444
1177
  }
445
1178
  generateTypeValidation(type, w) {
446
- const typeName = toPascalCase4(type.name);
1179
+ const typeName = toPascalCase6(type.name);
447
1180
  const funcName = `Validate${typeName}`;
1181
+ if (this.generatedValidations.has(funcName)) {
1182
+ return;
1183
+ }
1184
+ this.generatedValidations.add(funcName);
448
1185
  w.func(`${funcName}(input ${typeName}) error`, (b) => {
449
1186
  b.var("errs", "ValidationErrors");
450
1187
  if (type.properties) {
451
1188
  for (const prop of type.properties) {
452
- this.generatePropertyValidation(prop, "input", type, b);
1189
+ this.generatePropertyValidation(prop, "input", b);
453
1190
  }
454
1191
  }
455
1192
  b.if("len(errs) > 0", (b2) => {
@@ -458,97 +1195,160 @@ var GoValidationGenerator = class {
458
1195
  b.return("nil");
459
1196
  }).n();
460
1197
  }
461
- generatePropertyValidation(prop, prefix, parentType, w) {
462
- const fieldName = toPascalCase4(prop.name);
1198
+ generatePropertyValidation(prop, prefix, w) {
1199
+ const fieldName = toPascalCase6(prop.name);
463
1200
  const fieldPath = `${prefix}.${fieldName}`;
464
1201
  const fieldPathStr = prop.name;
465
- const actualType = this.getActualType(prop.type);
1202
+ const unwrappedType = this.unwrapOptionalNullable(prop.type);
1203
+ const isPointerType = this.isPointerType(prop.type);
1204
+ if (isPointerType) {
1205
+ w.comment(`Validate ${prop.name} when present`);
1206
+ w.if(`${fieldPath} != nil`, (b) => {
1207
+ this.generatePropertyValidationForValue(
1208
+ prop,
1209
+ `*${fieldPath}`,
1210
+ fieldPathStr,
1211
+ unwrappedType,
1212
+ b,
1213
+ false
1214
+ );
1215
+ });
1216
+ return;
1217
+ }
1218
+ this.generatePropertyValidationForValue(
1219
+ prop,
1220
+ fieldPath,
1221
+ fieldPathStr,
1222
+ unwrappedType,
1223
+ w,
1224
+ prop.required
1225
+ );
1226
+ }
1227
+ generatePropertyValidationForValue(prop, valuePath, fieldPathStr, typeRef, w, isRequired) {
1228
+ const actualType = this.getActualType(typeRef);
466
1229
  const isString = actualType === "string";
467
1230
  const isNumber = actualType === "number";
468
- const isArray = prop.type.kind === "array";
469
- if (prop.required) {
1231
+ const isArray = typeRef.kind === "array";
1232
+ const enumValues = this.getEnumValues(typeRef);
1233
+ const isEnum = enumValues !== null;
1234
+ if (isRequired) {
470
1235
  w.comment(`Validate ${prop.name}`);
471
- if (isString) {
472
- w.if(`${fieldPath} == ""`, (b) => {
473
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "is required",`).u().l(`})`);
1236
+ if (isEnum || isString) {
1237
+ w.if(`${valuePath} == ""`, (b) => {
1238
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "is required",`).u().l("})");
474
1239
  });
475
1240
  } else if (isNumber) {
476
1241
  } else if (isArray) {
477
- w.if(`${fieldPath} == nil`, (b) => {
478
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "is required",`).u().l(`})`);
1242
+ w.if(`${valuePath} == nil`, (b) => {
1243
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "is required",`).u().l("})");
479
1244
  });
480
1245
  }
481
1246
  }
1247
+ if (isEnum && enumValues) {
1248
+ const enumValuesStr = enumValues.join(", ");
1249
+ const enumConditions = enumValues.map((v) => `${valuePath} != "${v}"`).join(" && ");
1250
+ w.if(`${valuePath} != "" && ${enumConditions}`, (b) => {
1251
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be one of: ${enumValuesStr}",`).u().l("})");
1252
+ });
1253
+ }
482
1254
  const validationRules = prop.validation || prop.type.validation;
483
1255
  if (validationRules) {
484
- if (!prop.required) {
1256
+ if (!isRequired) {
485
1257
  if (isString) {
486
- w.if(`${fieldPath} != ""`, (b) => {
1258
+ w.if(`${valuePath} != ""`, (b) => {
487
1259
  const validationTypeRef = {
488
1260
  kind: "primitive",
489
1261
  baseType: actualType
490
1262
  };
491
- this.generateValidationRules(validationRules, fieldPath, fieldPathStr, validationTypeRef, b, false);
1263
+ this.generateValidationRules(
1264
+ validationRules,
1265
+ valuePath,
1266
+ fieldPathStr,
1267
+ validationTypeRef,
1268
+ b,
1269
+ false
1270
+ );
492
1271
  });
493
1272
  } else if (isNumber) {
494
1273
  const validationTypeRef = {
495
1274
  kind: "primitive",
496
1275
  baseType: actualType
497
1276
  };
498
- this.generateValidationRules(validationRules, fieldPath, fieldPathStr, validationTypeRef, w, false);
1277
+ this.generateValidationRules(
1278
+ validationRules,
1279
+ valuePath,
1280
+ fieldPathStr,
1281
+ validationTypeRef,
1282
+ w,
1283
+ false
1284
+ );
499
1285
  } else if (isArray) {
500
- w.if(`${fieldPath} != nil`, (b) => {
501
- this.generateValidationRules(validationRules, fieldPath, fieldPathStr, prop.type, b, false);
1286
+ w.if(`${valuePath} != nil`, (b) => {
1287
+ this.generateValidationRules(
1288
+ validationRules,
1289
+ valuePath,
1290
+ fieldPathStr,
1291
+ typeRef,
1292
+ b,
1293
+ false
1294
+ );
502
1295
  });
503
1296
  } else {
504
- this.generateValidationRules(validationRules, fieldPath, fieldPathStr, prop.type, w, false);
1297
+ this.generateValidationRules(
1298
+ validationRules,
1299
+ valuePath,
1300
+ fieldPathStr,
1301
+ typeRef,
1302
+ w,
1303
+ false
1304
+ );
505
1305
  }
1306
+ } else if (isArray) {
1307
+ this.generateValidationRules(
1308
+ validationRules,
1309
+ valuePath,
1310
+ fieldPathStr,
1311
+ typeRef,
1312
+ w,
1313
+ true
1314
+ );
506
1315
  } else {
507
- if (isArray) {
508
- this.generateValidationRules(validationRules, fieldPath, fieldPathStr, prop.type, w, prop.required);
509
- } else {
510
- const validationTypeRef = {
511
- kind: "primitive",
512
- baseType: actualType
513
- };
514
- this.generateValidationRules(validationRules, fieldPath, fieldPathStr, validationTypeRef, w, prop.required);
515
- }
1316
+ const validationTypeRef = {
1317
+ kind: "primitive",
1318
+ baseType: actualType
1319
+ };
1320
+ this.generateValidationRules(
1321
+ validationRules,
1322
+ valuePath,
1323
+ fieldPathStr,
1324
+ validationTypeRef,
1325
+ w,
1326
+ true
1327
+ );
516
1328
  }
517
1329
  }
518
- if (prop.type.kind === "object" && prop.type.name) {
519
- const nestedTypeName = toPascalCase4(prop.type.name);
1330
+ if (typeRef.kind === "object" && typeRef.name) {
1331
+ const nestedTypeName = toPascalCase6(typeRef.name);
520
1332
  const nestedFuncName = `Validate${nestedTypeName}`;
521
- w.if(`${fieldPath} != nil`, (b) => {
522
- b.l(`if err := ${nestedFuncName}(${fieldPath}); err != nil {`).i().l(`if nestedErrs, ok := err.(ValidationErrors); ok {`).i().l(`errs = append(errs, nestedErrs...)`).u().l(`} else {`).i().l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: err.Error(),`).u().l(`})`).u().l(`}`).u().l(`}`);
523
- });
524
- } else if (prop.type.kind === "object" && prop.type.properties) {
525
- w.if(`${fieldPath} != nil`, (b) => {
526
- for (const nestedProp of prop.type.properties || []) {
527
- const inlineType = {
528
- name: "",
529
- kind: "object",
530
- properties: prop.type.properties
531
- };
532
- this.generatePropertyValidation(nestedProp, fieldPath, inlineType, b);
533
- }
534
- });
1333
+ w.l(`if err := ${nestedFuncName}(${valuePath}); err != nil {`).i().l("if nestedErrs, ok := err.(ValidationErrors); ok {").i().l("errs = append(errs, nestedErrs...)").u().l("} else {").i().l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l("Message: err.Error(),").u().l("})").u().l("}").u().l("}");
535
1334
  }
536
- if (prop.type.kind === "array" && prop.type.elementType) {
537
- if (prop.type.elementType.kind === "object" && prop.type.elementType.name) {
538
- const elementTypeName = toPascalCase4(prop.type.elementType.name);
1335
+ if (typeRef.kind === "array" && typeRef.elementType) {
1336
+ const elementTypeRef = this.unwrapOptionalNullable(typeRef.elementType);
1337
+ if (elementTypeRef.kind === "object" && elementTypeRef.name) {
1338
+ const elementTypeName = toPascalCase6(elementTypeRef.name);
539
1339
  const elementFuncName = `Validate${elementTypeName}`;
540
- w.l(`for i, item := range ${fieldPath} {`).i().l(`if err := ${elementFuncName}(item); err != nil {`).i().l(`if nestedErrs, ok := err.(ValidationErrors); ok {`).i().l(`for _, nestedErr := range nestedErrs {`).i().l(`errs = append(errs, &ValidationError{`).i().l(`Field: fmt.Sprintf("${fieldPathStr}[%%d].%%s", i, nestedErr.Field),`).l(`Message: nestedErr.Message,`).u().l(`})`).u().l(`}`).u().l(`}`).u().l(`}`).u().l(`}`);
541
- } else if (prop.type.elementType.kind === "object" && prop.type.elementType.properties) {
542
- w.l(`for i, item := range ${fieldPath} {`).i();
543
- const inlineType = {
544
- name: "",
545
- kind: "object",
546
- properties: prop.type.elementType.properties
547
- };
548
- for (const nestedProp of prop.type.elementType.properties || []) {
549
- this.generatePropertyValidation(nestedProp, "item", inlineType, w);
1340
+ const isElementPointer = this.isPointerType(typeRef.elementType);
1341
+ w.l(`for i, item := range ${valuePath} {`).i();
1342
+ if (isElementPointer) {
1343
+ w.l("if item == nil { continue }");
1344
+ w.l(`if err := ${elementFuncName}(*item); err != nil {`);
1345
+ } else {
1346
+ w.l(`if err := ${elementFuncName}(item); err != nil {`);
550
1347
  }
551
- w.u().l(`}`);
1348
+ w.i().l("if nestedErrs, ok := err.(ValidationErrors); ok {").i().l("for _, nestedErr := range nestedErrs {").i().l("errs = append(errs, &ValidationError{").i().l(
1349
+ `Field: fmt.Sprintf("${fieldPathStr}[%%d].%%s", i, nestedErr.Field),`
1350
+ ).l("Message: nestedErr.Message,").u().l("})").u().l("}").u().l("}").u().l("}").u().l("}");
1351
+ w.u().l("}");
552
1352
  }
553
1353
  }
554
1354
  }
@@ -557,169 +1357,570 @@ var GoValidationGenerator = class {
557
1357
  if (rules.minLength !== void 0) {
558
1358
  const minLengthCondition = isRequired ? `${fieldPath} != "" && len(${fieldPath}) < ${rules.minLength}` : `len(${fieldPath}) < ${rules.minLength}`;
559
1359
  w.if(minLengthCondition, (b) => {
560
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: fmt.Sprintf("must be at least %d character(s)", ${rules.minLength}),`).u().l(`})`);
1360
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(
1361
+ `Message: fmt.Sprintf("must be at least %d character(s)", ${rules.minLength}),`
1362
+ ).u().l("})");
561
1363
  });
562
1364
  }
563
1365
  if (rules.maxLength !== void 0) {
564
1366
  w.if(`len(${fieldPath}) > ${rules.maxLength}`, (b) => {
565
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: fmt.Sprintf("must be at most %d character(s)", ${rules.maxLength}),`).u().l(`})`);
1367
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(
1368
+ `Message: fmt.Sprintf("must be at most %d character(s)", ${rules.maxLength}),`
1369
+ ).u().l("})");
566
1370
  });
567
1371
  }
568
1372
  if (rules.email) {
569
1373
  if (isRequired) {
570
1374
  w.if(`${fieldPath} != ""`, (b) => {
571
- b.l(`if _, err := mail.ParseAddress(${fieldPath}); err != nil {`).i().l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid email address",`).u().l(`})`).u().l(`}`);
1375
+ b.l(`if _, err := mail.ParseAddress(${fieldPath}); err != nil {`).i().l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid email address",`).u().l("})").u().l("}");
572
1376
  });
573
1377
  } else {
574
- w.l(`if _, err := mail.ParseAddress(${fieldPath}); err != nil {`).i().l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid email address",`).u().l(`})`).u().l(`}`);
1378
+ w.l(`if _, err := mail.ParseAddress(${fieldPath}); err != nil {`).i().l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid email address",`).u().l("})").u().l("}");
575
1379
  }
576
1380
  }
577
1381
  if (rules.url) {
578
1382
  if (isRequired) {
579
1383
  w.if(`${fieldPath} != ""`, (b) => {
580
- b.l(`if u, err := url.Parse(${fieldPath}); err != nil || u.Scheme == "" || u.Host == "" {`).i().l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid URL",`).u().l(`})`).u().l(`}`);
1384
+ b.l(
1385
+ `if u, err := url.Parse(${fieldPath}); err != nil || u.Scheme == "" || u.Host == "" {`
1386
+ ).i().l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid URL",`).u().l("})").u().l("}");
581
1387
  });
582
1388
  } else {
583
- w.l(`if u, err := url.Parse(${fieldPath}); err != nil || u.Scheme == "" || u.Host == "" {`).i().l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid URL",`).u().l(`})`).u().l(`}`);
1389
+ w.l(
1390
+ `if u, err := url.Parse(${fieldPath}); err != nil || u.Scheme == "" || u.Host == "" {`
1391
+ ).i().l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid URL",`).u().l("})").u().l("}");
584
1392
  }
585
1393
  }
586
1394
  if (rules.uuid) {
587
1395
  if (isRequired) {
588
1396
  w.if(`${fieldPath} != ""`, (b) => {
589
- b.l(`matched, _ := regexp.MatchString("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", ${fieldPath})`).n().l(`if !matched {`).i().l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid UUID",`).u().l(`})`).u().l(`}`);
1397
+ b.l(
1398
+ `matched, _ := regexp.MatchString("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", ${fieldPath})`
1399
+ ).n().l("if !matched {").i().l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid UUID",`).u().l("})").u().l("}");
590
1400
  });
591
1401
  } else {
592
- w.l(`matched, _ := regexp.MatchString("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", ${fieldPath})`).n().l(`if !matched {`).i().l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid UUID",`).u().l(`})`).u().l(`}`);
1402
+ w.l(
1403
+ `matched, _ := regexp.MatchString("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", ${fieldPath})`
1404
+ ).n().l("if !matched {").i().l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be a valid UUID",`).u().l("})").u().l("}");
593
1405
  }
594
1406
  }
595
1407
  if (rules.regex && !rules.email && !rules.url && !rules.uuid) {
596
1408
  if (isRequired) {
597
1409
  w.if(`${fieldPath} != ""`, (b) => {
598
- const escapedRegex = rules.regex.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
599
- b.l(`matched, _ := regexp.MatchString("${escapedRegex}", ${fieldPath})`).n().l(`if !matched {`).i().l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must match the required pattern",`).u().l(`})`).u().l(`}`);
1410
+ const escapedRegex = rules.regex?.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1411
+ b.l(
1412
+ `matched, _ := regexp.MatchString("${escapedRegex}", ${fieldPath})`
1413
+ ).n().l("if !matched {").i().l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must match the required pattern",`).u().l("})").u().l("}");
600
1414
  });
601
1415
  } else {
602
- const escapedRegex = rules.regex.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
603
- w.l(`matched, _ := regexp.MatchString("${escapedRegex}", ${fieldPath})`).n().l(`if !matched {`).i().l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must match the required pattern",`).u().l(`})`).u().l(`}`);
1416
+ const escapedRegex = rules.regex?.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1417
+ w.l(
1418
+ `matched, _ := regexp.MatchString("${escapedRegex}", ${fieldPath})`
1419
+ ).n().l("if !matched {").i().l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must match the required pattern",`).u().l("})").u().l("}");
604
1420
  }
605
1421
  }
606
1422
  } else if (typeRef.baseType === "number") {
607
1423
  if (rules.min !== void 0) {
608
1424
  w.if(`${fieldPath} < ${rules.min}`, (b) => {
609
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: fmt.Sprintf("must be at least %v", ${rules.min}),`).u().l(`})`);
1425
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: fmt.Sprintf("must be at least %v", ${rules.min}),`).u().l("})");
610
1426
  });
611
1427
  }
612
1428
  if (rules.max !== void 0) {
613
1429
  w.if(`${fieldPath} > ${rules.max}`, (b) => {
614
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: fmt.Sprintf("must be at most %v", ${rules.max}),`).u().l(`})`);
1430
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: fmt.Sprintf("must be at most %v", ${rules.max}),`).u().l("})");
615
1431
  });
616
1432
  }
617
1433
  if (rules.int) {
618
1434
  w.if(`float64(${fieldPath}) != float64(int64(${fieldPath}))`, (b) => {
619
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be an integer",`).u().l(`})`);
1435
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be an integer",`).u().l("})");
620
1436
  });
621
1437
  }
622
1438
  if (rules.positive) {
623
1439
  w.if(`${fieldPath} <= 0`, (b) => {
624
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be positive",`).u().l(`})`);
1440
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be positive",`).u().l("})");
625
1441
  });
626
1442
  }
627
1443
  if (rules.negative) {
628
1444
  w.if(`${fieldPath} >= 0`, (b) => {
629
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be negative",`).u().l(`})`);
1445
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be negative",`).u().l("})");
630
1446
  });
631
1447
  }
632
1448
  } else if (typeRef.kind === "array") {
633
1449
  const arrayCheckCondition = isRequired ? `${fieldPath} != nil` : `${fieldPath} != nil`;
634
1450
  if (rules.minItems !== void 0) {
635
- w.if(`${arrayCheckCondition} && len(${fieldPath}) < ${rules.minItems}`, (b) => {
636
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: fmt.Sprintf("must have at least %d item(s)", ${rules.minItems}),`).u().l(`})`);
637
- });
1451
+ w.if(
1452
+ `${arrayCheckCondition} && len(${fieldPath}) < ${rules.minItems}`,
1453
+ (b) => {
1454
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(
1455
+ `Message: fmt.Sprintf("must have at least %d item(s)", ${rules.minItems}),`
1456
+ ).u().l("})");
1457
+ }
1458
+ );
638
1459
  }
639
1460
  if (rules.maxItems !== void 0) {
640
- w.if(`${arrayCheckCondition} && len(${fieldPath}) > ${rules.maxItems}`, (b) => {
641
- b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: fmt.Sprintf("must have at most %d item(s)", ${rules.maxItems}),`).u().l(`})`);
642
- });
1461
+ w.if(
1462
+ `${arrayCheckCondition} && len(${fieldPath}) > ${rules.maxItems}`,
1463
+ (b) => {
1464
+ b.l("errs = append(errs, &ValidationError{").i().l(`Field: "${fieldPathStr}",`).l(
1465
+ `Message: fmt.Sprintf("must have at most %d item(s)", ${rules.maxItems}),`
1466
+ ).u().l("})");
1467
+ }
1468
+ );
643
1469
  }
644
1470
  }
645
1471
  }
646
- hasValidationRule(contract, check) {
1472
+ hasValidationRule(contract, collectedTypes, check) {
647
1473
  for (const type of contract.types) {
648
- if (type.properties) {
649
- for (const prop of type.properties) {
650
- if (prop.validation && check(prop.validation)) {
651
- return true;
652
- }
653
- if (prop.type.properties) {
654
- for (const nestedProp of prop.type.properties) {
655
- if (nestedProp.validation && check(nestedProp.validation)) {
656
- return true;
657
- }
658
- }
659
- }
660
- if (prop.type.elementType?.properties) {
661
- for (const elemProp of prop.type.elementType.properties) {
662
- if (elemProp.validation && check(elemProp.validation)) {
663
- return true;
664
- }
665
- }
666
- }
667
- }
1474
+ if (this.checkTypeForValidationRule(type.properties, check)) {
1475
+ return true;
668
1476
  }
669
1477
  if (type.elementType?.validation && check(type.elementType.validation)) {
670
1478
  return true;
671
1479
  }
1480
+ if (type.elementType?.properties && this.checkTypeForValidationRule(type.elementType.properties, check)) {
1481
+ return true;
1482
+ }
1483
+ }
1484
+ if (collectedTypes) {
1485
+ for (const collected of collectedTypes) {
1486
+ if (collected.typeRef.properties && this.checkTypeForValidationRule(collected.typeRef.properties, check)) {
1487
+ return true;
1488
+ }
1489
+ }
672
1490
  }
673
1491
  return false;
674
1492
  }
675
- generateHelperFunctions(w) {
1493
+ checkTypeForValidationRule(properties, check) {
1494
+ if (!properties) return false;
1495
+ for (const prop of properties) {
1496
+ if (prop.validation && check(prop.validation)) {
1497
+ return true;
1498
+ }
1499
+ if (this.checkTypeRefForValidationRule(prop.type, check)) {
1500
+ return true;
1501
+ }
1502
+ }
1503
+ return false;
676
1504
  }
677
- getActualType(typeRef) {
1505
+ checkTypeRefForValidationRule(typeRef, check) {
1506
+ if (typeRef.validation && check(typeRef.validation)) {
1507
+ return true;
1508
+ }
1509
+ if (typeRef.properties && this.checkTypeForValidationRule(typeRef.properties, check)) {
1510
+ return true;
1511
+ }
1512
+ if (typeRef.elementType) {
1513
+ if (typeRef.elementType.validation && check(typeRef.elementType.validation)) {
1514
+ return true;
1515
+ }
1516
+ if (typeRef.elementType.properties && this.checkTypeForValidationRule(typeRef.elementType.properties, check)) {
1517
+ return true;
1518
+ }
1519
+ }
1520
+ if ((typeRef.kind === "optional" || typeRef.kind === "nullable") && typeof typeRef.baseType === "object") {
1521
+ if (this.checkTypeRefForValidationRule(typeRef.baseType, check)) {
1522
+ return true;
1523
+ }
1524
+ }
1525
+ return false;
1526
+ }
1527
+ generateHelperFunctions(_w) {
1528
+ }
1529
+ unwrapOptionalNullable(typeRef) {
678
1530
  if (typeRef.kind === "optional" || typeRef.kind === "nullable") {
679
1531
  if (typeRef.baseType) {
680
1532
  if (typeof typeRef.baseType === "string") {
681
- return typeRef.baseType;
1533
+ return { kind: "primitive", baseType: typeRef.baseType };
682
1534
  }
683
- return this.getActualType(typeRef.baseType);
1535
+ return this.unwrapOptionalNullable(typeRef.baseType);
684
1536
  }
685
1537
  }
686
- if (typeof typeRef.baseType === "string") {
687
- return typeRef.baseType;
1538
+ if (typeRef.kind === "union" && typeRef.unionTypes) {
1539
+ const nonNullVariants = typeRef.unionTypes.filter(
1540
+ (variant) => !(variant.kind === "literal" && variant.literalValue === null)
1541
+ );
1542
+ if (nonNullVariants.length === 1) {
1543
+ return this.unwrapOptionalNullable(nonNullVariants[0]);
1544
+ }
688
1545
  }
689
- if (typeRef.baseType) {
690
- return this.getActualType(typeRef.baseType);
1546
+ return typeRef;
1547
+ }
1548
+ getActualType(typeRef) {
1549
+ const unwrapped = this.unwrapOptionalNullable(typeRef);
1550
+ if (unwrapped.kind === "primitive" && typeof unwrapped.baseType === "string") {
1551
+ return unwrapped.baseType;
1552
+ }
1553
+ if (typeof unwrapped.baseType === "string") {
1554
+ return unwrapped.baseType;
1555
+ }
1556
+ if (unwrapped.baseType) {
1557
+ return this.getActualType(unwrapped.baseType);
691
1558
  }
692
1559
  return "unknown";
693
1560
  }
694
- toPascalCase(str) {
695
- return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
1561
+ getEnumValues(typeRef) {
1562
+ const unwrapped = this.unwrapOptionalNullable(typeRef);
1563
+ if (unwrapped.kind === "enum" && unwrapped.enumValues) {
1564
+ return unwrapped.enumValues.filter(
1565
+ (v) => typeof v === "string"
1566
+ );
1567
+ }
1568
+ return null;
1569
+ }
1570
+ isPointerType(typeRef) {
1571
+ if (typeRef.kind === "nullable") {
1572
+ return true;
1573
+ }
1574
+ if (typeRef.kind === "optional") {
1575
+ if (typeRef.baseType && typeof typeRef.baseType !== "string") {
1576
+ return this.isPointerType(typeRef.baseType);
1577
+ }
1578
+ }
1579
+ if (typeRef.kind === "union" && typeRef.unionTypes) {
1580
+ const nonNullVariants = typeRef.unionTypes.filter(
1581
+ (variant) => !(variant.kind === "literal" && variant.literalValue === null)
1582
+ );
1583
+ if (nonNullVariants.length === 1) {
1584
+ return true;
1585
+ }
1586
+ }
1587
+ return false;
696
1588
  }
697
1589
  };
698
1590
 
699
1591
  // src/generator.ts
700
- var GoCodeGenerator = class extends BaseCodeGenerator {
701
- typeGenerator;
702
- serverGenerator;
703
- validationGenerator;
704
- constructor(config) {
705
- super(config);
706
- const packageName = config.packageName || "server";
707
- this.typeGenerator = new GoTypeGenerator(packageName);
708
- this.serverGenerator = new GoServerGenerator(packageName);
709
- this.validationGenerator = new GoValidationGenerator(packageName);
710
- }
711
- generate(contract) {
1592
+ var support = {
1593
+ supportedTypes: [...TYPE_KINDS],
1594
+ supportedValidations: [...VALIDATION_KINDS],
1595
+ notes: [
1596
+ "Generates idiomatic Go code using standard library only",
1597
+ "Uses net/http for HTTP handling",
1598
+ "Uses encoding/json for JSON marshaling",
1599
+ "Validation uses net/mail for email, net/url for URLs, regexp for patterns"
1600
+ ]
1601
+ };
1602
+ function getPackageName(options) {
1603
+ if (options && typeof options.packageName === "string" && options.packageName) {
1604
+ return options.packageName;
1605
+ }
1606
+ return "server";
1607
+ }
1608
+ function isNullableType(typeRef) {
1609
+ if (typeRef.kind === "nullable") {
1610
+ return true;
1611
+ }
1612
+ if (typeRef.kind === "optional" && typeof typeRef.baseType === "object") {
1613
+ return isNullableType(typeRef.baseType);
1614
+ }
1615
+ if (typeRef.kind === "union" && typeRef.unionTypes) {
1616
+ const nonNullVariants = typeRef.unionTypes.filter(
1617
+ (variant) => !(variant.kind === "literal" && variant.literalValue === null)
1618
+ );
1619
+ return nonNullVariants.length === 1;
1620
+ }
1621
+ return false;
1622
+ }
1623
+ function collectRequiredNullableFields(contract) {
1624
+ const results = /* @__PURE__ */ new Set();
1625
+ const visited = /* @__PURE__ */ new Set();
1626
+ const collectFromProperties = (properties, parentName) => {
1627
+ for (const prop of properties) {
1628
+ const propPath = `${parentName}.${prop.name}`;
1629
+ if (prop.required && isNullableType(prop.type)) {
1630
+ results.add(propPath);
1631
+ }
1632
+ collectFromTypeRef(prop.type, `${parentName}${toPascalCase7(prop.name)}`);
1633
+ }
1634
+ };
1635
+ const collectFromTypeRef = (typeRef, contextName) => {
1636
+ if (typeRef.kind === "optional" || typeRef.kind === "nullable") {
1637
+ if (typeof typeRef.baseType === "object") {
1638
+ collectFromTypeRef(typeRef.baseType, contextName);
1639
+ }
1640
+ return;
1641
+ }
1642
+ if (typeRef.kind === "object" && typeRef.properties) {
1643
+ const typeName = typeRef.name ? toPascalCase7(typeRef.name) : contextName;
1644
+ collectFromProperties(typeRef.properties, typeName);
1645
+ return;
1646
+ }
1647
+ if (typeRef.kind === "array" && typeRef.elementType) {
1648
+ collectFromTypeRef(typeRef.elementType, `${contextName}Item`);
1649
+ return;
1650
+ }
1651
+ if (typeRef.kind === "record" && typeRef.valueType) {
1652
+ collectFromTypeRef(typeRef.valueType, `${contextName}Value`);
1653
+ return;
1654
+ }
1655
+ if (typeRef.kind === "tuple" && typeRef.tupleElements) {
1656
+ typeRef.tupleElements.forEach((elem, index) => {
1657
+ collectFromTypeRef(elem, `${contextName}V${index}`);
1658
+ });
1659
+ return;
1660
+ }
1661
+ if (typeRef.kind === "union" && typeRef.unionTypes) {
1662
+ typeRef.unionTypes.forEach((variant, index) => {
1663
+ collectFromTypeRef(variant, `${contextName}Variant${index}`);
1664
+ });
1665
+ }
1666
+ };
1667
+ const collectFromTypeDefinition = (typeDef, typeName) => {
1668
+ if (visited.has(typeName)) return;
1669
+ visited.add(typeName);
1670
+ if (typeDef.properties) {
1671
+ collectFromProperties(typeDef.properties, typeName);
1672
+ }
1673
+ if (typeDef.kind === "array" && typeDef.elementType) {
1674
+ collectFromTypeRef(typeDef.elementType, `${typeName}Item`);
1675
+ }
1676
+ };
1677
+ for (const type of contract.types) {
1678
+ collectFromTypeDefinition(type, toPascalCase7(type.name));
1679
+ }
1680
+ for (const endpoint of contract.endpoints) {
1681
+ collectFromTypeRef(endpoint.input, `${endpoint.fullName}.input`);
1682
+ collectFromTypeRef(endpoint.output, `${endpoint.fullName}.output`);
1683
+ }
1684
+ return Array.from(results);
1685
+ }
1686
+ function generateGoServer(input) {
1687
+ const { contract } = input;
1688
+ const diagnostics = validateSupport(contract, support, "go-server");
1689
+ const requiredNullableFields = collectRequiredNullableFields(contract);
1690
+ for (const field of requiredNullableFields) {
1691
+ diagnostics.push({
1692
+ severity: "warning",
1693
+ message: `Field "${field}" is required and nullable. Go cannot distinguish missing values from null, so validation only runs when the value is present.`
1694
+ });
1695
+ }
1696
+ const hasErrors = diagnostics.some((issue) => issue.severity === "error");
1697
+ if (hasErrors) {
1698
+ return { files: [], diagnostics };
1699
+ }
1700
+ const packageName = getPackageName(input.options);
1701
+ const typeCollector = new GoTypeCollector();
1702
+ const collectedTypes = typeCollector.collectTypes(contract);
1703
+ const typeGenerator = new GoTypeGenerator(packageName);
1704
+ const serverGenerator = new GoServerGenerator(packageName);
1705
+ const validationGenerator = new GoValidationGenerator(packageName);
1706
+ return {
1707
+ files: [
1708
+ {
1709
+ path: "types.go",
1710
+ content: typeGenerator.generateTypes(contract, collectedTypes)
1711
+ },
1712
+ {
1713
+ path: "router.go",
1714
+ content: serverGenerator.generateServer(contract)
1715
+ },
1716
+ {
1717
+ path: "validation.go",
1718
+ content: validationGenerator.generateValidation(
1719
+ contract,
1720
+ collectedTypes
1721
+ )
1722
+ }
1723
+ ],
1724
+ diagnostics
1725
+ };
1726
+ }
1727
+ var goTarget = {
1728
+ name: "go-server",
1729
+ generate: generateGoServer
1730
+ };
1731
+
1732
+ // src/validation-mapper.ts
1733
+ import {
1734
+ ValidationMapperBase
1735
+ } from "@xrpckit/sdk";
1736
+ var GoValidationMapper = class extends ValidationMapperBase {
1737
+ /**
1738
+ * Complete mapping of all validation kinds to Go validation code.
1739
+ * TypeScript enforces exhaustiveness at compile time.
1740
+ */
1741
+ validationMapping = {
1742
+ // String validations
1743
+ minLength: (ctx) => this.handleMinLength(ctx),
1744
+ maxLength: (ctx) => this.handleMaxLength(ctx),
1745
+ email: (ctx) => this.handleEmail(ctx),
1746
+ url: (ctx) => this.handleUrl(ctx),
1747
+ uuid: (ctx) => this.handleUuid(ctx),
1748
+ regex: (ctx) => this.handleRegex(ctx),
1749
+ // Number validations
1750
+ min: (ctx) => this.handleMin(ctx),
1751
+ max: (ctx) => this.handleMax(ctx),
1752
+ int: (ctx) => this.handleInt(ctx),
1753
+ positive: (ctx) => this.handlePositive(ctx),
1754
+ negative: (ctx) => this.handleNegative(ctx),
1755
+ // Array validations
1756
+ minItems: (ctx) => this.handleMinItems(ctx),
1757
+ maxItems: (ctx) => this.handleMaxItems(ctx)
1758
+ };
1759
+ // --- String validation handlers ---
1760
+ handleMinLength(ctx) {
1761
+ const { fieldPath, value, isRequired } = ctx;
1762
+ const condition = isRequired ? `${fieldPath} != "" && len(${fieldPath}) < ${value}` : `len(${fieldPath}) < ${value}`;
1763
+ return {
1764
+ validation: {
1765
+ condition,
1766
+ message: `fmt.Sprintf("must be at least %d character(s)", ${value})`,
1767
+ skipIfEmpty: !isRequired
1768
+ },
1769
+ imports: ["fmt"]
1770
+ };
1771
+ }
1772
+ handleMaxLength(ctx) {
1773
+ const { fieldPath, value } = ctx;
1774
+ return {
1775
+ validation: {
1776
+ condition: `len(${fieldPath}) > ${value}`,
1777
+ message: `fmt.Sprintf("must be at most %d character(s)", ${value})`
1778
+ },
1779
+ imports: ["fmt"]
1780
+ };
1781
+ }
1782
+ handleEmail(ctx) {
1783
+ const { fieldPath, isRequired } = ctx;
1784
+ const condition = `func() bool { _, err := mail.ParseAddress(${fieldPath}); return err != nil }()`;
1785
+ return {
1786
+ validation: {
1787
+ condition,
1788
+ message: `"must be a valid email address"`,
1789
+ skipIfEmpty: !isRequired
1790
+ },
1791
+ imports: ["net/mail"]
1792
+ };
1793
+ }
1794
+ handleUrl(ctx) {
1795
+ const { fieldPath, isRequired } = ctx;
1796
+ const condition = `func() bool { u, err := url.Parse(${fieldPath}); return err != nil || u.Scheme == "" || u.Host == "" }()`;
1797
+ return {
1798
+ validation: {
1799
+ condition,
1800
+ message: `"must be a valid URL"`,
1801
+ skipIfEmpty: !isRequired
1802
+ },
1803
+ imports: ["net/url"]
1804
+ };
1805
+ }
1806
+ handleUuid(ctx) {
1807
+ const { fieldPath, isRequired } = ctx;
1808
+ const uuidRegex = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$";
1809
+ const condition = `func() bool { matched, _ := regexp.MatchString("${uuidRegex}", ${fieldPath}); return !matched }()`;
1810
+ return {
1811
+ validation: {
1812
+ condition,
1813
+ message: `"must be a valid UUID"`,
1814
+ skipIfEmpty: !isRequired
1815
+ },
1816
+ imports: ["regexp"]
1817
+ };
1818
+ }
1819
+ handleRegex(ctx) {
1820
+ const { fieldPath, value, isRequired, allRules } = ctx;
1821
+ if (allRules.email || allRules.url || allRules.uuid) {
1822
+ return {
1823
+ validation: {
1824
+ condition: "false",
1825
+ // Never triggers
1826
+ message: `""`
1827
+ // No message
1828
+ }
1829
+ };
1830
+ }
1831
+ const escapedRegex = String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1832
+ const condition = `func() bool { matched, _ := regexp.MatchString("${escapedRegex}", ${fieldPath}); return !matched }()`;
1833
+ return {
1834
+ validation: {
1835
+ condition,
1836
+ message: `"must match the required pattern"`,
1837
+ skipIfEmpty: !isRequired
1838
+ },
1839
+ imports: ["regexp"]
1840
+ };
1841
+ }
1842
+ // --- Number validation handlers ---
1843
+ handleMin(ctx) {
1844
+ const { fieldPath, value } = ctx;
1845
+ return {
1846
+ validation: {
1847
+ condition: `${fieldPath} < ${value}`,
1848
+ message: `fmt.Sprintf("must be at least %v", ${value})`
1849
+ },
1850
+ imports: ["fmt"]
1851
+ };
1852
+ }
1853
+ handleMax(ctx) {
1854
+ const { fieldPath, value } = ctx;
1855
+ return {
1856
+ validation: {
1857
+ condition: `${fieldPath} > ${value}`,
1858
+ message: `fmt.Sprintf("must be at most %v", ${value})`
1859
+ },
1860
+ imports: ["fmt"]
1861
+ };
1862
+ }
1863
+ handleInt(ctx) {
1864
+ const { fieldPath } = ctx;
1865
+ return {
1866
+ validation: {
1867
+ condition: `float64(${fieldPath}) != float64(int64(${fieldPath}))`,
1868
+ message: `"must be an integer"`
1869
+ }
1870
+ };
1871
+ }
1872
+ handlePositive(ctx) {
1873
+ const { fieldPath } = ctx;
1874
+ return {
1875
+ validation: {
1876
+ condition: `${fieldPath} <= 0`,
1877
+ message: `"must be positive"`
1878
+ }
1879
+ };
1880
+ }
1881
+ handleNegative(ctx) {
1882
+ const { fieldPath } = ctx;
1883
+ return {
1884
+ validation: {
1885
+ condition: `${fieldPath} >= 0`,
1886
+ message: `"must be negative"`
1887
+ }
1888
+ };
1889
+ }
1890
+ // --- Array validation handlers ---
1891
+ handleMinItems(ctx) {
1892
+ const { fieldPath, value } = ctx;
1893
+ return {
1894
+ validation: {
1895
+ condition: `${fieldPath} != nil && len(${fieldPath}) < ${value}`,
1896
+ message: `fmt.Sprintf("must have at least %d item(s)", ${value})`
1897
+ },
1898
+ imports: ["fmt"]
1899
+ };
1900
+ }
1901
+ handleMaxItems(ctx) {
1902
+ const { fieldPath, value } = ctx;
712
1903
  return {
713
- types: this.typeGenerator.generateTypes(contract),
714
- server: this.serverGenerator.generateServer(contract),
715
- validation: this.validationGenerator.generateValidation(contract)
1904
+ validation: {
1905
+ condition: `${fieldPath} != nil && len(${fieldPath}) > ${value}`,
1906
+ message: `fmt.Sprintf("must have at most %d item(s)", ${value})`
1907
+ },
1908
+ imports: ["fmt"]
716
1909
  };
717
1910
  }
718
1911
  };
719
1912
  export {
720
1913
  GoBuilder,
721
- GoCodeGenerator,
722
1914
  GoServerGenerator,
1915
+ GoTypeCollector,
723
1916
  GoTypeGenerator,
724
- GoTypeMapper
1917
+ GoTypeMapper,
1918
+ GoValidationGenerator,
1919
+ GoValidationMapper,
1920
+ createGoBigIntPattern,
1921
+ createGoDatePattern,
1922
+ createGoEnumPattern,
1923
+ createGoTuplePattern,
1924
+ createGoUnionPattern,
1925
+ goTarget
725
1926
  };