@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.d.ts +267 -36
- package/dist/index.js +1511 -310
- package/package.json +14 -5
- package/src/generator.ts +204 -0
- package/src/go-builder.ts +148 -0
- package/src/index.ts +15 -0
- package/src/patterns.test.ts +114 -0
- package/src/patterns.ts +284 -0
- package/src/server-generator.ts +233 -0
- package/src/type-collector.ts +222 -0
- package/src/type-generator.ts +353 -0
- package/src/type-mapper.ts +305 -0
- package/src/validation-generator.ts +851 -0
- package/src/validation-mapper.ts +260 -0
|
@@ -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
|
+
}
|