ondc-code-generator 0.7.0 → 0.7.4
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/README.md +6 -0
- package/dist/bin/cli-tool.d.ts +70 -0
- package/dist/bin/cli-tool.js +310 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +112 -0
- package/dist/constants/syntax.js +26 -0
- package/dist/generator/config-compiler.d.ts +3 -2
- package/dist/generator/config-compiler.js +18 -6
- package/dist/generator/generators/{golang → go}/go-ast.js +2 -2
- package/dist/generator/generators/go/go-generator.d.ts +13 -0
- package/dist/generator/generators/go/go-generator.js +322 -0
- package/dist/generator/generators/go/templates/api-tests.mustache +65 -0
- package/dist/generator/generators/go/templates/go-mod.mustache +3 -0
- package/dist/generator/generators/go/templates/index.mustache +34 -0
- package/dist/generator/generators/go/templates/json-normalizer.mustache +155 -0
- package/dist/generator/generators/go/templates/json-path-utils.mustache +63 -0
- package/dist/generator/generators/go/templates/storage-templates/api-save-utils.mustache +84 -0
- package/dist/generator/generators/go/templates/storage-templates/api-save.mustache +44 -0
- package/dist/generator/generators/go/templates/storage-templates/index.mustache +72 -0
- package/dist/generator/generators/go/templates/storage-templates/save-utils.mustache +75 -0
- package/dist/generator/generators/{golang → go}/templates/storage-templates/storage-interface.mustache +33 -22
- package/dist/generator/generators/go/templates/test-config.mustache +62 -0
- package/dist/generator/generators/go/templates/test-object.mustache +52 -0
- package/dist/generator/generators/go/templates/validation-code.mustache +66 -0
- package/dist/generator/generators/go/templates/validation-utils.mustache +321 -0
- package/dist/generator/generators/typescript/templates/index.mustache +1 -1
- package/dist/generator/generators/typescript/ts-generator.js +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/index.test.js +1 -0
- package/dist/types/build.d.ts +2 -0
- package/dist/types/compiler-types.d.ts +1 -1
- package/dist/types/compiler-types.js +1 -1
- package/dist/utils/fs-utils.d.ts +1 -0
- package/dist/utils/fs-utils.js +18 -34
- package/dist/utils/general-utils/string-utils.d.ts +1 -0
- package/dist/utils/general-utils/string-utils.js +11 -0
- package/package.json +5 -1
- package/dist/generator/generators/golang/go-generator.d.ts +0 -23
- package/dist/generator/generators/golang/go-generator.js +0 -511
- package/dist/generator/generators/golang/templates/api-test.mustache +0 -48
- package/dist/generator/generators/golang/templates/json-normalizer.mustache +0 -46
- package/dist/generator/generators/golang/templates/json-path-utils.mustache +0 -21
- package/dist/generator/generators/golang/templates/storage-templates/api-save.mustache +0 -30
- package/dist/generator/generators/golang/templates/storage-templates/index.mustache +0 -41
- package/dist/generator/generators/golang/templates/storage-templates/save-utils.mustache +0 -37
- package/dist/generator/generators/golang/templates/storage-templates/storage-helpers.mustache +0 -51
- package/dist/generator/generators/golang/templates/storage-templates/storage-types.mustache +0 -15
- package/dist/generator/generators/golang/templates/test-config.mustache +0 -39
- package/dist/generator/generators/golang/templates/test-object.mustache +0 -39
- package/dist/generator/generators/golang/templates/validation-code.mustache +0 -51
- package/dist/generator/generators/golang/templates/validation-utils.mustache +0 -246
- /package/dist/generator/generators/{golang → go}/go-ast.d.ts +0 -0
|
@@ -1,511 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
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}}}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
package utils
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"strings"
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
// NormalizeKeys converts all keys in a JSON object to snake_case
|
|
9
|
-
func NormalizeKeys(data interface{}) interface{} {
|
|
10
|
-
switch v := data.(type) {
|
|
11
|
-
case map[string]interface{}:
|
|
12
|
-
normalized := make(map[string]interface{})
|
|
13
|
-
for key, value := range v {
|
|
14
|
-
normalizedKey := toSnakeCase(key)
|
|
15
|
-
normalized[normalizedKey] = NormalizeKeys(value)
|
|
16
|
-
}
|
|
17
|
-
return normalized
|
|
18
|
-
case []interface{}:
|
|
19
|
-
normalized := make([]interface{}, len(v))
|
|
20
|
-
for i, value := range v {
|
|
21
|
-
normalized[i] = NormalizeKeys(value)
|
|
22
|
-
}
|
|
23
|
-
return normalized
|
|
24
|
-
default:
|
|
25
|
-
return v
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// toSnakeCase converts a string to snake_case
|
|
30
|
-
func toSnakeCase(s string) string {
|
|
31
|
-
var result strings.Builder
|
|
32
|
-
for i, r := range s {
|
|
33
|
-
if i > 0 && r >= 'A' && r <= 'Z' {
|
|
34
|
-
result.WriteRune('_')
|
|
35
|
-
}
|
|
36
|
-
result.WriteRune(r)
|
|
37
|
-
}
|
|
38
|
-
return strings.ToLower(result.String())
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// FromJSON parses JSON bytes into interface{}
|
|
42
|
-
func FromJSON(data []byte) (interface{}, error) {
|
|
43
|
-
var result interface{}
|
|
44
|
-
err := json.Unmarshal(data, &result)
|
|
45
|
-
return result, err
|
|
46
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
package utils
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"github.com/PaesslerAG/jsonpath"
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
// GetJSONPath extracts values from a JSON object using JSONPath
|
|
8
|
-
func GetJSONPath(data interface{}, path string) []interface{} {
|
|
9
|
-
result, err := jsonpath.Get(path, data)
|
|
10
|
-
if err != nil {
|
|
11
|
-
return []interface{}{}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// If result is already a slice, return it
|
|
15
|
-
if slice, ok := result.([]interface{}); ok {
|
|
16
|
-
return slice
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Otherwise, wrap the single result in a slice
|
|
20
|
-
return []interface{}{result}
|
|
21
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
package storageactions
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"L1_validations/interfaces"
|
|
5
|
-
"L1_validations/types"
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
// Store_{{action}} saves data for the {{action}} action
|
|
9
|
-
func Store_{{action}}(uniquePrefix string, payload map[string]interface{}, store interfaces.StorageInterface, config types.StorageConfig) error {
|
|
10
|
-
{{#storeActions}}
|
|
11
|
-
if err := SaveFunction(payload, uniquePrefix, "{{{key}}}", "{{{value}}}", "{{action}}", store, config); err != nil {
|
|
12
|
-
return err
|
|
13
|
-
}
|
|
14
|
-
{{/storeActions}}
|
|
15
|
-
return nil
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// LoadFor_{{action}} loads data for the {{action}} action
|
|
19
|
-
func LoadFor_{{action}}(uniquePrefix string, store interfaces.StorageInterface) (map[string]interface{}, error) {
|
|
20
|
-
result := make(map[string]interface{})
|
|
21
|
-
|
|
22
|
-
{{#loadActions}}
|
|
23
|
-
{{{key}}}Data, err := LoadFunction(store, uniquePrefix, "{{{key}}}")
|
|
24
|
-
if err == nil {
|
|
25
|
-
result["{{{key}}}"] = {{{key}}}Data
|
|
26
|
-
}
|
|
27
|
-
{{/loadActions}}
|
|
28
|
-
|
|
29
|
-
return result, nil
|
|
30
|
-
}
|