ondc-code-generator 0.6.21 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.github/copilot-instructions.md +128 -0
  2. package/GOLANG_IMPLEMENTATION.md +156 -0
  3. package/README.md +4 -1
  4. package/alpha/possible-json-paths.json +3734 -0
  5. package/dist/generator/config-compiler.js +6 -0
  6. package/dist/generator/generators/golang/go-ast.d.ts +1 -0
  7. package/dist/generator/generators/golang/go-ast.js +60 -0
  8. package/dist/generator/generators/golang/go-generator.d.ts +23 -0
  9. package/dist/generator/generators/golang/go-generator.js +511 -0
  10. package/dist/generator/generators/golang/templates/api-test.mustache +48 -0
  11. package/dist/generator/generators/golang/templates/json-normalizer.mustache +46 -0
  12. package/dist/generator/generators/golang/templates/json-path-utils.mustache +21 -0
  13. package/dist/generator/generators/golang/templates/storage-templates/api-save.mustache +30 -0
  14. package/dist/generator/generators/golang/templates/storage-templates/index.mustache +41 -0
  15. package/dist/generator/generators/golang/templates/storage-templates/save-utils.mustache +37 -0
  16. package/dist/generator/generators/golang/templates/storage-templates/storage-helpers.mustache +51 -0
  17. package/dist/generator/generators/golang/templates/storage-templates/storage-interface.mustache +96 -0
  18. package/dist/generator/generators/golang/templates/storage-templates/storage-types.mustache +15 -0
  19. package/dist/generator/generators/golang/templates/test-config.mustache +39 -0
  20. package/dist/generator/generators/golang/templates/test-object.mustache +39 -0
  21. package/dist/generator/generators/golang/templates/validation-code.mustache +51 -0
  22. package/dist/generator/generators/golang/templates/validation-utils.mustache +246 -0
  23. package/dist/generator/generators/python/py-generator.js +1 -1
  24. package/dist/generator/generators/python/templates/json-path-utils.mustache +16 -1
  25. package/dist/generator/generators/python/templates/storage-templates/api-save.mustache +1 -1
  26. package/dist/generator/generators/typescript/templates/json-path-utils.mustache +25 -7
  27. package/dist/generator/generators/typescript/templates/storage-templates/api-save.mustache +1 -1
  28. package/dist/generator/generators/typescript/ts-generator.js +1 -1
  29. package/dist/index.d.ts +2 -1
  30. package/dist/index.js +2 -1
  31. package/dist/index.test.js +7 -1
  32. package/dist/types/compiler-types.d.ts +2 -1
  33. package/dist/types/compiler-types.js +1 -0
  34. package/dist/utils/fs-utils.js +40 -0
  35. package/package.json +2 -2
  36. package/sample.md +273 -0
@@ -11,6 +11,7 @@ import path from "path";
11
11
  import { duplicateVariablesInChildren } from "../utils/config-utils/duplicateVariables.js";
12
12
  import { PythonGenerator } from "./generators/python/py-generator.js";
13
13
  import { JavascriptGenerator } from "./generators/javascript/js-generator.js";
14
+ import { GolangGenerator } from "./generators/golang/go-generator.js";
14
15
  const __filename = fileURLToPath(import.meta.url);
15
16
  const __dirname = path.dirname(__filename);
16
17
  const defaultConfig = {
@@ -85,6 +86,11 @@ export class ConfigCompiler {
85
86
  codeName: codeName,
86
87
  });
87
88
  break;
89
+ case SupportedLanguages.Golang:
90
+ await new GolangGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
91
+ codeName: codeName,
92
+ });
93
+ break;
88
94
  default:
89
95
  throw new Error("Language not supported");
90
96
  }
