ondc-code-generator 0.8.0 → 0.8.2

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.
@@ -58,43 +58,54 @@ export class ConfigCompiler {
58
58
  };
59
59
  // };
60
60
  this.generateCode = async (valConfig, codeName = "L1-Validations", minimal = false, outputPath = "./", absolutePath = false) => {
61
- valConfig = JSON.parse(JSON.stringify(valConfig));
62
- if (this.generatorConfig?.duplicateVariablesInChildren) {
63
- valConfig = duplicateVariablesInChildren(valConfig);
64
- }
65
- if (minimal) {
66
- await this.withMinimalValidations(valConfig);
67
- }
68
- else {
69
- await this.performValidations(valConfig);
61
+ try {
62
+ console.log("[ CODE GENERATION ] Starting code generation...");
63
+ valConfig = JSON.parse(JSON.stringify(valConfig));
64
+ if (this.generatorConfig?.duplicateVariablesInChildren) {
65
+ valConfig = duplicateVariablesInChildren(valConfig);
66
+ }
67
+ if (minimal) {
68
+ await this.withMinimalValidations(valConfig);
69
+ }
70
+ else {
71
+ await this.performValidations(valConfig);
72
+ }
73
+ // Generate code based on the language
74
+ const targetPath = absolutePath
75
+ ? outputPath
76
+ : `${outputPath}generated/${codeName}`;
77
+ switch (this.language) {
78
+ case SupportedLanguages.Typescript:
79
+ await new TypescriptGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
80
+ codeName: codeName,
81
+ });
82
+ break;
83
+ case SupportedLanguages.Python:
84
+ await new PythonGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
85
+ codeName: codeName,
86
+ });
87
+ break;
88
+ case SupportedLanguages.Javascript:
89
+ await new JavascriptGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
90
+ codeName: codeName,
91
+ });
92
+ break;
93
+ case SupportedLanguages.Golang:
94
+ await new GoGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
95
+ codeName: codeName,
96
+ });
97
+ break;
98
+ default:
99
+ throw new Error("Language not supported");
100
+ }
101
+ console.log("[ CODE GENERATION ] Code generation completed successfully.");
70
102
  }
71
- // Generate code based on the language
72
- const targetPath = absolutePath
73
- ? outputPath
74
- : `${outputPath}generated/${codeName}`;
75
- switch (this.language) {
76
- case SupportedLanguages.Typescript:
77
- await new TypescriptGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
78
- codeName: codeName,
79
- });
80
- break;
81
- case SupportedLanguages.Python:
82
- await new PythonGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
83
- codeName: codeName,
84
- });
85
- break;
86
- case SupportedLanguages.Javascript:
87
- await new JavascriptGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
88
- codeName: codeName,
89
- });
90
- break;
91
- case SupportedLanguages.Golang:
92
- await new GoGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
93
- codeName: codeName,
94
- });
95
- break;
96
- default:
97
- throw new Error("Language not supported");
103
+ catch (e) {
104
+ console.error(`\n [ CODE GENERATION ERROR ] ${e?.message || e}\n`);
105
+ if (e?.stack) {
106
+ console.error("Stack trace:");
107
+ console.error(e.stack);
108
+ }
98
109
  }
99
110
  };
