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
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { CodeGenerator, } from "../classes/abstract-generator.js";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import Mustache from "mustache";
|
|
6
|
+
import { ConfigSyntax, ExternalDataSyntax, TestObjectSyntax, } from "../../../constants/syntax.js";
|
|
7
|
+
import { writeAndFormatCode } from "../../../utils/fs-utils.js";
|
|
8
|
+
import { collectLoadData } from "../../../utils/config-utils/load-variables.js";
|
|
9
|
+
import { compileInputToGo } from "./go-ast.js";
|
|
10
|
+
import { getVariablesFromTest } from "../../../utils/general-utils/test-object-utils.js";
|
|
11
|
+
import { ConvertArrayToStringGoStyle } from "../../../utils/general-utils/string-utils.js";
|
|
12
|
+
import { markdownMessageGenerator } from "../documentation/markdown-message-generator.js";
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
const packageName = "validationpkg";
|
|
16
|
+
export class GoGenerator extends CodeGenerator {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
this.generateCode = async (codeConfig) => {
|
|
20
|
+
this.codeConfig = codeConfig;
|
|
21
|
+
const jsonPathUtilsCode = readFileSync(path.resolve(__dirname, "./templates/json-path-utils.mustache"), "utf-8");
|
|
22
|
+
const validationUtils = readFileSync(path.resolve(__dirname, "./templates/validation-utils.mustache"), "utf-8");
|
|
23
|
+
const typesTemplate = readFileSync(path.resolve(__dirname, "./templates/test-config.mustache"), "utf-8");
|
|
24
|
+
const normalizerTemplate = readFileSync(path.resolve(__dirname, "./templates/json-normalizer.mustache"), "utf-8");
|
|
25
|
+
const goMod = readFileSync(path.resolve(__dirname, "./templates/go-mod.mustache"), "utf-8");
|
|
26
|
+
const typesCode = Mustache.render(typesTemplate, {
|
|
27
|
+
externalData: this.getExternalKeys(),
|
|
28
|
+
});
|
|
29
|
+
writeAndFormatCode(this.rootPath, `./${packageName}/validationutils/json_path_utils.go`, jsonPathUtilsCode, "go");
|
|
30
|
+
writeAndFormatCode(this.rootPath, `./${packageName}/validationutils/validation_utils.go`, validationUtils, "go");
|
|
31
|
+
writeAndFormatCode(this.rootPath, `./${packageName}/validationutils/test-config.go`, typesCode, "go");
|
|
32
|
+
writeAndFormatCode(this.rootPath, `./${packageName}/validationutils/json_normalizer.go`, normalizerTemplate, "go");
|
|
33
|
+
await this.generateValidationCode();
|
|
34
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/main-validator.go`, this.generateIndexFile(Object.keys(this.validationConfig[ConfigSyntax.Tests]), codeConfig.codeName), "go");
|
|
35
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/go.mod`, goMod, "text");
|
|
36
|
+
await this.generateSessionDataCode();
|
|
37
|
+
};
|
|
38
|
+
this.generateTestFunction = async (testObject) => {
|
|
39
|
+
const template = readFileSync(path.resolve(__dirname, "./templates/test-object.mustache"), "utf-8");
|
|
40
|
+
const view = {
|
|
41
|
+
name: stringToCaps(testObject[TestObjectSyntax.Name]),
|
|
42
|
+
scopePath: testObject[TestObjectSyntax.Scope] ?? "$",
|
|
43
|
+
variables: this.createVariablesCode(testObject),
|
|
44
|
+
hasContinue: testObject[TestObjectSyntax.Continue] ? true : false,
|
|
45
|
+
skipCheckStatement: testObject[TestObjectSyntax.Continue]
|
|
46
|
+
? compileInputToGo(testObject[TestObjectSyntax.Continue])
|
|
47
|
+
: undefined,
|
|
48
|
+
validationCode: await this.createValidationLogicCode(testObject),
|
|
49
|
+
successCode: testObject[TestObjectSyntax.SuccessCode] ?? 200,
|
|
50
|
+
errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
|
|
51
|
+
testName: testObject[TestObjectSyntax.Name],
|
|
52
|
+
TEST_OBJECT: `${JSON.stringify(testObject)}`,
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
funcName: testObject[TestObjectSyntax.Name],
|
|
56
|
+
code: Mustache.render(template, view),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async generateSessionDataCode() {
|
|
61
|
+
if (!this.codeConfig) {
|
|
62
|
+
throw new Error("Code config is not set");
|
|
63
|
+
}
|
|
64
|
+
const sessionData = this.validationConfig[ConfigSyntax.SessionData];
|
|
65
|
+
const tests = this.validationConfig[ConfigSyntax.Tests];
|
|
66
|
+
const relevantSessionData = {};
|
|
67
|
+
collectLoadData(tests, relevantSessionData);
|
|
68
|
+
const sessionDataUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/save-utils.mustache"), "utf-8");
|
|
69
|
+
const storageInterfaceTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-interface.mustache"), "utf-8");
|
|
70
|
+
const indexTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/index.mustache"), "utf-8");
|
|
71
|
+
const saveActionTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/api-save.mustache"), "utf-8");
|
|
72
|
+
const saveActionUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/api-save-utils.mustache"), "utf-8");
|
|
73
|
+
const allActions = Object.keys(tests);
|
|
74
|
+
const indexCode = Mustache.render(indexTemplate, {
|
|
75
|
+
actions: Array.from(allActions).map((action) => {
|
|
76
|
+
return { action: action };
|
|
77
|
+
}),
|
|
78
|
+
functionName: this.codeConfig.codeName.replace(/[^a-zA-Z0-9_]/g, ""),
|
|
79
|
+
});
|
|
80
|
+
for (const action of allActions) {
|
|
81
|
+
const loadData = relevantSessionData[action] || {};
|
|
82
|
+
const saveData = sessionData[action] || {};
|
|
83
|
+
const saveCode = Mustache.render(saveActionTemplate, {
|
|
84
|
+
storeActions: Object.keys(saveData).map((key) => {
|
|
85
|
+
return {
|
|
86
|
+
key: key,
|
|
87
|
+
value: saveData[key],
|
|
88
|
+
};
|
|
89
|
+
}),
|
|
90
|
+
loadActions: Object.keys(loadData).map((key) => {
|
|
91
|
+
console.log(loadData[key]);
|
|
92
|
+
return {
|
|
93
|
+
key: loadData[key],
|
|
94
|
+
};
|
|
95
|
+
}),
|
|
96
|
+
action: action,
|
|
97
|
+
});
|
|
98
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/storageutils/${action}.go`, saveCode, "go");
|
|
99
|
+
}
|
|
100
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/storageutils/save_utils.go`, sessionDataUtilsTemplate, "go");
|
|
101
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/validationutils/storage-interface.go`, storageInterfaceTemplate, "go");
|
|
102
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/storageutils/index.go`, indexCode, "go");
|
|
103
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/storageutils/api_save_utils.go`, saveActionUtilsTemplate, "go");
|
|
104
|
+
}
|
|
105
|
+
async generateValidationCode() {
|
|
106
|
+
const testConfig = this.validationConfig[ConfigSyntax.Tests];
|
|
107
|
+
for (const key in testConfig) {
|
|
108
|
+
const testObjects = testConfig[key];
|
|
109
|
+
const betaConfig = {
|
|
110
|
+
[TestObjectSyntax.Name]: key + "Validations",
|
|
111
|
+
[TestObjectSyntax.Return]: testObjects,
|
|
112
|
+
};
|
|
113
|
+
const testFunction = await this.generateTestFunction(betaConfig);
|
|
114
|
+
const apiTestTemplate = readFileSync(path.resolve(__dirname, "./templates/api-tests.mustache"), "utf-8");
|
|
115
|
+
const finalCode = Mustache.render(apiTestTemplate, {
|
|
116
|
+
functionCode: testFunction.code,
|
|
117
|
+
apiName: stringToCaps(key),
|
|
118
|
+
});
|
|
119
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/jsonvalidations/${key}.go`, finalCode, "go");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
generateIndexFile(apis, functionName = "L1Validations") {
|
|
123
|
+
functionName = functionName.replace(/[^a-zA-Z0-9_]/g, "");
|
|
124
|
+
let importList = [
|
|
125
|
+
`"validationpkg/validationutils"`,
|
|
126
|
+
`"validationpkg/jsonvalidations"`,
|
|
127
|
+
`"fmt"`,
|
|
128
|
+
`"encoding/json"`,
|
|
129
|
+
`"validationpkg/storageutils"`,
|
|
130
|
+
];
|
|
131
|
+
const masterTemplate = readFileSync(path.resolve(__dirname, "./templates/index.mustache"), "utf-8");
|
|
132
|
+
const masterFunction = `func Perform${functionName}(
|
|
133
|
+
action string,
|
|
134
|
+
payload interface{},
|
|
135
|
+
config *validationutils.ValidationConfig,
|
|
136
|
+
externalData validationutils.ExternalData,
|
|
137
|
+
) ([]validationutils.ValidationOutput, error) {
|
|
138
|
+
completeConfig := getCompleteConfig(config)
|
|
139
|
+
|
|
140
|
+
// Validate stateful requirements
|
|
141
|
+
if completeConfig.StateFullValidations {
|
|
142
|
+
if completeConfig.Store == nil {
|
|
143
|
+
return nil, fmt.Errorf("stateful validations require a storage interface to be provided in the config")
|
|
144
|
+
}
|
|
145
|
+
if completeConfig.UniqueKey == nil || *completeConfig.UniqueKey == "" {
|
|
146
|
+
return nil, fmt.Errorf("stateful validations require a uniqueKey to be provided in the config")
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
normalizedPayload := validationutils.NormalizeKeys(payload)
|
|
152
|
+
|
|
153
|
+
// Set _SELF
|
|
154
|
+
externalData.Self = normalizedPayload
|
|
155
|
+
|
|
156
|
+
// Load stateful data if needed
|
|
157
|
+
if completeConfig.StateFullValidations {
|
|
158
|
+
loadedData, err := Perform${functionName}Load(action, *completeConfig.UniqueKey, completeConfig.Store)
|
|
159
|
+
if err != nil {
|
|
160
|
+
return nil, fmt.Errorf("failed to load stateful data: %w", err)
|
|
161
|
+
}
|
|
162
|
+
// Merge loaded data with external data
|
|
163
|
+
externalData = mergeExternalData(loadedData, externalData)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Create validation input
|
|
167
|
+
input := validationutils.ValidationInput {
|
|
168
|
+
Payload: normalizedPayload,
|
|
169
|
+
ExternalData: externalData,
|
|
170
|
+
Config: completeConfig,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Route to action-specific validation
|
|
174
|
+
switch action {
|
|
175
|
+
${apis
|
|
176
|
+
.map((api) => `
|
|
177
|
+
case "${api}":
|
|
178
|
+
return jsonvalidations.${stringToCaps(api)}_Tests(input)
|
|
179
|
+
`)
|
|
180
|
+
.join("\n")}
|
|
181
|
+
default:
|
|
182
|
+
return nil, fmt.Errorf("action not found: %s", action)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// getCompleteConfig returns a complete config with defaults
|
|
187
|
+
func getCompleteConfig(config *validationutils.ValidationConfig) validationutils.ValidationConfig {
|
|
188
|
+
if config == nil {
|
|
189
|
+
return validationutils.ValidationConfig{
|
|
190
|
+
OnlyInvalid: true,
|
|
191
|
+
HideParentErrors: true,
|
|
192
|
+
StateFullValidations: false,
|
|
193
|
+
Debug: false,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Return copy with defaults for unset fields
|
|
198
|
+
completeConfig := *config
|
|
199
|
+
// Go doesn't have a clean way to check if bool was explicitly set,
|
|
200
|
+
// so we assume false means "use default true" only if it seems intentional
|
|
201
|
+
// In practice, you might want to use pointers for optional bools
|
|
202
|
+
return completeConfig
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// mergeExternalData merges loaded data with provided external data
|
|
206
|
+
// using JSON marshal/unmarshal for a generic merge strategy.
|
|
207
|
+
// Non-null fields in provided override loaded fields.
|
|
208
|
+
func mergeExternalData(loaded, provided validationutils.ExternalData) validationutils.ExternalData {
|
|
209
|
+
// Convert to maps
|
|
210
|
+
loadedBytes, _ := json.Marshal(loaded)
|
|
211
|
+
providedBytes, _ := json.Marshal(provided)
|
|
212
|
+
|
|
213
|
+
var loadedMap, providedMap map[string]interface{}
|
|
214
|
+
json.Unmarshal(loadedBytes, &loadedMap)
|
|
215
|
+
json.Unmarshal(providedBytes, &providedMap)
|
|
216
|
+
|
|
217
|
+
// Merge provided into loaded
|
|
218
|
+
for key, value := range providedMap {
|
|
219
|
+
if value != nil {
|
|
220
|
+
loadedMap[key] = value
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Convert back
|
|
225
|
+
var result validationutils.ExternalData
|
|
226
|
+
mergedBytes, _ := json.Marshal(loadedMap)
|
|
227
|
+
json.Unmarshal(mergedBytes, &result)
|
|
228
|
+
|
|
229
|
+
return result
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
var Perform${functionName}Load = storageutils.Perform${functionName}Load
|
|
233
|
+
var Perform${functionName}Save = storageutils.Perform${functionName}Save
|
|
234
|
+
`;
|
|
235
|
+
const importCode = `import (
|
|
236
|
+
${importList.map((imp) => `\t${imp}`).join("\n")}
|
|
237
|
+
)`;
|
|
238
|
+
return Mustache.render(masterTemplate, {
|
|
239
|
+
importCode: importCode,
|
|
240
|
+
masterFunction: masterFunction,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
getExternalKeys() {
|
|
244
|
+
const apis = Object.keys(this.validationConfig[ConfigSyntax.SessionData]);
|
|
245
|
+
let result = [];
|
|
246
|
+
for (const api of apis) {
|
|
247
|
+
const keys = Object.keys(this.validationConfig[ConfigSyntax.SessionData][api]);
|
|
248
|
+
for (const key of keys) {
|
|
249
|
+
result.push({ name: key });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
result = result.filter((v) => v.name !== "_SELF");
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
createVariablesCode(testObject) {
|
|
256
|
+
const variables = [];
|
|
257
|
+
const varNames = getVariablesFromTest(testObject);
|
|
258
|
+
for (const name of varNames) {
|
|
259
|
+
const value = testObject[name];
|
|
260
|
+
const final = typeof value === "string"
|
|
261
|
+
? `validationutils.GetJsonPath(testObjMap, "${value}",true)`
|
|
262
|
+
: ConvertArrayToStringGoStyle(value);
|
|
263
|
+
variables.push({
|
|
264
|
+
name: name,
|
|
265
|
+
value: final,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return variables;
|
|
269
|
+
}
|
|
270
|
+
async createValidationLogicCode(testObject) {
|
|
271
|
+
const template = readFileSync(path.resolve(__dirname, "./templates/validation-code.mustache"), "utf-8");
|
|
272
|
+
const skip = testObject[TestObjectSyntax.Continue];
|
|
273
|
+
const skipList = skip ? [skip] : undefined;
|
|
274
|
+
if (typeof testObject[TestObjectSyntax.Return] === "string") {
|
|
275
|
+
const returnStatement = compileInputToGo(testObject[TestObjectSyntax.Return]);
|
|
276
|
+
let isStateFull = false;
|
|
277
|
+
for (const k in testObject) {
|
|
278
|
+
const value = testObject[k];
|
|
279
|
+
if (typeof value === "string") {
|
|
280
|
+
if (value.includes(`${ExternalDataSyntax}.`) &&
|
|
281
|
+
!value.includes("_SELF")) {
|
|
282
|
+
isStateFull = true;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return Mustache.render(template, {
|
|
288
|
+
isNested: false,
|
|
289
|
+
isStateFull: isStateFull,
|
|
290
|
+
returnStatement: returnStatement,
|
|
291
|
+
errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
|
|
292
|
+
errorDescription: this.CreateErrorMarkdown(testObject, skipList),
|
|
293
|
+
testName: testObject[TestObjectSyntax.Name],
|
|
294
|
+
TEST_OBJECT: `${JSON.stringify(testObject)}`,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const subObjects = testObject[TestObjectSyntax.Return];
|
|
299
|
+
const functionCodes = [];
|
|
300
|
+
for (const subObject of subObjects) {
|
|
301
|
+
const func = await this.generateTestFunction(subObject);
|
|
302
|
+
functionCodes.push(func);
|
|
303
|
+
}
|
|
304
|
+
const names = functionCodes.map((f) => {
|
|
305
|
+
return { name: f.funcName };
|
|
306
|
+
});
|
|
307
|
+
return Mustache.render(template, {
|
|
308
|
+
isNested: true,
|
|
309
|
+
nestedFunctions: functionCodes.map((f) => f.code).join("\n"),
|
|
310
|
+
names: names,
|
|
311
|
+
testName: testObject[TestObjectSyntax.Name],
|
|
312
|
+
TEST_OBJECT: `${JSON.stringify(testObject)}`,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
CreateErrorMarkdown(testObject, skipList) {
|
|
317
|
+
return markdownMessageGenerator(testObject[TestObjectSyntax.Return], testObject, testObject[TestObjectSyntax.Name], skipList);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function stringToCaps(str) {
|
|
321
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
322
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Code generated by github.com/ONDC-Official/automation-validation-compiler, DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
package jsonvalidations
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"validationpkg/validationutils"
|
|
7
|
+
"fmt"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func {{apiName}}_Tests(input validationutils.ValidationInput) ([]validationutils.ValidationOutput, error) {
|
|
11
|
+
totalResults, err := {{apiName}}Validations(input)
|
|
12
|
+
if err != nil {
|
|
13
|
+
return nil, err
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if !input.Config.Debug {
|
|
17
|
+
for i := range totalResults {
|
|
18
|
+
totalResults[i].DebugInfo = nil
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if input.Config.HideParentErrors {
|
|
23
|
+
// Delete results with valid false and no description
|
|
24
|
+
filtered := make([]validationutils.ValidationOutput, 0)
|
|
25
|
+
for _, r := range totalResults {
|
|
26
|
+
if !r.Valid && r.Description == "" {
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
filtered = append(filtered, r)
|
|
30
|
+
}
|
|
31
|
+
totalResults = filtered
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if input.Config.OnlyInvalid {
|
|
35
|
+
res := make([]validationutils.ValidationOutput, 0)
|
|
36
|
+
for _, r := range totalResults {
|
|
37
|
+
if !r.Valid {
|
|
38
|
+
res = append(res, r)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if len(res) == 0 {
|
|
43
|
+
// Find the overall test result
|
|
44
|
+
var targetSuccess *validationutils.ValidationOutput
|
|
45
|
+
for i := range totalResults {
|
|
46
|
+
if totalResults[i].TestName == "{{apiName}}Validations" {
|
|
47
|
+
targetSuccess = &totalResults[i]
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if targetSuccess == nil {
|
|
53
|
+
panic("Critical: Overall test result not found")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return []validationutils.ValidationOutput{*targetSuccess}, nil
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return res, nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return totalResults, nil
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
{{{functionCode}}}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Code generated by github.com/ONDC-Official/automation-validation-compiler, DO NOT EDIT.
|
|
2
|
+
package main
|
|
3
|
+
|
|
4
|
+
{{{importCode}}}
|
|
5
|
+
|
|
6
|
+
// PerformL1Validations performs Level-1 validations against a payload for a given action.
|
|
7
|
+
//
|
|
8
|
+
// Output shape - ValidationOutput is a slice of:
|
|
9
|
+
// - TestName: string
|
|
10
|
+
// - Valid: boolean
|
|
11
|
+
// - Code: number
|
|
12
|
+
// - Description: *string (optional)
|
|
13
|
+
// - DebugInfo: *DebugInfo (optional) with FedConfig
|
|
14
|
+
//
|
|
15
|
+
// Config - ValidationConfig (all fields optional):
|
|
16
|
+
// - OnlyInvalid (default true)
|
|
17
|
+
// - HideParentErrors (default true)
|
|
18
|
+
// - Debug (default false)
|
|
19
|
+
// - StateFullValidations (default false)
|
|
20
|
+
// - UniqueKey (optional)
|
|
21
|
+
// - Store (optional)
|
|
22
|
+
//
|
|
23
|
+
// Parameters:
|
|
24
|
+
// - action: The action name to validate against
|
|
25
|
+
// - payload: The JSON payload to validate (map[string]interface{} or struct)
|
|
26
|
+
// - config: Partial ValidationConfig. Merged with defaults
|
|
27
|
+
// - externalData: Extra data accessible to rules (Self will be set to normalized payload)
|
|
28
|
+
//
|
|
29
|
+
// Returns: []ValidationOutput
|
|
30
|
+
//
|
|
31
|
+
// Example:
|
|
32
|
+
// results := PerformL1Validations("search", payload, ValidationConfig{OnlyInvalid: false}, ExternalData{})
|
|
33
|
+
// // results[0] => ValidationOutput{ TestName, Valid, Code, Description, DebugInfo }
|
|
34
|
+
{{{masterFunction}}}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Code generated by github.com/ONDC-Official/automation-validation-compiler, DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
package validationutils
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"github.com/bytedance/sonic"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// NormalizeKeys normalizes JSON structures so that:
|
|
10
|
+
// - All objects with the same property name share the union of keys seen anywhere
|
|
11
|
+
// - All objects inside the same array share the union of keys at that array level
|
|
12
|
+
// - Missing keys are filled with nil
|
|
13
|
+
func NormalizeKeys(input interface{}) interface{} {
|
|
14
|
+
// Step 1: Collect templates by property name
|
|
15
|
+
templatesByPropName := make(map[string]map[string]struct{})
|
|
16
|
+
collectTemplates(input, templatesByPropName)
|
|
17
|
+
|
|
18
|
+
// Step 2: Apply templates and within-array unions
|
|
19
|
+
return applyTemplates(input, templatesByPropName)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// collectTemplates walks the data structure and collects all keys for each property name
|
|
23
|
+
func collectTemplates(node interface{}, templates map[string]map[string]struct{}) {
|
|
24
|
+
switch v := node.(type) {
|
|
25
|
+
case []interface{}:
|
|
26
|
+
// Recurse into array items
|
|
27
|
+
for _, item := range v {
|
|
28
|
+
collectTemplates(item, templates)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
case map[string]interface{}:
|
|
32
|
+
// For each property: if it's an object (non-array), record its keys
|
|
33
|
+
for propName, propValue := range v {
|
|
34
|
+
if obj, ok := propValue.(map[string]interface{}); ok {
|
|
35
|
+
// Initialize set for this property name if needed
|
|
36
|
+
if templates[propName] == nil {
|
|
37
|
+
templates[propName] = make(map[string]struct{})
|
|
38
|
+
}
|
|
39
|
+
// Add all keys from this object to the template
|
|
40
|
+
for childKey := range obj {
|
|
41
|
+
templates[propName][childKey] = struct{}{}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Recurse into the value
|
|
45
|
+
collectTemplates(propValue, templates)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// applyTemplates applies the collected templates and array-level unions
|
|
51
|
+
func applyTemplates(node interface{}, templates map[string]map[string]struct{}) interface{} {
|
|
52
|
+
switch v := node.(type) {
|
|
53
|
+
case []interface{}:
|
|
54
|
+
// Compute union of keys across all object elements at this array level
|
|
55
|
+
arrayUnion := make(map[string]struct{})
|
|
56
|
+
for _, item := range v {
|
|
57
|
+
if obj, ok := item.(map[string]interface{}); ok {
|
|
58
|
+
for key := range obj {
|
|
59
|
+
arrayUnion[key] = struct{}{}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Apply union to each array element
|
|
65
|
+
result := make([]interface{}, len(v))
|
|
66
|
+
for i, item := range v {
|
|
67
|
+
if obj, ok := item.(map[string]interface{}); ok {
|
|
68
|
+
// Create new object with array union keys
|
|
69
|
+
next := make(map[string]interface{})
|
|
70
|
+
|
|
71
|
+
// Copy existing keys
|
|
72
|
+
for k, val := range obj {
|
|
73
|
+
next[k] = val
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Add missing keys from array union
|
|
77
|
+
for key := range arrayUnion {
|
|
78
|
+
if _, exists := next[key]; !exists {
|
|
79
|
+
next[key] = nil
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Now apply templates per property name for nested objects
|
|
84
|
+
for propName, propValue := range next {
|
|
85
|
+
if nestedObj, ok := propValue.(map[string]interface{}); ok {
|
|
86
|
+
// Align to template for this property name
|
|
87
|
+
next[propName] = fillFromTemplate(propName, nestedObj, templates)
|
|
88
|
+
} else {
|
|
89
|
+
// Recurse for arrays or other structures
|
|
90
|
+
next[propName] = applyTemplates(propValue, templates)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
result[i] = next
|
|
94
|
+
} else {
|
|
95
|
+
// Not an object, just recurse
|
|
96
|
+
result[i] = applyTemplates(item, templates)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
case map[string]interface{}:
|
|
102
|
+
out := make(map[string]interface{})
|
|
103
|
+
for propName, propValue := range v {
|
|
104
|
+
if nestedObj, ok := propValue.(map[string]interface{}); ok {
|
|
105
|
+
// Align object to the template for this property name
|
|
106
|
+
out[propName] = fillFromTemplate(propName, nestedObj, templates)
|
|
107
|
+
} else {
|
|
108
|
+
out[propName] = applyTemplates(propValue, templates)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return out
|
|
112
|
+
|
|
113
|
+
default:
|
|
114
|
+
// Primitives unchanged
|
|
115
|
+
return v
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// fillFromTemplate applies the template for a given property name
|
|
120
|
+
func fillFromTemplate(propName string, obj map[string]interface{}, templates map[string]map[string]struct{}) interface{} {
|
|
121
|
+
// First recurse on children so nested arrays/objects also normalize
|
|
122
|
+
base := applyTemplates(obj, templates).(map[string]interface{})
|
|
123
|
+
|
|
124
|
+
// Get template for this property name
|
|
125
|
+
template, hasTemplate := templates[propName]
|
|
126
|
+
if !hasTemplate {
|
|
127
|
+
return base // No known template keys for this prop
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Fill missing keys with nil
|
|
131
|
+
filled := make(map[string]interface{})
|
|
132
|
+
for k, v := range base {
|
|
133
|
+
filled[k] = v
|
|
134
|
+
}
|
|
135
|
+
for key := range template {
|
|
136
|
+
if _, exists := filled[key]; !exists {
|
|
137
|
+
filled[key] = nil
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return filled
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// DeepCloneJSON creates a deep clone of a JSON-serializable structure using sonic
|
|
145
|
+
func DeepCloneJSON(v interface{}) interface{} {
|
|
146
|
+
b, err := sonic.Marshal(v)
|
|
147
|
+
if err != nil {
|
|
148
|
+
panic(err) // or handle error
|
|
149
|
+
}
|
|
150
|
+
var out interface{}
|
|
151
|
+
if err := sonic.Unmarshal(b, &out); err != nil {
|
|
152
|
+
panic(err)
|
|
153
|
+
}
|
|
154
|
+
return out
|
|
155
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Code generated by github.com/ONDC-Official/automation-validation-compiler, DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
package validationutils
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"github.com/AsaiYusuke/jsonpath"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// isListOfStringsOrNull checks if the variable is a slice containing only strings or nil values
|
|
10
|
+
func isListOfStringsOrNull(variable interface{}) bool {
|
|
11
|
+
slice, ok := variable.([]interface{})
|
|
12
|
+
if !ok {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for _, item := range slice {
|
|
17
|
+
if item == nil {
|
|
18
|
+
continue
|
|
19
|
+
}
|
|
20
|
+
if _, isString := item.(string); !isString {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// GetJsonPath queries a payload using JSONPath and returns the results
|
|
28
|
+
func GetJsonPath(payload interface{}, path string, flattenResult bool) []interface{} {
|
|
29
|
+
output, err := jsonpath.Retrieve(path, payload)
|
|
30
|
+
if err != nil {
|
|
31
|
+
return []interface{}{}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if isListOfStringsOrNull(output) {
|
|
35
|
+
for i, item := range output {
|
|
36
|
+
if item == nil {
|
|
37
|
+
output[i] = "null"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if flattenResult {
|
|
43
|
+
output = flattenSlice(output)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if len(output) == 0 {
|
|
47
|
+
return []interface{}{}
|
|
48
|
+
}
|
|
49
|
+
return output
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// flattenSlice recursively flattens nested slices
|
|
53
|
+
func flattenSlice(slice []interface{}) []interface{} {
|
|
54
|
+
result := make([]interface{}, 0)
|
|
55
|
+
for _, item := range slice {
|
|
56
|
+
if nestedSlice, ok := item.([]interface{}); ok {
|
|
57
|
+
result = append(result, flattenSlice(nestedSlice)...)
|
|
58
|
+
} else {
|
|
59
|
+
result = append(result, item)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result
|
|
63
|
+
}
|