@@ -0,0 +1 @@
1
+ export declare const compileInputToGo: (input: string) => string;
@@ -0,0 +1,60 @@
1
+ import { buildAstFromInput } from "../../../services/return-complier/combined.js";
2
+ import { AllIn, AnyIn, AreUnique, EqualTo, FollowRegex, GreaterThan, LessThan, NoneIn, ArePresent, } from "../../../services/return-complier/tokens.js";
3
+ const uniaryFunction = {
4
+ [AreUnique.LABEL ?? "are unique"]: "AreUnique",
5
+ [ArePresent.LABEL ?? "are present"]: "ArePresent",
6
+ };
7
+ const binaryFunction = {
8
+ [AllIn.LABEL ?? "all in"]: "AllIn",
9
+ [AnyIn.LABEL ?? "any in"]: "AnyIn",
10
+ [FollowRegex.LABEL ?? "follow regex"]: "FollowRegex",
11
+ [NoneIn.LABEL ?? "none in"]: "NoneIn",
12
+ [EqualTo.LABEL ?? "equal to"]: "EqualTo",
13
+ [GreaterThan.LABEL ?? "greater than"]: "GreaterThan",
14
+ [LessThan.LABEL ?? "less than"]: "LessThan",
15
+ };
16
+ function getGoOperator(op) {
17
+ switch (op) {
18
+ case "&&":
19
+ return "&&";
20
+ case "||":
21
+ return "||";
22
+ default:
23
+ return op;
24
+ }
25
+ }
26
+ function compileToGo(node) {
27
+ if (node.type === "returnStatement") {
28
+ const returnNode = node;
29
+ return compileToGo(returnNode.expression);
30
+ }
31
+ if (node.type === "binaryOperator") {
32
+ const binaryNode = node;
33
+ const lhs = compileToGo(binaryNode.lhs);
34
+ const rhs = compileToGo(binaryNode.rhs);
35
+ return `(${lhs}) ${getGoOperator(binaryNode.operator)} (${rhs})`;
36
+ }
37
+ if (node.type === "notOperator") {
38
+ const notNode = node;
39
+ const expression = compileToGo(notNode.expression);
40
+ return `!(${expression})`;
41
+ }
42
+ if (node.type === "customUniaryFunction") {
43
+ const unary = node;
44
+ const func = uniaryFunction[unary.customFunction];
45
+ const varName = unary.expression.name;
46
+ return `utils.${func}(${varName})`;
47
+ }
48
+ if (node.type === "customBinaryFunction") {
49
+ const binary = node;
50
+ const func = binaryFunction[binary.customFunction];
51
+ const lhs = binary.lhs.name;
52
+ const rhs = binary.rhs.name;
53
+ return `utils.${func}(${lhs}, ${rhs})`;
54
+ }
55
+ throw new Error("Unknown node type");
56
+ }
57
+ export const compileInputToGo = (input) => {
58
+ const ast = buildAstFromInput(input);
59
+ return compileToGo(ast);
60
+ };
@@ -0,0 +1,23 @@
1
+ import { TestObject } from "../../../types/config-types.js";
2
+ import { CodeGenerator, CodeGeneratorProps } from "../classes/abstract-generator.js";
3
+ export declare class GolangGenerator extends CodeGenerator {
4
+ codeConfig: CodeGeneratorProps | undefined;
5
+ generateSessionDataCode: () => Promise<void>;
6
+ generateValidationCode: () => Promise<void>;
7
+ private collectAllFunctions;
8
+ private preCalculateFunctionNames;
9
+ private collectAllFunctionsInner;
10
+ generateCode: (codeConfig: CodeGeneratorProps) => Promise<void>;
11
+ generateTestFunction: (testObject: TestObject, apiPrefix?: string, functionNameMap?: Map<string, number>, isInnerPass?: boolean) => Promise<{
12
+ funcName: string;
13
+ code: string;
14
+ }>;
15
+ private CreateErrorMarkdown;
16
+ private createVariablesCode;
17
+ private convertArrayToInterfaceGo;
18
+ private createValidationLogicCode;
19
+ private generateErrorFile;
20
+ private getExternalKeys;
21
+ private generateMainFile;
22
+ private generateGoMod;
23
+ }
@@ -0,0 +1,511 @@
1
+ import { readFileSync } from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { ConfigSyntax, ExternalDataSyntax, TestObjectSyntax, } from "../../../constants/syntax.js";
5
+ import Mustache from "mustache";
6
+ import { markdownMessageGenerator } from "../documentation/markdown-message-generator.js";
7
+ import { getVariablesFromTest as extractVariablesFromText } from "../../../utils/general-utils/test-object-utils.js";
8
+ import { compileInputToGo } from "./go-ast.js";
9
+ import { CodeGenerator, } from "../classes/abstract-generator.js";
10
+ import { writeAndFormatCode } from "../../../utils/fs-utils.js";
11
+ import { MarkdownDocGenerator } from "../documentation/md-generator.js";
12
+ import { collectLoadData } from "../../../utils/config-utils/load-variables.js";
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ export class GolangGenerator extends CodeGenerator {
16
+ constructor() {
17
+ super(...arguments);
18
+ this.generateSessionDataCode = async () => {
19
+ if (!this.codeConfig) {
20
+ throw new Error("Code config not set. Please call generateCode first.");
21
+ }
22
+ const sessionData = this.validationConfig[ConfigSyntax.SessionData];
23
+ const tests = this.validationConfig[ConfigSyntax.Tests];
24
+ const relevantSessionData = {};
25
+ collectLoadData(tests, relevantSessionData);
26
+ console.log("Relevant Session Data for Loading:", relevantSessionData);
27
+ // Load storage templates
28
+ const storageInterfaceTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-interface.mustache"), "utf-8");
29
+ const storageTypesTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-types.mustache"), "utf-8");
30
+ const saveUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/save-utils.mustache"), "utf-8");
31
+ const storageHelpersTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-helpers.mustache"), "utf-8");
32
+ const indexTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/index.mustache"), "utf-8");
33
+ const apiSaveTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/api-save.mustache"), "utf-8");
34
+ const allActions = Object.keys(tests);
35
+ // Helper to capitalize and sanitize function names
36
+ const capitalize = (name) => name.charAt(0).toUpperCase() + name.slice(1);
37
+ const sanitizeName = (name) => name.replace(/[^a-zA-Z0-9_]/g, "");
38
+ // Generate index code with all actions
39
+ const indexCode = Mustache.render(indexTemplate, {
40
+ actions: allActions.map((action) => ({ action })),
41
+ functionName: capitalize(sanitizeName(this.codeConfig.codeName)),
42
+ });
43
+ // Generate individual action storage files
44
+ for (const action of allActions) {
45
+ const loadData = relevantSessionData[action] || {};
46
+ const saveData = sessionData[action] || {};
47
+ const saveCode = Mustache.render(apiSaveTemplate, {
48
+ storeActions: Object.keys(saveData).map((key) => ({
49
+ key: key,
50
+ value: saveData[key],
51
+ })),
52
+ loadActions: Object.keys(loadData).map((key) => ({
53
+ key: loadData[key],
54
+ })),
55
+ action: action,
56
+ });
57
+ await writeAndFormatCode(this.rootPath, `./storageactions/${action}.go`, saveCode, "go");
58
+ }
59
+ // Write common storage files
60
+ await writeAndFormatCode(this.rootPath, "./utils/save_utils.go", saveUtilsTemplate, "go");
61
+ await writeAndFormatCode(this.rootPath, "./types/storage_types.go", storageTypesTemplate, "go");
62
+ await writeAndFormatCode(this.rootPath, "./interfaces/storage_interface.go", storageInterfaceTemplate, "go");
63
+ await writeAndFormatCode(this.rootPath, "./storageactions/helpers.go", storageHelpersTemplate, "go");
64
+ await writeAndFormatCode(this.rootPath, "./storageactions/index.go", indexCode, "go");
65
+ };
66
+ this.generateValidationCode = async () => {
67
+ const testConfig = this.validationConfig[ConfigSyntax.Tests];
68
+ // Helper to avoid Go reserved keywords
69
+ const goSafeName = (name) => {
70
+ const reserved = [
71
+ "break",
72
+ "case",
73
+ "chan",
74
+ "const",
75
+ "continue",
76
+ "default",
77
+ "defer",
78
+ "else",
79
+ "fallthrough",
80
+ "for",
81
+ "func",
82
+ "go",
83
+ "goto",
84
+ "if",
85
+ "import",
86
+ "init",
87
+ "interface",
88
+ "map",
89
+ "package",
90
+ "range",
91
+ "return",
92
+ "select",
93
+ "struct",
94
+ "switch",
95
+ "type",
96
+ "var",
97
+ ];
98
+ return reserved.includes(name) ? `${name}Action` : name;
99
+ };
100
+ // Helper to capitalize first letter for exported functions
101
+ const capitalize = (name) => name.charAt(0).toUpperCase() + name.slice(1);
102
+ for (const key in testConfig) {
103
+ const testObjects = testConfig[key];
104
+ const safeName = capitalize(goSafeName(key));
105
+ const betaConfig = {
106
+ [TestObjectSyntax.Name]: safeName + "Validations",
107
+ [TestObjectSyntax.Return]: testObjects,
108
+ };
109
+ // Collect all functions (main + nested) with API prefix and deduplication
110
+ const functionNameMap = new Map(); // Track function names to handle duplicates
111
+ const allFunctions = await this.collectAllFunctions(betaConfig, safeName, functionNameMap);
112
+ const apiTestTemplate = readFileSync(path.resolve(__dirname, "./templates/api-test.mustache"), "utf-8");
113
+ const finalCode = Mustache.render(apiTestTemplate, {
114
+ functionCode: allFunctions.map((f) => f.code).join("\n\n"),
115
+ apiName: safeName,
116
+ });
117
+ await writeAndFormatCode(this.rootPath, `./apitests/${key}.go`, finalCode, "go");
118
+ }
119
+ };
120
+ this.generateCode = async (codeConfig) => {
121
+ this.codeConfig = codeConfig;
122
+ const jsonPathUtilsCode = readFileSync(path.resolve(__dirname, "./templates/json-path-utils.mustache"), "utf-8");
123
+ const validationUtils = readFileSync(path.resolve(__dirname, "./templates/validation-utils.mustache"), "utf-8");
124
+ const typesTemplate = readFileSync(path.resolve(__dirname, "./templates/test-config.mustache"), "utf-8");
125
+ const normalizerTemplate = readFileSync(path.resolve(__dirname, "./templates/json-normalizer.mustache"), "utf-8");
126
+ const typesCode = Mustache.render(typesTemplate, {
127
+ externalData: this.getExternalKeys(),
128
+ });
129
+ await writeAndFormatCode(this.rootPath, "./utils/json_path_utils.go", jsonPathUtilsCode, "go");
130
+ await writeAndFormatCode(this.rootPath, "./utils/json_normalizer.go", normalizerTemplate, "go");
131
+ await writeAndFormatCode(this.rootPath, "./utils/validation_utils.go", validationUtils, "go");
132
+ await writeAndFormatCode(this.rootPath, "./types/test_config.go", typesCode, "go");
133
+ await this.generateValidationCode();
134
+ await writeAndFormatCode(this.rootPath, "error.go", this.generateErrorFile(this.errorCodes), "go");
135
+ await writeAndFormatCode(this.rootPath, "main.go", this.generateMainFile(Object.keys(this.validationConfig[ConfigSyntax.Tests]), codeConfig.codeName), "go");
136
+ await writeAndFormatCode(this.rootPath, "go.mod", this.generateGoMod(codeConfig.codeName), "text");
137
+ await new MarkdownDocGenerator(this.validationConfig, this.errorCodes, this.rootPath).generateCode();
138
+ await this.generateSessionDataCode();
139
+ };
140
+ this.generateTestFunction = async (testObject, apiPrefix = "", functionNameMap, isInnerPass = false) => {
141
+ const template = readFileSync(path.resolve(__dirname, "./templates/test-object.mustache"), "utf-8");
142
+ // Calculate function name with API prefix
143
+ const baseName = apiPrefix
144
+ ? `${apiPrefix}_${testObject[TestObjectSyntax.Name]}`
145
+ : testObject[TestObjectSyntax.Name];
146
+ let funcName = baseName;
147
+ // Handle duplicates by adding a counter suffix
148
+ if (functionNameMap) {
149
+ const usageKey = `${baseName}_usage`;
150
+ const usageCount = functionNameMap.get(usageKey) || 0;
151
+ functionNameMap.set(usageKey, usageCount + 1);
152
+ const totalCount = functionNameMap.get(baseName) || 1;
153
+ if (totalCount > 1) {
154
+ // Always add suffix if there are duplicates: _1, _2, _3, etc.
155
+ funcName = `${baseName}_${usageCount + 1}`;
156
+ }
157
+ }
158
+ const view = {
159
+ name: funcName,
160
+ scopePath: testObject[TestObjectSyntax.Scope] ?? "$",
161
+ variables: this.createVariablesCode(testObject),
162
+ hasContinue: testObject[TestObjectSyntax.Continue] ? true : false,
163
+ skipCheckStatement: testObject[TestObjectSyntax.Continue]
164
+ ? compileInputToGo(testObject[TestObjectSyntax.Continue])
165
+ : undefined,
166
+ validationCode: await this.createValidationLogicCode(testObject, apiPrefix, functionNameMap, isInnerPass),
167
+ successCode: testObject[TestObjectSyntax.SuccessCode] ?? 200,
168
+ errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
169
+ testName: testObject[TestObjectSyntax.Name],
170
+ TEST_OBJECT: `${JSON.stringify(testObject)}`,
171
+ };
172
+ return {
173
+ funcName: funcName,
174
+ code: Mustache.render(template, view),
175
+ };
176
+ };
177
+ this.createValidationLogicCode = async (testObject, apiPrefix = "", functionNameMap, isInnerPass = false) => {
178
+ const template = readFileSync(path.resolve(__dirname, "./templates/validation-code.mustache"), "utf-8");
179
+ const skip = testObject[TestObjectSyntax.Continue];
180
+ const skipList = skip ? [skip] : undefined;
181
+ if (typeof testObject[TestObjectSyntax.Return] === "string") {
182
+ const returnStatement = compileInputToGo(testObject[TestObjectSyntax.Return]);
183
+ // Check if this is a stateful validation
184
+ let isStateFull = false;
185
+ for (const k in testObject) {
186
+ const value = testObject[k];
187
+ if (typeof value === "string") {
188
+ if (value.includes(`${ExternalDataSyntax}.`) &&
189
+ !value.includes("_SELF")) {
190
+ isStateFull = true;
191
+ break;
192
+ }
193
+ }
194
+ }
195
+ return Mustache.render(template, {
196
+ isNested: false,
197
+ isStateFull: isStateFull,
198
+ returnStatement: returnStatement,
199
+ errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
200
+ errorDescription: this.CreateErrorMarkdown(testObject, skipList),
201
+ testName: testObject[TestObjectSyntax.Name],
202
+ TEST_OBJECT: `${JSON.stringify(testObject)}`,
203
+ });
204
+ }
205
+ else {
206
+ const subObjects = testObject[TestObjectSyntax.Return];
207
+ // For nested functions in Go, calculate the correct function names to match definitions
208
+ const names = subObjects.map((subObj) => {
209
+ const baseName = apiPrefix
210
+ ? `${apiPrefix}_${subObj[TestObjectSyntax.Name]}`
211
+ : subObj[TestObjectSyntax.Name];
212
+ let funcName = baseName;
213
+ if (functionNameMap) {
214
+ const callKey = `${baseName}_calls`;
215
+ const currentCalls = functionNameMap.get(callKey) || 0;
216
+ functionNameMap.set(callKey, currentCalls + 1);
217
+ const totalCount = functionNameMap.get(baseName) || 1;
218
+ if (totalCount > 1) {
219
+ // Add suffix to match function definition: _1, _2, _3, etc.
220
+ funcName = `${baseName}_${currentCalls + 1}`;
221
+ }
222
+ }
223
+ return { name: funcName };
224
+ });
225
+ return Mustache.render(template, {
226
+ isNested: true,
227
+ names: names,
228
+ testName: testObject[TestObjectSyntax.Name],
229
+ TEST_OBJECT: `${JSON.stringify(testObject)}`,
230
+ });
231
+ }
232
+ };
233
+ }
234
+ // Helper to recursively collect all nested functions
235
+ async collectAllFunctions(testObject, apiPrefix, functionNameMap) {
236
+ const result = [];
237
+ // First pass: pre-calculate all function names to build the map
238
+ this.preCalculateFunctionNames(testObject, apiPrefix, functionNameMap);
239
+ // Second pass: generate the actual code with correct function references
240
+ const mainFunc = await this.generateTestFunction(testObject, apiPrefix, functionNameMap);
241
+ result.push(mainFunc);
242
+ // If it has nested functions, collect them recursively
243
+ if (Array.isArray(testObject[TestObjectSyntax.Return])) {
244
+ for (const subObject of testObject[TestObjectSyntax.Return]) {
245
+ const nestedFuncs = await this.collectAllFunctionsInner(subObject, apiPrefix, functionNameMap);
246
+ result.push(...nestedFuncs);
247
+ }
248
+ }
249
+ return result;
250
+ }
251
+ // Helper to pre-calculate function names
252
+ preCalculateFunctionNames(testObject, apiPrefix, functionNameMap) {
253
+ const baseName = apiPrefix
254
+ ? `${apiPrefix}_${testObject[TestObjectSyntax.Name]}`
255
+ : testObject[TestObjectSyntax.Name];
256
+ const count = functionNameMap.get(baseName) || 0;
257
+ functionNameMap.set(baseName, count + 1);
258
+ // Recursively pre-calculate nested function names
259
+ if (Array.isArray(testObject[TestObjectSyntax.Return])) {
260
+ for (const subObject of testObject[TestObjectSyntax.Return]) {
261
+ this.preCalculateFunctionNames(subObject, apiPrefix, functionNameMap);
262
+ }
263
+ }
264
+ }
265
+ // Inner helper that doesn't pre-calculate (used after first pass)
266
+ async collectAllFunctionsInner(testObject, apiPrefix, functionNameMap) {
267
+ const result = [];
268
+ const mainFunc = await this.generateTestFunction(testObject, apiPrefix, functionNameMap, true);
269
+ result.push(mainFunc);
270
+ if (Array.isArray(testObject[TestObjectSyntax.Return])) {
271
+ for (const subObject of testObject[TestObjectSyntax.Return]) {
272
+ const nestedFuncs = await this.collectAllFunctionsInner(subObject, apiPrefix, functionNameMap);
273
+ result.push(...nestedFuncs);
274
+ }
275
+ }
276
+ return result;
277
+ }
278
+ CreateErrorMarkdown(testObject, skipList) {
279
+ return markdownMessageGenerator(testObject[TestObjectSyntax.Return], testObject, testObject[TestObjectSyntax.Name], skipList);
280
+ }
281
+ createVariablesCode(testObject) {
282
+ const variables = [];
283
+ const varNames = extractVariablesFromText(testObject);
284
+ for (const name of varNames) {
285
+ const value = testObject[name];
286
+ const final = typeof value === "string"
287
+ ? `utils.GetJSONPath(testObj, "${value}")`
288
+ : this.convertArrayToInterfaceGo(value);
289
+ variables.push({
290
+ name: name,
291
+ value: final,
292
+ isAssignment: false,
293
+ });
294
+ }
295
+ // Add _ = variable for unused variables to avoid Go compile errors
296
+ const returnStatement = testObject[TestObjectSyntax.Return];
297
+ if (typeof returnStatement === "string") {
298
+ for (const name of varNames) {
299
+ if (!returnStatement.includes(name)) {
300
+ variables.push({
301
+ name: "_",
302
+ value: name,
303
+ isAssignment: true,
304
+ });
305
+ }
306
+ }
307
+ }
308
+ return variables;
309
+ }
310
+ convertArrayToInterfaceGo(value) {
311
+ // Convert TypeScript array notation to Go slice notation for []interface{}
312
+ const elements = value.map((v) => {
313
+ if (typeof v === "string") {
314
+ // Use raw string literal for strings with backslashes (likely regex)
315
+ if (v.includes("\\")) {
316
+ return "`" + v + "`";
317
+ }
318
+ return `"${v}"`;
319
+ }
320
+ if (v === null)
321
+ return "nil";
322
+ if (typeof v === "boolean")
323
+ return v ? "true" : "false";
324
+ if (typeof v === "number")
325
+ return v.toString();
326
+ return JSON.stringify(v);
327
+ });
328
+ return `[]interface{}{${elements.join(", ")}}`;
329
+ }
330
+ generateErrorFile(errors) {
331
+ const allCodes = errors.map((error) => error.code);
332
+ if (allCodes.length !== new Set(allCodes).size) {
333
+ throw new Error("Duplicate error codes found");
334
+ }
335
+ errors.push({
336
+ code: 20006,
337
+ Description: "Invalid response does not meet API contract specifications",
338
+ });
339
+ errors.push({
340
+ code: 30000,
341
+ Description: "Invalid request does not meet API contract specifications",
342
+ });
343
+ const errorsList = errors
344
+ .map((error) => ` {Code: ${error.code}, Message: "${error.Description}"}`)
345
+ .join(",\n");
346
+ return `package validations
347
+
348
+ import "fmt"
349
+
350
+ type Error struct {
351
+ Code int
352
+ Message string
353
+ }
354
+
355
+ var errors = []Error{
356
+ ${errorsList},
357
+ }
358
+
359
+ func GetError(code int) (*Error, error) {
360
+ for _, err := range errors {
361
+ if err.Code == code {
362
+ return &err, nil
363
+ }
364
+ }
365
+ return nil, fmt.Errorf("error code %d not found", code)
366
+ }
367
+ `;
368
+ }
369
+ getExternalKeys() {
370
+ const apis = Object.keys(this.validationConfig[ConfigSyntax.SessionData]);
371
+ const result = [];
372
+ for (const api of apis) {
373
+ const keys = Object.keys(this.validationConfig[ConfigSyntax.SessionData][api]);
374
+ for (const key of keys) {
375
+ if (key !== "_SELF") {
376
+ result.push({ name: key });
377
+ }
378
+ }
379
+ }
380
+ return result;
381
+ }
382
+ generateMainFile(apis, functionName = "L1Validations") {
383
+ functionName = functionName.replace(/[^a-zA-Z0-9_]/g, "");
384
+ // Helper to avoid Go reserved keywords
385
+ const goSafeName = (name) => {
386
+ const reserved = [
387
+ "break",
388
+ "case",
389
+ "chan",
390
+ "const",
391
+ "continue",
392
+ "default",
393
+ "defer",
394
+ "else",
395
+ "fallthrough",
396
+ "for",
397
+ "func",
398
+ "go",
399
+ "goto",
400
+ "if",
401
+ "import",
402
+ "init",
403
+ "interface",
404
+ "map",
405
+ "package",
406
+ "range",
407
+ "return",
408
+ "select",
409
+ "struct",
410
+ "switch",
411
+ "type",
412
+ "var",
413
+ ];
414
+ const safeName = reserved.includes(name) ? `${name}Action` : name;
415
+ return safeName.charAt(0).toUpperCase() + safeName.slice(1);
416
+ };
417
+ const imports = `package validations
418
+
419
+ import (
420
+ "fmt"
421
+ "L1_validations/apitests"
422
+ "L1_validations/storageactions"
423
+ "L1_validations/types"
424
+ "L1_validations/utils"
425
+ )
426
+ `;
427
+ const masterFunction = `
428
+ // Perform${functionName} executes validation for the specified action
429
+ func Perform${functionName}(action string, payload map[string]interface{}, config *types.ValidationConfig, externalData map[string]interface{}) ([]types.ValidationOutput, error) {
430
+ if config == nil {
431
+ config = &types.ValidationConfig{
432
+ OnlyInvalid: true,
433
+ StandardLogs: false,
434
+ HideParentErrors: true,
435
+ StateFullValidations: false,
436
+ Debug: false,
437
+ }
438
+ }
439
+
440
+ if config.StateFullValidations && config.Store == nil {
441
+ return nil, fmt.Errorf("state full validations require a storage interface to be provided in the config")
442
+ }
443
+
444
+ if config.StateFullValidations && config.UniqueKey == "" {
445
+ return nil, fmt.Errorf("state full validations require a unique_key to be provided in the config")
446
+ }
447
+
448
+ normalizedPayload := utils.NormalizeKeys(payload).(map[string]interface{})
449
+
450
+ if externalData == nil {
451
+ externalData = make(map[string]interface{})
452
+ }
453
+ externalData["_SELF"] = normalizedPayload
454
+
455
+ // Load stateful data if enabled
456
+ if config.StateFullValidations {
457
+ loadedData, err := storageactions.Perform${functionName}Load(action, config.UniqueKey, config.Store)
458
+ if err == nil && loadedData != nil {
459
+ // Merge loaded data with external data
460
+ for k, v := range loadedData {
461
+ if _, exists := externalData[k]; !exists {
462
+ externalData[k] = v
463
+ }
464
+ }
465
+ }
466
+ }
467
+
468
+ input := types.ValidationInput{
469
+ Payload: normalizedPayload,
470
+ ExternalData: externalData,
471
+ Config: *config,
472
+ }
473
+
474
+ var result []types.ValidationOutput
475
+
476
+ switch action {
477
+ ${apis
478
+ .map((api) => ` case "${api}":
479
+ result = apitests.${goSafeName(api)}(input)`)
480
+ .join("\n")}
481
+ default:
482
+ return nil, fmt.Errorf("action not found: %s", action)
483
+ }
484
+
485
+ // Save stateful data if enabled
486
+ if config.StateFullValidations {
487
+ if err := storageactions.Perform${functionName}Save(action, config.UniqueKey, normalizedPayload, config.Store, config.StorageConfig); err != nil {
488
+ // Log error but don't fail validation
489
+ if config.Debug {
490
+ fmt.Printf("Warning: Failed to save state: %v\\n", err)
491
+ }
492
+ }
493
+ }
494
+
495
+ return result, nil
496
+ }
497
+ `;
498
+ return imports + masterFunction;
499
+ }
500
+ generateGoMod(moduleName) {
501
+ moduleName = moduleName.replace(/[^a-zA-Z0-9_-]/g, "");
502
+ return `module ${moduleName}
503
+
504
+ go 1.21
505
+
506
+ require (
507
+ github.com/PaesslerAG/jsonpath v0.1.1
508
+ )
509
+ `;
510
+ }
511
+ }
@@ -0,0 +1,48 @@
1
+ package apitests
2
+
3
+ import (
4
+ "L1_validations/types"
5
+ "L1_validations/utils"
6
+ )
7
+
8
+ func {{apiName}}(input types.ValidationInput) []types.ValidationOutput {
9
+ totalResults := {{apiName}}_{{apiName}}Validations(input)
10
+
11
+ if !input.Config.Debug {
12
+ for i := range totalResults {
13
+ totalResults[i].DebugInfo = nil
14
+ }
15
+ }
16
+
17
+ if input.Config.HideParentErrors {
18
+ filtered := []types.ValidationOutput{}
19
+ for _, r := range totalResults {
20
+ if !(r.Valid == false && r.Description == "") {
21
+ filtered = append(filtered, r)
22
+ }
23
+ }
24
+ totalResults = filtered
25
+ }
26
+
27
+ if input.Config.OnlyInvalid {
28
+ invalid := []types.ValidationOutput{}
29
+ for _, r := range totalResults {
30
+ if !r.Valid {
31
+ invalid = append(invalid, r)
32
+ }
33
+ }
34
+ if len(invalid) == 0 {
35
+ for _, r := range totalResults {
36
+ if r.TestName == "{{apiName}}Validations" {
37
+ return []types.ValidationOutput{r}
38
+ }
39
+ }
40
+ panic("Critical: Overall test result not found")
41
+ }
42
+ return invalid
43
+ }
44
+
45
+ return totalResults
46
+ }
47
+
48
+ {{{functionCode}}}