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.
Files changed (53) hide show
  1. package/README.md +6 -0
  2. package/dist/bin/cli-tool.d.ts +70 -0
  3. package/dist/bin/cli-tool.js +310 -0
  4. package/dist/bin/cli.d.ts +2 -0
  5. package/dist/bin/cli.js +112 -0
  6. package/dist/constants/syntax.js +26 -0
  7. package/dist/generator/config-compiler.d.ts +3 -2
  8. package/dist/generator/config-compiler.js +18 -6
  9. package/dist/generator/generators/{golang → go}/go-ast.js +2 -2
  10. package/dist/generator/generators/go/go-generator.d.ts +13 -0
  11. package/dist/generator/generators/go/go-generator.js +322 -0
  12. package/dist/generator/generators/go/templates/api-tests.mustache +65 -0
  13. package/dist/generator/generators/go/templates/go-mod.mustache +3 -0
  14. package/dist/generator/generators/go/templates/index.mustache +34 -0
  15. package/dist/generator/generators/go/templates/json-normalizer.mustache +155 -0
  16. package/dist/generator/generators/go/templates/json-path-utils.mustache +63 -0
  17. package/dist/generator/generators/go/templates/storage-templates/api-save-utils.mustache +84 -0
  18. package/dist/generator/generators/go/templates/storage-templates/api-save.mustache +44 -0
  19. package/dist/generator/generators/go/templates/storage-templates/index.mustache +72 -0
  20. package/dist/generator/generators/go/templates/storage-templates/save-utils.mustache +75 -0
  21. package/dist/generator/generators/{golang → go}/templates/storage-templates/storage-interface.mustache +33 -22
  22. package/dist/generator/generators/go/templates/test-config.mustache +62 -0
  23. package/dist/generator/generators/go/templates/test-object.mustache +52 -0
  24. package/dist/generator/generators/go/templates/validation-code.mustache +66 -0
  25. package/dist/generator/generators/go/templates/validation-utils.mustache +321 -0
  26. package/dist/generator/generators/typescript/templates/index.mustache +1 -1
  27. package/dist/generator/generators/typescript/ts-generator.js +2 -2
  28. package/dist/index.d.ts +5 -0
  29. package/dist/index.js +4 -0
  30. package/dist/index.test.js +1 -0
  31. package/dist/types/build.d.ts +2 -0
  32. package/dist/types/compiler-types.d.ts +1 -1
  33. package/dist/types/compiler-types.js +1 -1
  34. package/dist/utils/fs-utils.d.ts +1 -0
  35. package/dist/utils/fs-utils.js +18 -34
  36. package/dist/utils/general-utils/string-utils.d.ts +1 -0
  37. package/dist/utils/general-utils/string-utils.js +11 -0
  38. package/package.json +5 -1
  39. package/dist/generator/generators/golang/go-generator.d.ts +0 -23
  40. package/dist/generator/generators/golang/go-generator.js +0 -511
  41. package/dist/generator/generators/golang/templates/api-test.mustache +0 -48
  42. package/dist/generator/generators/golang/templates/json-normalizer.mustache +0 -46
  43. package/dist/generator/generators/golang/templates/json-path-utils.mustache +0 -21
  44. package/dist/generator/generators/golang/templates/storage-templates/api-save.mustache +0 -30
  45. package/dist/generator/generators/golang/templates/storage-templates/index.mustache +0 -41
  46. package/dist/generator/generators/golang/templates/storage-templates/save-utils.mustache +0 -37
  47. package/dist/generator/generators/golang/templates/storage-templates/storage-helpers.mustache +0 -51
  48. package/dist/generator/generators/golang/templates/storage-templates/storage-types.mustache +0 -15
  49. package/dist/generator/generators/golang/templates/test-config.mustache +0 -39
  50. package/dist/generator/generators/golang/templates/test-object.mustache +0 -39
  51. package/dist/generator/generators/golang/templates/validation-code.mustache +0 -51
  52. package/dist/generator/generators/golang/templates/validation-utils.mustache +0 -246
  53. /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,3 @@
1
+ module validationpkg
2
+
3
+ go 1.24.4
@@ -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
+ }