@xrpckit/target-go-server 0.0.1

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,66 @@
1
+ import { BaseCodeGenerator, GeneratorConfig, GeneratedFiles, CodeWriter } from '@xrpckit/codegen';
2
+ import { ContractDefinition, TypeReference } from '@xrpckit/parser';
3
+
4
+ declare class GoCodeGenerator extends BaseCodeGenerator {
5
+ private typeGenerator;
6
+ private serverGenerator;
7
+ private validationGenerator;
8
+ constructor(config: GeneratorConfig);
9
+ generate(contract: ContractDefinition): GeneratedFiles;
10
+ }
11
+
12
+ declare class GoTypeGenerator {
13
+ private w;
14
+ private typeMapper;
15
+ private packageName;
16
+ constructor(packageName?: string);
17
+ generateTypes(contract: ContractDefinition): string;
18
+ private generateContextType;
19
+ private generateMiddlewareTypes;
20
+ private generateType;
21
+ private generateJSONTag;
22
+ private generateTypedHandlers;
23
+ }
24
+
25
+ declare class GoServerGenerator {
26
+ private w;
27
+ private packageName;
28
+ constructor(packageName?: string);
29
+ generateServer(contract: ContractDefinition): string;
30
+ private generateServeHTTP;
31
+ }
32
+
33
+ declare class GoTypeMapper {
34
+ mapType(typeRef: TypeReference): string;
35
+ mapPrimitive(type: string): string;
36
+ }
37
+
38
+ /**
39
+ * Go-specific code builder with fluent DSL for common Go patterns
40
+ */
41
+ declare class GoBuilder extends CodeWriter {
42
+ l(text: string): this;
43
+ n(): this;
44
+ i(): this;
45
+ u(): this;
46
+ package(name: string): this;
47
+ import(...packages: string[]): this;
48
+ type(name: string, typeDef: string): this;
49
+ struct(name: string, fn: (b: this) => void): this;
50
+ anonStruct(fn: (b: this) => void): this;
51
+ func(signature: string, fn?: (b: this) => void): this;
52
+ method(receiver: string, name: string, params: string, returns: string, fn: (b: this) => void): this;
53
+ if(condition: string, fn: (b: this) => void): this;
54
+ ifErr(fn: (b: this) => void): this;
55
+ return(value?: string): this;
56
+ var(name: string, type?: string, value?: string): this;
57
+ decl(name: string, value: string): this;
58
+ switch(value: string, cases: Array<{
59
+ value: string;
60
+ fn: (b: GoBuilder) => void;
61
+ }>, defaultCase?: (b: GoBuilder) => void): this;
62
+ comment(text: string): this;
63
+ blockComment(lines: string[]): this;
64
+ }
65
+
66
+ export { GoBuilder, GoCodeGenerator, GoServerGenerator, GoTypeGenerator, GoTypeMapper };
package/dist/index.js ADDED
@@ -0,0 +1,725 @@
1
+ // src/generator.ts
2
+ import { BaseCodeGenerator } from "@xrpckit/codegen";
3
+
4
+ // src/go-builder.ts
5
+ import { CodeWriter } from "@xrpckit/codegen";
6
+ var GoBuilder = class extends CodeWriter {
7
+ // Short aliases
8
+ l(text) {
9
+ return this.writeLine(text);
10
+ }
11
+ n() {
12
+ return this.newLine();
13
+ }
14
+ i() {
15
+ return this.indent();
16
+ }
17
+ u() {
18
+ return this.unindent();
19
+ }
20
+ // Go-specific patterns
21
+ package(name) {
22
+ return this.l(`package ${name}`).n();
23
+ }
24
+ import(...packages) {
25
+ if (packages.length === 0) return this;
26
+ if (packages.length === 1) {
27
+ return this.l(`import "${packages[0]}"`).n();
28
+ }
29
+ this.l("import (").i();
30
+ for (const pkg of packages) {
31
+ this.l(`"${pkg}"`);
32
+ }
33
+ return this.u().l(")").n();
34
+ }
35
+ type(name, typeDef) {
36
+ return this.l(`type ${name} ${typeDef}`).n();
37
+ }
38
+ struct(name, fn) {
39
+ this.l(`type ${name} struct {`).i();
40
+ fn(this);
41
+ return this.u().l("}").n();
42
+ }
43
+ // Anonymous struct
44
+ anonStruct(fn) {
45
+ this.l("struct {").i();
46
+ fn(this);
47
+ return this.u().l("}");
48
+ }
49
+ func(signature, fn) {
50
+ if (fn) {
51
+ this.write(`func ${signature} {`);
52
+ this.indent();
53
+ fn(this);
54
+ this.unindent();
55
+ this.writeLine("}");
56
+ this.newLine();
57
+ } else {
58
+ this.l(`func ${signature}`);
59
+ this.n();
60
+ }
61
+ return this;
62
+ }
63
+ method(receiver, name, params, returns, fn) {
64
+ const sig = returns ? `${name}(${params}) ${returns}` : `${name}(${params})`;
65
+ return this.func(`(${receiver}) ${sig}`, fn);
66
+ }
67
+ if(condition, fn) {
68
+ this.l(`if ${condition} {`).i();
69
+ fn(this);
70
+ return this.u().l("}");
71
+ }
72
+ ifErr(fn) {
73
+ return this.if("err != nil", fn);
74
+ }
75
+ return(value) {
76
+ return value ? this.l(`return ${value}`) : this.l("return");
77
+ }
78
+ var(name, type, value) {
79
+ if (type && value) {
80
+ return this.l(`var ${name} ${type} = ${value}`);
81
+ } else if (type) {
82
+ return this.l(`var ${name} ${type}`);
83
+ } else if (value) {
84
+ return this.l(`${name} := ${value}`);
85
+ }
86
+ return this.l(`var ${name}`);
87
+ }
88
+ // Declare variable with type inference
89
+ decl(name, value) {
90
+ return this.l(`${name} := ${value}`);
91
+ }
92
+ switch(value, cases, defaultCase) {
93
+ this.l(`switch ${value} {`).i();
94
+ for (const c of cases) {
95
+ this.l(`case ${c.value}:`).i();
96
+ c.fn(this);
97
+ this.u();
98
+ }
99
+ if (defaultCase) {
100
+ this.l("default:").i();
101
+ defaultCase(this);
102
+ this.u();
103
+ }
104
+ return this.u().l("}");
105
+ }
106
+ comment(text) {
107
+ return this.l(`// ${text}`);
108
+ }
109
+ blockComment(lines) {
110
+ this.l("/*");
111
+ for (const line of lines) {
112
+ this.l(` * ${line}`);
113
+ }
114
+ return this.l(" */");
115
+ }
116
+ };
117
+
118
+ // src/type-mapper.ts
119
+ import { toPascalCase } from "@xrpckit/codegen";
120
+ var GoTypeMapper = class {
121
+ mapType(typeRef) {
122
+ if (typeRef.kind === "optional") {
123
+ if (typeof typeRef.baseType === "object") {
124
+ return this.mapType(typeRef.baseType);
125
+ }
126
+ return "interface{}";
127
+ }
128
+ if (typeRef.kind === "nullable") {
129
+ if (typeof typeRef.baseType === "object") {
130
+ return `*${this.mapType(typeRef.baseType)}`;
131
+ }
132
+ return "*interface{}";
133
+ }
134
+ if (typeRef.kind === "array") {
135
+ return `[]${this.mapType(typeRef.elementType)}`;
136
+ }
137
+ if (typeRef.kind === "object") {
138
+ if (typeRef.name) {
139
+ return toPascalCase(typeRef.name);
140
+ }
141
+ return "interface{}";
142
+ }
143
+ if (typeRef.kind === "record") {
144
+ const valueType = typeRef.valueType ? this.mapType(typeRef.valueType) : "interface{}";
145
+ return `map[string]${valueType}`;
146
+ }
147
+ if (typeRef.kind === "tuple") {
148
+ return "[]interface{}";
149
+ }
150
+ if (typeRef.kind === "union") {
151
+ return "interface{}";
152
+ }
153
+ if (typeRef.kind === "enum") {
154
+ return "string";
155
+ }
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";
163
+ }
164
+ return "interface{}";
165
+ }
166
+ if (typeRef.kind === "date") {
167
+ return "time.Time";
168
+ }
169
+ if (typeRef.kind === "primitive") {
170
+ const baseType = typeof typeRef.baseType === "string" ? typeRef.baseType : "unknown";
171
+ return this.mapPrimitive(baseType);
172
+ }
173
+ return "interface{}";
174
+ }
175
+ mapPrimitive(type) {
176
+ const mapping = {
177
+ string: "string",
178
+ number: "float64",
179
+ integer: "int",
180
+ boolean: "bool",
181
+ date: "time.Time",
182
+ uuid: "string",
183
+ email: "string"
184
+ };
185
+ return mapping[type] || "interface{}";
186
+ }
187
+ };
188
+
189
+ // 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("");
193
+ }
194
+ var GoTypeGenerator = class {
195
+ w;
196
+ typeMapper;
197
+ packageName;
198
+ constructor(packageName = "server") {
199
+ this.w = new GoBuilder();
200
+ this.typeMapper = new GoTypeMapper();
201
+ this.packageName = packageName;
202
+ }
203
+ generateTypes(contract) {
204
+ const w = this.w.reset();
205
+ w.package(this.packageName).import("net/http");
206
+ this.generateContextType();
207
+ this.generateMiddlewareTypes();
208
+ for (const type of contract.types) {
209
+ this.generateType(type);
210
+ }
211
+ this.generateTypedHandlers(contract);
212
+ return w.toString();
213
+ }
214
+ generateContextType() {
215
+ this.w.struct("Context", (b) => {
216
+ b.l("Request *http.Request").l("ResponseWriter http.ResponseWriter").l("Data map[string]interface{}");
217
+ }).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
+ }
231
+ generateMiddlewareTypes() {
232
+ this.w.comment("MiddlewareFunc is a function that processes a request and extends context").n().type("MiddlewareFunc", "func(ctx *Context) *MiddlewareResult").n();
233
+ this.w.struct("MiddlewareResult", (b) => {
234
+ b.l("Context *Context").l("Error error").l("Response *http.Response");
235
+ }).n();
236
+ this.w.comment("NewMiddlewareResult creates a successful middleware result").n().func("NewMiddlewareResult(ctx *Context) *MiddlewareResult", (b) => {
237
+ b.return("&MiddlewareResult{Context: ctx}");
238
+ }).n();
239
+ this.w.comment("NewMiddlewareError creates a middleware result with an error").n().func("NewMiddlewareError(err error) *MiddlewareResult", (b) => {
240
+ b.return("&MiddlewareResult{Error: err}");
241
+ }).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();
245
+ }
246
+ generateType(type) {
247
+ const typeName = toPascalCase2(type.name);
248
+ if (type.kind === "array" && type.elementType) {
249
+ 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
+ });
258
+ this.w.type(typeName, `[]${elementTypeName}`);
259
+ } else {
260
+ const elementGoType = this.typeMapper.mapType(type.elementType);
261
+ this.w.type(typeName, `[]${elementGoType}`);
262
+ }
263
+ return;
264
+ }
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);
269
+ const jsonTag = this.generateJSONTag(prop);
270
+ b.l(`${toPascalCase2(prop.name)} ${goType} \`${jsonTag}\``);
271
+ }
272
+ }
273
+ });
274
+ }
275
+ generateJSONTag(prop) {
276
+ if (prop.required) {
277
+ return `json:"${prop.name}"`;
278
+ }
279
+ return `json:"${prop.name},omitempty"`;
280
+ }
281
+ generateTypedHandlers(contract) {
282
+ this.w.comment("Typed handler types for each endpoint").n();
283
+ 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
+ });
328
+ }
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
+ }
380
+ };
381
+
382
+ // src/validation-generator.ts
383
+ import { toPascalCase as toPascalCase4 } from "@xrpckit/codegen";
384
+ var GoValidationGenerator = class {
385
+ w;
386
+ packageName;
387
+ constructor(packageName = "server") {
388
+ this.w = new GoBuilder();
389
+ this.packageName = packageName;
390
+ }
391
+ generateValidation(contract) {
392
+ const w = this.w.reset();
393
+ const imports = /* @__PURE__ */ new Set(["fmt", "strings"]);
394
+ const needsRegex = this.hasValidationRule(
395
+ contract,
396
+ (rules) => rules.uuid || rules.regex && !rules.email
397
+ // regex but not email (email uses mail)
398
+ );
399
+ const needsMail = this.hasValidationRule(
400
+ contract,
401
+ (rules) => rules.email
402
+ );
403
+ const needsURL = this.hasValidationRule(
404
+ contract,
405
+ (rules) => rules.url
406
+ );
407
+ if (needsRegex) imports.add("regexp");
408
+ if (needsMail) imports.add("net/mail");
409
+ if (needsURL) imports.add("net/url");
410
+ w.package(this.packageName);
411
+ if (imports.size > 0) {
412
+ w.import(...Array.from(imports));
413
+ }
414
+ this.generateErrorTypes(w);
415
+ for (const type of contract.types) {
416
+ if (type.kind === "object" && type.properties) {
417
+ this.generateTypeValidation(type, w);
418
+ } else if (type.kind === "array" && type.elementType?.kind === "object" && type.elementType.properties) {
419
+ const elementTypeName = toPascalCase4(type.name) + "Item";
420
+ const elementType = {
421
+ name: elementTypeName,
422
+ kind: "object",
423
+ properties: type.elementType.properties
424
+ };
425
+ this.generateTypeValidation(elementType, w);
426
+ }
427
+ }
428
+ this.generateHelperFunctions(w);
429
+ return w.toString();
430
+ }
431
+ generateErrorTypes(w) {
432
+ w.struct("ValidationError", (b) => {
433
+ b.l('Field string `json:"field"`').l('Message string `json:"message"`');
434
+ }).n();
435
+ w.type("ValidationErrors", "[]*ValidationError").n();
436
+ w.method("e *ValidationError", "Error", "", "string", (b) => {
437
+ b.return('fmt.Sprintf("%s: %s", e.Field, e.Message)');
438
+ }).n();
439
+ w.method("e ValidationErrors", "Error", "", "string", (b) => {
440
+ b.var("msgs", "[]string");
441
+ b.l("for _, err := range e {").i().l("msgs = append(msgs, err.Error())").u().l("}");
442
+ b.return('strings.Join(msgs, "; ")');
443
+ }).n();
444
+ }
445
+ generateTypeValidation(type, w) {
446
+ const typeName = toPascalCase4(type.name);
447
+ const funcName = `Validate${typeName}`;
448
+ w.func(`${funcName}(input ${typeName}) error`, (b) => {
449
+ b.var("errs", "ValidationErrors");
450
+ if (type.properties) {
451
+ for (const prop of type.properties) {
452
+ this.generatePropertyValidation(prop, "input", type, b);
453
+ }
454
+ }
455
+ b.if("len(errs) > 0", (b2) => {
456
+ b2.return("errs");
457
+ });
458
+ b.return("nil");
459
+ }).n();
460
+ }
461
+ generatePropertyValidation(prop, prefix, parentType, w) {
462
+ const fieldName = toPascalCase4(prop.name);
463
+ const fieldPath = `${prefix}.${fieldName}`;
464
+ const fieldPathStr = prop.name;
465
+ const actualType = this.getActualType(prop.type);
466
+ const isString = actualType === "string";
467
+ const isNumber = actualType === "number";
468
+ const isArray = prop.type.kind === "array";
469
+ if (prop.required) {
470
+ 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(`})`);
474
+ });
475
+ } else if (isNumber) {
476
+ } 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(`})`);
479
+ });
480
+ }
481
+ }
482
+ const validationRules = prop.validation || prop.type.validation;
483
+ if (validationRules) {
484
+ if (!prop.required) {
485
+ if (isString) {
486
+ w.if(`${fieldPath} != ""`, (b) => {
487
+ const validationTypeRef = {
488
+ kind: "primitive",
489
+ baseType: actualType
490
+ };
491
+ this.generateValidationRules(validationRules, fieldPath, fieldPathStr, validationTypeRef, b, false);
492
+ });
493
+ } else if (isNumber) {
494
+ const validationTypeRef = {
495
+ kind: "primitive",
496
+ baseType: actualType
497
+ };
498
+ this.generateValidationRules(validationRules, fieldPath, fieldPathStr, validationTypeRef, w, false);
499
+ } else if (isArray) {
500
+ w.if(`${fieldPath} != nil`, (b) => {
501
+ this.generateValidationRules(validationRules, fieldPath, fieldPathStr, prop.type, b, false);
502
+ });
503
+ } else {
504
+ this.generateValidationRules(validationRules, fieldPath, fieldPathStr, prop.type, w, false);
505
+ }
506
+ } 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
+ }
516
+ }
517
+ }
518
+ if (prop.type.kind === "object" && prop.type.name) {
519
+ const nestedTypeName = toPascalCase4(prop.type.name);
520
+ 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
+ });
535
+ }
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);
539
+ 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);
550
+ }
551
+ w.u().l(`}`);
552
+ }
553
+ }
554
+ }
555
+ generateValidationRules(rules, fieldPath, fieldPathStr, typeRef, w, isRequired = false) {
556
+ if (typeRef.baseType === "string") {
557
+ if (rules.minLength !== void 0) {
558
+ const minLengthCondition = isRequired ? `${fieldPath} != "" && len(${fieldPath}) < ${rules.minLength}` : `len(${fieldPath}) < ${rules.minLength}`;
559
+ 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(`})`);
561
+ });
562
+ }
563
+ if (rules.maxLength !== void 0) {
564
+ 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(`})`);
566
+ });
567
+ }
568
+ if (rules.email) {
569
+ if (isRequired) {
570
+ 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(`}`);
572
+ });
573
+ } 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(`}`);
575
+ }
576
+ }
577
+ if (rules.url) {
578
+ if (isRequired) {
579
+ 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(`}`);
581
+ });
582
+ } 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(`}`);
584
+ }
585
+ }
586
+ if (rules.uuid) {
587
+ if (isRequired) {
588
+ 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(`}`);
590
+ });
591
+ } 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(`}`);
593
+ }
594
+ }
595
+ if (rules.regex && !rules.email && !rules.url && !rules.uuid) {
596
+ if (isRequired) {
597
+ 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(`}`);
600
+ });
601
+ } 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(`}`);
604
+ }
605
+ }
606
+ } else if (typeRef.baseType === "number") {
607
+ if (rules.min !== void 0) {
608
+ 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(`})`);
610
+ });
611
+ }
612
+ if (rules.max !== void 0) {
613
+ 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(`})`);
615
+ });
616
+ }
617
+ if (rules.int) {
618
+ 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(`})`);
620
+ });
621
+ }
622
+ if (rules.positive) {
623
+ w.if(`${fieldPath} <= 0`, (b) => {
624
+ b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be positive",`).u().l(`})`);
625
+ });
626
+ }
627
+ if (rules.negative) {
628
+ w.if(`${fieldPath} >= 0`, (b) => {
629
+ b.l(`errs = append(errs, &ValidationError{`).i().l(`Field: "${fieldPathStr}",`).l(`Message: "must be negative",`).u().l(`})`);
630
+ });
631
+ }
632
+ } else if (typeRef.kind === "array") {
633
+ const arrayCheckCondition = isRequired ? `${fieldPath} != nil` : `${fieldPath} != nil`;
634
+ 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
+ });
638
+ }
639
+ 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
+ });
643
+ }
644
+ }
645
+ }
646
+ hasValidationRule(contract, check) {
647
+ 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
+ }
668
+ }
669
+ if (type.elementType?.validation && check(type.elementType.validation)) {
670
+ return true;
671
+ }
672
+ }
673
+ return false;
674
+ }
675
+ generateHelperFunctions(w) {
676
+ }
677
+ getActualType(typeRef) {
678
+ if (typeRef.kind === "optional" || typeRef.kind === "nullable") {
679
+ if (typeRef.baseType) {
680
+ if (typeof typeRef.baseType === "string") {
681
+ return typeRef.baseType;
682
+ }
683
+ return this.getActualType(typeRef.baseType);
684
+ }
685
+ }
686
+ if (typeof typeRef.baseType === "string") {
687
+ return typeRef.baseType;
688
+ }
689
+ if (typeRef.baseType) {
690
+ return this.getActualType(typeRef.baseType);
691
+ }
692
+ return "unknown";
693
+ }
694
+ toPascalCase(str) {
695
+ return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
696
+ }
697
+ };
698
+
699
+ // 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) {
712
+ return {
713
+ types: this.typeGenerator.generateTypes(contract),
714
+ server: this.serverGenerator.generateServer(contract),
715
+ validation: this.validationGenerator.generateValidation(contract)
716
+ };
717
+ }
718
+ };
719
+ export {
720
+ GoBuilder,
721
+ GoCodeGenerator,
722
+ GoServerGenerator,
723
+ GoTypeGenerator,
724
+ GoTypeMapper
725
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@xrpckit/target-go-server",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/mwesox/xrpc.git",
9
+ "directory": "packages/target-go-server"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "bun": "./src/index.ts",
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsup src/index.ts --format esm --dts --clean"
28
+ },
29
+ "dependencies": {
30
+ "@xrpckit/codegen": "^0.0.1",
31
+ "@xrpckit/parser": "^0.0.1"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^22.0.0",
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.0.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ }
41
+ }