100
111
  this.generateL0Schema = async (outputPath = "./", type = "typescript", absolutePath = false) => {
@@ -82,13 +82,17 @@ export class GoGenerator extends CodeGenerator {
82
82
  const loadData = relevantSessionData[action] || {};
83
83
  const saveData = sessionData[action] || {};
84
84
  const saveCode = Mustache.render(saveActionTemplate, {
85
- storeActions: Object.keys(saveData).map((key) => {
85
+ storeActions: Object.keys(saveData)
86
+ .filter((f) => f !== "_SELF")
87
+ .map((key) => {
86
88
  return {
87
89
  key: key,
88
90
  value: saveData[key],
89
91
  };
90
92
  }),
91
- loadActions: Object.keys(loadData).map((key) => {
93
+ loadActions: Object.keys(loadData)
94
+ .filter((f) => f !== "_SELF")
95
+ .map((key) => {
92
96
  console.log(loadData[key]);
93
97
  return {
94
98
  key: loadData[key],
@@ -159,7 +163,7 @@ export class GoGenerator extends CodeGenerator {
159
163
  normalizedPayload := validationutils.NormalizeKeys(payload)
160
164
 
161
165
  // Set _SELF
162
- externalData.Self = normalizedPayload
166
+ externalData["_SELF"] = normalizedPayload
163
167
 
164
168
  // Load stateful data if needed
165
169
  if completeConfig.StateFullValidations {
@@ -256,7 +260,8 @@ ${importList.map((imp) => `\t${imp}`).join("\n")}
256
260
  for (const api of apis) {
257
261
  const keys = Object.keys(this.validationConfig[ConfigSyntax.SessionData][api]);
258
262
  for (const key of keys) {
259
- result.push({ name: key });
263
+ const goVarName = key;
264
+ result.push({ name: key, keyName: goVarName });
260
265
  }
261
266
  }
262
267
  result = result.filter((v) => v.name !== "_SELF");
@@ -287,7 +292,9 @@ ${importList.map((imp) => `\t${imp}`).join("\n")}
287
292
  }
288
293
  let final = "";
289
294
  if (value.includes("_EXTERNAL")) {
290
- final = `validationutils.GetJsonPath(input, "${value}",true)`;
295
+ // $._EXTERNAL.some.path -> $.some.path
296
+ const converted = value.replace("$._EXTERNAL", "$");
297
+ final = `validationutils.GetJsonPath(input.ExternalData, "${converted}",true)`;
291
298
  }
292
299
  else {
293
300
  final =
@@ -73,7 +73,7 @@ func resolveJSONPathData(path string, data interface{}) (string, interface{}, er
73
73
  if input, ok := data.(ValidationInput); ok {
74
74
  if strings.HasPrefix(path, "$._EXTERNAL._SELF") {
75
75
  resolvedPath := strings.Replace(path, "$._EXTERNAL._SELF", "$", 1)
76
- return resolvedPath, input.ExternalData.Self, nil
76
+ return resolvedPath, input.ExternalData["_SELF"], nil
77
77
  }else if( strings.HasPrefix(path, "$._EXTERNAL")){
78
78
  resolvedPath := strings.Replace(path, "$._EXTERNAL", "$", 1)
79
79
  return resolvedPath, input.ExternalData, nil
@@ -35,7 +35,7 @@ func LoadFor_{{action}}(
35
35
  if err != nil {
36
36
  return result, fmt.Errorf("failed to load key {{{key}}}: %w", err)
37
37
  }
38
- result.{{{key}}} = {{{key}}}_value
38
+ result["{{{key}}}"] = {{{key}}}_value
39
39
  {{/loadActions}}
40
40
 
41
41
  fmt.Printf("Loaded external data for action {{action}}: %+v\n", result)
@@ -43,13 +43,22 @@ type DebugInfo struct {
43
43
  FedConfig interface{} `json:"fedConfig"`
44
44
  }
45
45
 
46
- // ExternalData holds external data references for validation
47
- type ExternalData struct {
48
- Self interface{} `json:"_SELF,omitempty"`
49
- {{#externalData}}
50
- {{name}} []string `json:"{{name}},omitempty"`
51
- {{/externalData}}
52
- }
46
+ // ExternalData holds external data references for validation as a map structure.
47
+ //
48
+ // Expected keys:
49
+ // - "_SELF": (interface{}) The current validation context object
50
+ {{#externalData}}
51
+ // - "{{name}}": (any) {{keyName}} data reference
52
+ {{/externalData}}
53
+ //
54
+ // Example usage:
55
+ // data := ExternalData{
56
+ // "_SELF": currentObject,
57
+ {{#externalData}}
58
+ // "{{name}}": {{keyName}}Value,
59
+ {{/externalData}}
60
+ // }
61
+ type ExternalData = map[string]interface{}
53
62
 
54
63
  // ValidationInput represents the input data for validation functions
55
64
  type ValidationInput struct {
@@ -50,18 +50,102 @@ func ArePresent(operand []interface{}) bool {
50
50
  return NoneIn(operand, []interface{}{"null", "undefined"}) && len(strs) > 0
51
51
  }
52
52
 
53
+ // Lookup interface for abstraction
54
+ type lookup interface {
55
+ contains(string) bool
56
+ isEmpty() bool
57
+ }
58
+
59
+ // Map-based lookup (O(1))
60
+ type mapLookup struct {
61
+ items map[string]bool
62
+ }
63
+
64
+ func (m *mapLookup) contains(item string) bool {
65
+ return m.items[item]
66
+ }
67
+
68
+ func (m *mapLookup) isEmpty() bool {
69
+ return len(m.items) == 0
70
+ }
71
+
72
+ // Slice-based lookup (O(n))
73
+ type sliceLookup struct {
74
+ items []string
75
+ }
76
+
77
+ func (s *sliceLookup) contains(item string) bool {
78
+ for _, v := range s.items {
79
+ if v == item {
80
+ return true
81
+ }
82
+ }
83
+ return false
84
+ }
85
+
86
+ func (s *sliceLookup) isEmpty() bool {
87
+ return len(s.items) == 0
88
+ }
89
+
90
+ // Smart lookup creation - auto-detects and optimizes
91
+ func createLookup(right []interface{}) (lookup, bool) {
92
+ // Case 1: Check if right contains a single map
93
+ if len(right) == 1 {
94
+ switch m := right[0].(type) {
95
+ case map[string]interface{}:
96
+ result := make(map[string]bool, len(m))
97
+ for k := range m {
98
+ result[k] = true
99
+ }
100
+ return &mapLookup{items: result}, true
101
+ case map[string]bool:
102
+ return &mapLookup{items: m}, true
103
+ case map[string]string:
104
+ result := make(map[string]bool, len(m))
105
+ for k := range m {
106
+ result[k] = true
107
+ }
108
+ return &mapLookup{items: result}, true
109
+ }
110
+ }
111
+
112
+ // Case 2: Convert slice to string slice
113
+ rightStrs, ok := toStringSlice(right)
114
+ if !ok {
115
+ return nil, false
116
+ }
117
+
118
+ // Optimization: Convert large slices to maps for better performance
119
+ const threshold = 10
120
+ if len(rightStrs) > threshold {
121
+ m := make(map[string]bool, len(rightStrs))
122
+ for _, v := range rightStrs {
123
+ m[v] = true
124
+ }
125
+ return &mapLookup{items: m}, true
126
+ }
127
+
128
+ return &sliceLookup{items: rightStrs}, true
129
+ }
130
+
131
+ // Updated comparison functions
53
132
  func AllIn(left []interface{}, right []interface{}) bool {
54
133
  leftStrs, ok1 := toStringSlice(left)
55
- rightStrs, ok2 := toStringSlice(right)
56
- if !ok1 || !ok2 {
134
+ if !ok1 {
57
135
  return false
58
136
  }
59
-
60
- if len(leftStrs) == 0 && len(rightStrs) != 0 {
137
+
138
+ rightLookup, ok2 := createLookup(right)
139
+ if !ok2 {
61
140
  return false
62
141
  }
142
+
143
+ if len(leftStrs) == 0 && !rightLookup.isEmpty() {
144
+ return false
145
+ }
146
+
63
147
  for _, v := range leftStrs {
64
- if !contains(rightStrs, v) {
148
+ if !rightLookup.contains(v) {
65
149
  return false
66
150
  }
67
151
  }
@@ -70,16 +154,21 @@ func AllIn(left []interface{}, right []interface{}) bool {
70
154
 
71
155
  func AnyIn(left []interface{}, right []interface{}) bool {
72
156
  leftStrs, ok1 := toStringSlice(left)
73
- rightStrs, ok2 := toStringSlice(right)
74
- if !ok1 || !ok2 {
157
+ if !ok1 {
75
158
  return false
76
159
  }
77
-
78
- if len(leftStrs) == 0 && len(rightStrs) != 0 {
160
+
161
+ rightLookup, ok2 := createLookup(right)
162
+ if !ok2 {
79
163
  return false
80
164
  }
165
+
166
+ if len(leftStrs) == 0 && !rightLookup.isEmpty() {
167
+ return false
168
+ }
169
+
81
170
  for _, v := range leftStrs {
82
- if contains(rightStrs, v) {
171
+ if rightLookup.contains(v) {
83
172
  return true
84
173
  }
85
174
  }
@@ -88,19 +177,22 @@ func AnyIn(left []interface{}, right []interface{}) bool {
88
177
 
89
178
  func NoneIn(left []interface{}, right []interface{}) bool {
90
179
  leftStrs, ok1 := toStringSlice(left)
91
- rightStrs, ok2 := toStringSlice(right)
92
- if !ok1 || !ok2 {
180
+ if !ok1 {
93
181
  return false
94
182
  }
95
-
183
+
184
+ rightLookup, ok2 := createLookup(right)
185
+ if !ok2 {
186
+ return false
187
+ }
188
+
96
189
  for _, v := range leftStrs {
97
- if contains(rightStrs, v) {
190
+ if rightLookup.contains(v) {
98
191
  return false
99
192
  }
100
193
  }
101
194
  return true
102
195
  }
103
-
104
196
  func EqualTo(left []interface{}, right []interface{}) bool {
105
197
  leftStrs, ok1 := toStringSlice(left)
106
198
  rightStrs, ok2 := toStringSlice(right)
@@ -299,15 +391,6 @@ func isISO8601(str string) bool {
299
391
  return err == nil
300
392
  }
301
393
 
302
- func contains(slice []string, item string) bool {
303
- for _, v := range slice {
304
- if v == item {
305
- return true
306
- }
307
- }
308
- return false
309
- }
310
-
311
394
  // StringSliceToInterface converts []string to []interface{}
312
395
  func StringSliceToInterface(strs []string) []interface{} {
313
396
  result := make([]interface{}, len(strs))
@@ -23,7 +23,6 @@ export class PythonGenerator extends CodeGenerator {
23
23
  const tests = this.validationConfig[ConfigSyntax.Tests];
24
24
  const relevantSessionData = {};
25
25
  collectLoadData(tests, relevantSessionData);
26
- console.log("Relevant Session Data for Loading:", relevantSessionData);
27
26
  const sessionDataUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/save-utils.mustache"), "utf-8");
28
27
  const storageInterfaceTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-interface.mustache"), "utf-8");
29
28
  const storageTypesTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-types.mustache"), "utf-8");
@@ -24,8 +24,6 @@ export class TypescriptGenerator extends CodeGenerator {
24
24
  const tests = this.validationConfig[ConfigSyntax.Tests];
25
25
  const relevantSessionData = {};
26
26
  collectLoadData(tests, relevantSessionData);
27
- console.log("Relevant Session Data for Loading:", relevantSessionData);
28
- const actions = Object.keys(sessionData);
29
27
  const sessionDataUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/save-utils.mustache"), "utf-8");
30
28
  const storageInterfaceTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-interface.mustache"), "utf-8");
31
29
  const storageTypesTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-types.mustache"), "utf-8");
@@ -119,7 +119,12 @@ export class VariableValidator extends TestObjectValidator {
119
119
  if (this.targetObject[TestObjectSyntax.Scope]) {
120
120
  const scope = this.targetObject[TestObjectSyntax.Scope];
121
121
  const pathWithoutDollar = cleanseDollarDot(path);
122
- path = `${scope}.${pathWithoutDollar}`;
122
+ if (pathWithoutDollar.startsWith("[")) {
123
+ path = `${scope}${pathWithoutDollar}`;
124
+ }
125
+ else {
126
+ path = `${scope}.${pathWithoutDollar}`;
127
+ }
123
128
  }
124
129
  const replaced = replaceBracketsWithAsteriskNested(path);
125
130
  if (this.minimal)
@@ -153,6 +158,12 @@ export class VariableValidator extends TestObjectValidator {
153
158
  if (!definedExternalValues.includes(externalData)) {
154
159
  throw new Error(`${externalData} is not defined in ${ConfigSyntax.SessionData} data at path ${this.validationPath}`);
155
160
  }
161
+ if (!path.includes("_SELF")) {
162
+ if (this.targetObject[TestObjectSyntax.Description] === undefined ||
163
+ this.targetObject[TestObjectSyntax.Description] === "") {
164
+ throw new Error(`Variable with path: ${path} is using external data but ${TestObjectSyntax.Description} is not provided at path ${this.validationPath}`);
165
+ }
166
+ }
156
167
  }
157
168
  }
158
169
  export class ContinueValidator extends TestObjectValidator {
@@ -204,10 +215,10 @@ export class ReturnValidator extends TestObjectValidator {
204
215
  }
205
216
  function cleanseDollarDot(path) {
206
217
  if (path.startsWith(`$.`)) {
207
- return path.slice(2);
218
+ return path.slice(2).trim();
208
219
  }
209
220
  else if (path.startsWith(`$`)) {
210
- return path.slice(1);
221
+ return path.slice(1).trim();
211
222
  }
212
- return path;
223
+ return path.trim();
213
224
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ondc-code-generator",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "generate code from build.yaml ",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
package/sample.md CHANGED
@@ -271,3 +271,50 @@ This document outlines best practices for writing effective JVAL (JSON Validatio
271
271
  4. **Inconsistent naming** - stick to snake_case throughout
272
272
  5. **Hard-coded values in expressions** - use variables for maintainability
273
273
  6. **Too broad JSONPath selectors** - be as specific as possible
274
+
275
+ ## some complex test examples
276
+
277
+ ```json
278
+ {
279
+ "_NAME_": "ITEMS_RETURNABLE",
280
+ "_SCOPE_": "$.message.catalog['bpp/providers'][*].items[*]",
281
+ "typeCode": "$.tags[?(@.code=='type')].list[*].code",
282
+ "typeValue": "$.tags[?(@.code=='type')].list[*].value",
283
+ "validCode": ["type"],
284
+ "valueValue": ["customization"],
285
+ "attr": "$['@ondc/org/returnable']",
286
+ "_CONTINUE_": "validCode all in typeCode && valueValue all in typeValue",
287
+ "_RETURN_": "attr are present"
288
+ }
289
+ ```
290
+
291
+ ```json
292
+ {
293
+ "_TESTS_": {
294
+ "search": [
295
+ {
296
+ "_NAME_": "sample_testing",
297
+ "ids": "$._EXTERNAL.full_ids",
298
+ "var1": "$.context.domain",
299
+ "_RETURN_": "var1 all in ids"
300
+ }
301
+ ]
302
+ },
303
+ "_SESSION_DATA_": {
304
+ "search": {
305
+ "full_ids": null
306
+ }
307
+ }
308
+ }
309
+ ```
310
+
311
+ ```go
312
+
313
+ results, runErr := PerformL1_validations(action, payload, cfg, validationutils.ExternalData{
314
+ "full_ids" : map[string]bool{
315
+ "test": true,
316
+ "test2": false,
317
+ },
318
+ })
319
+
320
+ ```