@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.
@@ -0,0 +1,305 @@
1
+ import {
2
+ type TypeContext,
3
+ TypeMapperBase,
4
+ type TypeMapping,
5
+ type TypeReference,
6
+ type TypeResult,
7
+ toPascalCase,
8
+ } from "@xrpckit/sdk";
9
+ import { createGoTuplePattern, createGoUnionPattern } from "./patterns";
10
+
11
+ /**
12
+ * Go type mapper that converts xRPC types to Go types.
13
+ * Extends TypeMapperBase to ensure all type kinds are handled.
14
+ */
15
+ export class GoTypeMapper extends TypeMapperBase<string> {
16
+ // Registry of tuple type names for reference during validation
17
+ private tupleTypes: Map<string, TypeReference> = new Map();
18
+ // Registry of union type names
19
+ private unionTypes: Map<string, TypeReference> = new Map();
20
+
21
+ /**
22
+ * Complete mapping of all type kinds to Go types.
23
+ * TypeScript enforces exhaustiveness at compile time.
24
+ */
25
+ readonly typeMapping: TypeMapping<string> = {
26
+ object: (ctx) => this.handleObject(ctx),
27
+ array: (ctx) => this.handleArray(ctx),
28
+ primitive: (ctx) => this.handlePrimitive(ctx),
29
+ optional: (ctx) => this.handleOptional(ctx),
30
+ nullable: (ctx) => this.handleNullable(ctx),
31
+ union: (ctx) => this.handleUnion(ctx),
32
+ enum: (ctx) => this.handleEnum(ctx),
33
+ literal: (ctx) => this.handleLiteral(ctx),
34
+ record: (ctx) => this.handleRecord(ctx),
35
+ tuple: (ctx) => this.handleTuple(ctx),
36
+ date: () => this.handleDate(),
37
+ };
38
+
39
+ /**
40
+ * Map a primitive base type to Go type.
41
+ */
42
+ mapPrimitive(type: string): string {
43
+ const mapping: Record<string, string> = {
44
+ string: "string",
45
+ number: "float64",
46
+ integer: "int",
47
+ boolean: "bool",
48
+ date: "time.Time",
49
+ uuid: "string",
50
+ email: "string",
51
+ any: "interface{}",
52
+ unknown: "interface{}",
53
+ };
54
+
55
+ const result = mapping[type];
56
+ if (!result) {
57
+ console.warn("[GoTypeMapper] Unknown primitive type:", type);
58
+ return "interface{}";
59
+ }
60
+ return result;
61
+ }
62
+
63
+ /**
64
+ * Get all registered tuple types that need struct generation
65
+ */
66
+ getTupleTypes(): Map<string, TypeReference> {
67
+ return this.tupleTypes;
68
+ }
69
+
70
+ /**
71
+ * Get all registered union types that need wrapper struct generation
72
+ */
73
+ getUnionTypes(): Map<string, TypeReference> {
74
+ return this.unionTypes;
75
+ }
76
+
77
+ /**
78
+ * Reset the type registries (call between generation runs)
79
+ */
80
+ override reset(): void {
81
+ super.reset();
82
+ this.tupleTypes.clear();
83
+ this.unionTypes.clear();
84
+ }
85
+
86
+ // --- Private handler methods ---
87
+
88
+ private handleObject(ctx: TypeContext): TypeResult<string> {
89
+ const { typeRef, name } = ctx;
90
+
91
+ if (name || typeRef.name) {
92
+ return { type: toPascalCase(name || typeRef.name!) };
93
+ }
94
+
95
+ // Inline object without name - this should be rare after type collection
96
+ console.warn(
97
+ "[GoTypeMapper] Object without name - falling back to map[string]interface{}:",
98
+ typeRef,
99
+ );
100
+ return { type: "map[string]interface{}" };
101
+ }
102
+
103
+ private handleArray(ctx: TypeContext): TypeResult<string> {
104
+ const { typeRef } = ctx;
105
+ if (!typeRef.elementType) {
106
+ return { type: "[]interface{}" };
107
+ }
108
+ const element = this.mapType(typeRef.elementType);
109
+ return {
110
+ type: `[]${element.type}`,
111
+ imports: element.imports,
112
+ };
113
+ }
114
+
115
+ private handlePrimitive(ctx: TypeContext): TypeResult<string> {
116
+ const { typeRef } = ctx;
117
+ const baseType =
118
+ typeof typeRef.baseType === "string" ? typeRef.baseType : "unknown";
119
+ const goType = this.mapPrimitive(baseType);
120
+
121
+ // time.Time requires import
122
+ if (goType === "time.Time") {
123
+ return { type: goType, imports: ["time"] };
124
+ }
125
+ return { type: goType };
126
+ }
127
+
128
+ private handleOptional(ctx: TypeContext): TypeResult<string> {
129
+ const { typeRef } = ctx;
130
+
131
+ // In Go, optional is handled by validation - we use the base type
132
+ if (typeof typeRef.baseType === "object") {
133
+ return this.mapType(typeRef.baseType);
134
+ }
135
+
136
+ // String baseType - map to primitive
137
+ if (typeof typeRef.baseType === "string") {
138
+ return { type: this.mapPrimitive(typeRef.baseType) };
139
+ }
140
+
141
+ console.warn("[GoTypeMapper] Optional with unknown baseType:", typeRef);
142
+ return { type: "interface{}" };
143
+ }
144
+
145
+ private handleNullable(ctx: TypeContext): TypeResult<string> {
146
+ const { typeRef } = ctx;
147
+
148
+ // In Go, nullable maps to pointer type
149
+ if (typeof typeRef.baseType === "object") {
150
+ const base = this.mapType(typeRef.baseType);
151
+ return {
152
+ type: `*${base.type}`,
153
+ imports: base.imports,
154
+ };
155
+ }
156
+
157
+ if (typeof typeRef.baseType === "string") {
158
+ return { type: `*${this.mapPrimitive(typeRef.baseType)}` };
159
+ }
160
+
161
+ console.warn("[GoTypeMapper] Nullable with unknown baseType:", typeRef);
162
+ return { type: "*interface{}" };
163
+ }
164
+
165
+ private handleUnion(ctx: TypeContext): TypeResult<string> {
166
+ const { typeRef, name } = ctx;
167
+
168
+ // If the union has a name, use a wrapper struct
169
+ const unionName = name || typeRef.name;
170
+ if (unionName) {
171
+ const goName = toPascalCase(unionName);
172
+ this.unionTypes.set(goName, typeRef);
173
+
174
+ // Generate union utility
175
+ const variants =
176
+ typeRef.unionTypes?.map((v) => this.mapType(v).type) ?? [];
177
+ const utility = createGoUnionPattern(goName, variants);
178
+
179
+ return {
180
+ type: goName,
181
+ utilities: [utility],
182
+ imports: utility.imports,
183
+ };
184
+ }
185
+
186
+ // For anonymous unions, try to determine if all variants are the same type
187
+ if (typeRef.unionTypes && typeRef.unionTypes.length > 0) {
188
+ const variantTypes = typeRef.unionTypes.map((v) => this.mapType(v));
189
+ const allSameType = variantTypes.every(
190
+ (v) => v.type === variantTypes[0].type,
191
+ );
192
+ if (allSameType) {
193
+ return variantTypes[0];
194
+ }
195
+
196
+ // Check if it's a nullable union (e.g., string | null)
197
+ const nonNullVariants = typeRef.unionTypes.filter(
198
+ (v) => !(v.kind === "literal" && v.literalValue === null),
199
+ );
200
+ if (nonNullVariants.length === 1) {
201
+ const base = this.mapType(nonNullVariants[0]);
202
+ return {
203
+ type: `*${base.type}`,
204
+ imports: base.imports,
205
+ };
206
+ }
207
+ }
208
+
209
+ // Heterogeneous anonymous union - use interface{}
210
+ console.warn(
211
+ "[GoTypeMapper] Anonymous heterogeneous union - using interface{}:",
212
+ typeRef,
213
+ );
214
+ return { type: "interface{}" };
215
+ }
216
+
217
+ private handleEnum(_ctx: TypeContext): TypeResult<string> {
218
+ // Enums in Go are typically represented as strings
219
+ // The validation layer handles the enum value checking
220
+ return { type: "string" };
221
+ }
222
+
223
+ private handleLiteral(ctx: TypeContext): TypeResult<string> {
224
+ const { typeRef } = ctx;
225
+
226
+ if (typeRef.literalValue === null) {
227
+ // null literal - typically handled by nullable wrapper
228
+ return { type: "interface{}" };
229
+ }
230
+
231
+ if (typeof typeRef.literalValue === "string") {
232
+ return { type: "string" };
233
+ }
234
+
235
+ if (typeof typeRef.literalValue === "number") {
236
+ return { type: "float64" };
237
+ }
238
+
239
+ if (typeof typeRef.literalValue === "boolean") {
240
+ return { type: "bool" };
241
+ }
242
+
243
+ console.warn("[GoTypeMapper] Unknown literal type:", typeRef);
244
+ return { type: "interface{}" };
245
+ }
246
+
247
+ private handleRecord(ctx: TypeContext): TypeResult<string> {
248
+ const { typeRef } = ctx;
249
+ if (!typeRef.valueType) {
250
+ return { type: "map[string]interface{}" };
251
+ }
252
+ const value = this.mapType(typeRef.valueType);
253
+ return {
254
+ type: `map[string]${value.type}`,
255
+ imports: value.imports,
256
+ };
257
+ }
258
+
259
+ private handleTuple(ctx: TypeContext): TypeResult<string> {
260
+ const { typeRef, name } = ctx;
261
+
262
+ // If the tuple has a name, use a struct type
263
+ const tupleName = name || typeRef.name;
264
+ if (tupleName) {
265
+ const goName = toPascalCase(tupleName);
266
+ this.tupleTypes.set(goName, typeRef);
267
+
268
+ // Generate tuple struct utility
269
+ const elements =
270
+ typeRef.tupleElements?.map((e) => this.mapType(e).type) ?? [];
271
+ const utility = createGoTuplePattern(goName, elements);
272
+
273
+ return {
274
+ type: goName,
275
+ utilities: [utility],
276
+ imports: utility.imports,
277
+ };
278
+ }
279
+
280
+ // For anonymous tuples, try to determine if all elements are the same type
281
+ if (typeRef.tupleElements && typeRef.tupleElements.length > 0) {
282
+ const elementTypes = typeRef.tupleElements.map((e) => this.mapType(e));
283
+ const allSameType = elementTypes.every(
284
+ (e) => e.type === elementTypes[0].type,
285
+ );
286
+ if (allSameType) {
287
+ return { type: `[]${elementTypes[0].type}` };
288
+ }
289
+ }
290
+
291
+ // Heterogeneous anonymous tuple - best we can do is []interface{}
292
+ console.warn(
293
+ "[GoTypeMapper] Anonymous heterogeneous tuple - using []interface{}:",
294
+ typeRef,
295
+ );
296
+ return { type: "[]interface{}" };
297
+ }
298
+
299
+ private handleDate(): TypeResult<string> {
300
+ return {
301
+ type: "time.Time",
302
+ imports: ["time"],
303
+ };
304
+ }
305
+ }