ondc-code-generator 0.6.21 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.github/copilot-instructions.md +128 -0
  2. package/GOLANG_IMPLEMENTATION.md +156 -0
  3. package/README.md +4 -1
  4. package/alpha/possible-json-paths.json +3734 -0
  5. package/dist/generator/config-compiler.js +6 -0
  6. package/dist/generator/generators/golang/go-ast.d.ts +1 -0
  7. package/dist/generator/generators/golang/go-ast.js +60 -0
  8. package/dist/generator/generators/golang/go-generator.d.ts +23 -0
  9. package/dist/generator/generators/golang/go-generator.js +511 -0
  10. package/dist/generator/generators/golang/templates/api-test.mustache +48 -0
  11. package/dist/generator/generators/golang/templates/json-normalizer.mustache +46 -0
  12. package/dist/generator/generators/golang/templates/json-path-utils.mustache +21 -0
  13. package/dist/generator/generators/golang/templates/storage-templates/api-save.mustache +30 -0
  14. package/dist/generator/generators/golang/templates/storage-templates/index.mustache +41 -0
  15. package/dist/generator/generators/golang/templates/storage-templates/save-utils.mustache +37 -0
  16. package/dist/generator/generators/golang/templates/storage-templates/storage-helpers.mustache +51 -0
  17. package/dist/generator/generators/golang/templates/storage-templates/storage-interface.mustache +96 -0
  18. package/dist/generator/generators/golang/templates/storage-templates/storage-types.mustache +15 -0
  19. package/dist/generator/generators/golang/templates/test-config.mustache +39 -0
  20. package/dist/generator/generators/golang/templates/test-object.mustache +39 -0
  21. package/dist/generator/generators/golang/templates/validation-code.mustache +51 -0
  22. package/dist/generator/generators/golang/templates/validation-utils.mustache +246 -0
  23. package/dist/generator/generators/python/py-generator.js +1 -1
  24. package/dist/generator/generators/python/templates/json-path-utils.mustache +16 -1
  25. package/dist/generator/generators/python/templates/storage-templates/api-save.mustache +1 -1
  26. package/dist/generator/generators/typescript/templates/json-path-utils.mustache +25 -7
  27. package/dist/generator/generators/typescript/templates/storage-templates/api-save.mustache +1 -1
  28. package/dist/generator/generators/typescript/ts-generator.js +1 -1
  29. package/dist/index.d.ts +2 -1
  30. package/dist/index.js +2 -1
  31. package/dist/index.test.js +7 -1
  32. package/dist/types/compiler-types.d.ts +2 -1
  33. package/dist/types/compiler-types.js +1 -0
  34. package/dist/utils/fs-utils.js +40 -0
  35. package/package.json +2 -2
  36. package/sample.md +273 -0
@@ -0,0 +1,246 @@
1
+ package utils
2
+
3
+ import (
4
+ "fmt"
5
+ "regexp"
6
+ "strconv"
7
+ "time"
8
+ )
9
+
10
+ // UNUSED suppresses "declared and not used" compiler errors
11
+ func UNUSED(x ...interface{}) {}
12
+
13
+ // toString converts interface{} to string representation
14
+ func toString(v interface{}) string {
15
+ if v == nil {
16
+ return "null"
17
+ }
18
+ return fmt.Sprintf("%v", v)
19
+ }
20
+
21
+ // AreUnique checks if all values in the slice are unique
22
+ func AreUnique(operand []interface{}) bool {
23
+ seen := make(map[string]bool)
24
+ for _, v := range operand {
25
+ s := toString(v)
26
+ if seen[s] {
27
+ return false
28
+ }
29
+ seen[s] = true
30
+ }
31
+ return true
32
+ }
33
+
34
+ // ArePresent checks if there are no null, undefined, or empty strings in the slice
35
+ func ArePresent(operand []interface{}) bool {
36
+ return NoneIn(operand, []interface{}{"null", "undefined", ""}) && len(operand) > 0
37
+ }
38
+
39
+ // AllIn checks if all values in left are present in right
40
+ func AllIn(left, right []interface{}) bool {
41
+ if len(left) == 0 && len(right) != 0 {
42
+ return false
43
+ }
44
+ rightMap := make(map[string]bool)
45
+ for _, v := range right {
46
+ rightMap[toString(v)] = true
47
+ }
48
+ for _, v := range left {
49
+ if !rightMap[toString(v)] {
50
+ return false
51
+ }
52
+ }
53
+ return true
54
+ }
55
+
56
+ // AnyIn checks if any value in left is present in right
57
+ func AnyIn(left, right []interface{}) bool {
58
+ if len(left) == 0 && len(right) != 0 {
59
+ return false
60
+ }
61
+ rightMap := make(map[string]bool)
62
+ for _, v := range right {
63
+ rightMap[toString(v)] = true
64
+ }
65
+ for _, v := range left {
66
+ if rightMap[toString(v)] {
67
+ return true
68
+ }
69
+ }
70
+ return false
71
+ }
72
+
73
+ // NoneIn checks if none of the values in left are present in right
74
+ func NoneIn(left, right []interface{}) bool {
75
+ rightMap := make(map[string]bool)
76
+ for _, v := range right {
77
+ rightMap[toString(v)] = true
78
+ }
79
+ for _, v := range left {
80
+ if rightMap[toString(v)] {
81
+ return false
82
+ }
83
+ }
84
+ return true
85
+ }
86
+
87
+ // EqualTo checks if left and right are equal (same values in same order)
88
+ func EqualTo(left, right []interface{}) bool {
89
+ if len(left) != len(right) {
90
+ return false
91
+ }
92
+ for i, v := range left {
93
+ if toString(v) != toString(right[i]) {
94
+ return false
95
+ }
96
+ }
97
+ return true
98
+ }
99
+
100
+ // isISO8601 checks if a string is in ISO 8601 format
101
+ func isISO8601(s string) bool {
102
+ iso8601Regex := `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$`
103
+ matched, _ := regexp.MatchString(iso8601Regex, s)
104
+ if !matched {
105
+ return false
106
+ }
107
+ _, err := time.Parse(time.RFC3339, s)
108
+ return err == nil
109
+ }
110
+
111
+ // isNumber checks if a string can be parsed as a number
112
+ func isNumber(s string) bool {
113
+ _, err := strconv.ParseFloat(s, 64)
114
+ return err == nil
115
+ }
116
+
117
+ // areAllISO checks if all values in the slice are ISO 8601 dates
118
+ func areAllISO(arr []interface{}) bool {
119
+ for _, v := range arr {
120
+ if !isISO8601(toString(v)) {
121
+ return false
122
+ }
123
+ }
124
+ return true
125
+ }
126
+
127
+ // areAllNumbers checks if all values in the slice are numbers
128
+ func areAllNumbers(arr []interface{}) bool {
129
+ for _, v := range arr {
130
+ if !isNumber(toString(v)) {
131
+ return false
132
+ }
133
+ }
134
+ return true
135
+ }
136
+
137
+ // GreaterThan checks if all values in left are greater than corresponding values in right
138
+ func GreaterThan(left, right []interface{}) bool {
139
+ if areAllISO(left) && areAllISO(right) {
140
+ leftDates := make([]time.Time, len(left))
141
+ rightDates := make([]time.Time, len(right))
142
+
143
+ for i, d := range left {
144
+ leftDates[i], _ = time.Parse(time.RFC3339, toString(d))
145
+ }
146
+ for i, d := range right {
147
+ rightDates[i], _ = time.Parse(time.RFC3339, toString(d))
148
+ }
149
+
150
+ for i, ld := range leftDates {
151
+ if i >= len(rightDates) {
152
+ continue
153
+ }
154
+ if !ld.After(rightDates[i]) {
155
+ return false
156
+ }
157
+ }
158
+ return true
159
+ } else if areAllNumbers(left) && areAllNumbers(right) {
160
+ leftNumbers := make([]float64, len(left))
161
+ rightNumbers := make([]float64, len(right))
162
+
163
+ for i, n := range left {
164
+ leftNumbers[i], _ = strconv.ParseFloat(toString(n), 64)
165
+ }
166
+ for i, n := range right {
167
+ rightNumbers[i], _ = strconv.ParseFloat(toString(n), 64)
168
+ }
169
+
170
+ for i, ln := range leftNumbers {
171
+ if i >= len(rightNumbers) {
172
+ continue
173
+ }
174
+ if ln <= rightNumbers[i] {
175
+ return false
176
+ }
177
+ }
178
+ return true
179
+ }
180
+ return false
181
+ }
182
+
183
+ // LessThan checks if all values in left are less than corresponding values in right
184
+ func LessThan(left, right []interface{}) bool {
185
+ if areAllISO(left) && areAllISO(right) {
186
+ leftDates := make([]time.Time, len(left))
187
+ rightDates := make([]time.Time, len(right))
188
+
189
+ for i, d := range left {
190
+ leftDates[i], _ = time.Parse(time.RFC3339, toString(d))
191
+ }
192
+ for i, d := range right {
193
+ rightDates[i], _ = time.Parse(time.RFC3339, toString(d))
194
+ }
195
+
196
+ for i, ld := range leftDates {
197
+ if i >= len(rightDates) {
198
+ continue
199
+ }
200
+ if !ld.Before(rightDates[i]) {
201
+ return false
202
+ }
203
+ }
204
+ return true
205
+ } else if areAllNumbers(left) && areAllNumbers(right) {
206
+ leftNumbers := make([]float64, len(left))
207
+ rightNumbers := make([]float64, len(right))
208
+
209
+ for i, n := range left {
210
+ leftNumbers[i], _ = strconv.ParseFloat(toString(n), 64)
211
+ }
212
+ for i, n := range right {
213
+ rightNumbers[i], _ = strconv.ParseFloat(toString(n), 64)
214
+ }
215
+
216
+ for i, ln := range leftNumbers {
217
+ if i >= len(rightNumbers) {
218
+ continue
219
+ }
220
+ if ln >= rightNumbers[i] {
221
+ return false
222
+ }
223
+ }
224
+ return true
225
+ }
226
+ return false
227
+ }
228
+
229
+ // FollowRegex checks if all values in left match all regex patterns in regexArray
230
+ func FollowRegex(left, regexArray []interface{}) bool {
231
+ if len(left) == 0 && len(regexArray) != 0 {
232
+ return false
233
+ }
234
+ for _, pattern := range regexArray {
235
+ re, err := regexp.Compile(toString(pattern))
236
+ if err != nil {
237
+ return false
238
+ }
239
+ for _, v := range left {
240
+ if !re.MatchString(toString(v)) {
241
+ return false
242
+ }
243
+ }
244
+ }
245
+ return true
246
+ }
@@ -194,7 +194,7 @@ export class PythonGenerator extends CodeGenerator {
194
194
  for (const name of varNames) {
195
195
  const value = testObject[name];
196
196
  const final = typeof value === "string"
197
- ? `payload_utils["get_json_path"](${testObject[TestObjectSyntax.Name]}_obj, "${value}")`
197
+ ? `payload_utils["get_json_path"](${testObject[TestObjectSyntax.Name]}_obj, "${value}", True)`
198
198
  : this.convertArrayToStringPython(value);
199
199
  variables.push({
200
200
  name: name,
@@ -8,13 +8,24 @@ def is_list_of_strings_or_none(value: Any) -> bool:
8
8
  and all((v is None) or isinstance(v, str) for v in value)
9
9
  )
10
10
 
11
- def get_json_path(payload: Any, path: str) -> List[Any]:
11
+ def flatten_list(lst: List[Any]) -> List[Any]:
12
+ """Recursively flatten a nested list"""
13
+ result = []
14
+ for item in lst:
15
+ if isinstance(item, list):
16
+ result.extend(flatten_list(item))
17
+ else:
18
+ result.append(item)
19
+ return result
20
+
21
+ def get_json_path(payload: Any, path: str, flatten_result: bool = False) -> List[Any]:
12
22
  """
13
23
  Evaluate a JSONPath against `payload`.
14
24
 
15
25
  - If the result is a list that contains only strings and/or None,
16
26
  convert None -> "null".
17
27
  - Return [] when there are no matches.
28
+ - If flatten_result is True, flatten nested arrays completely.
18
29
  """
19
30
  expr = parse(path)
20
31
  matches = [m.value for m in expr.find(payload)] # extract raw values
@@ -22,6 +33,10 @@ def get_json_path(payload: Any, path: str) -> List[Any]:
22
33
  if is_list_of_strings_or_none(matches):
23
34
  matches = ["null" if v is None else v for v in matches]
24
35
 
36
+ # Flatten only if flag is true
37
+ if flatten_result:
38
+ matches = flatten_list(matches)
39
+
25
40
  # Explicitly mirror the TS return of [] for no matches
26
41
  return matches if len(matches) > 0 else []
27
42
 
@@ -21,7 +21,7 @@ async def save_function(payload: dict, unique_prefix: str, key: str, path: str,
21
21
  if path == "" or key == "_SELF":
22
22
  return
23
23
 
24
- value = get_json_path(payload, path)
24
+ value = get_json_path(payload, path, True)
25
25
  data = {
26
26
  "stored_from": "{{action}}_action",
27
27
  "key_path": path,
@@ -1,17 +1,35 @@
1
1
  import jsonpath from "jsonpath";
2
- function getJsonPath(payload: any, path: string) {
3
- let output = jsonpath.query(payload, path);
4
- if (isListOfStringsOrNull(output)) {
5
- output = output.map((o) => (o === null ? "null" : o));
6
- }
7
- return output.length === 0 ? [] : output;
8
- }
2
+
9
3
  function isListOfStringsOrNull(variable: any): boolean {
10
4
  return (
11
5
  Array.isArray(variable) &&
12
6
  variable.every((item) => item === null || typeof item === "string")
13
7
  );
14
8
  }
9
+
10
+ function getJsonPath(
11
+ payload: any,
12
+ path: string,
13
+ flattenResult: boolean = false
14
+ ) {
15
+ let output = jsonpath.query(payload, path);
16
+
17
+ if (isListOfStringsOrNull(output)) {
18
+ output = output.map((o) => {
19
+ if (o === null) return "null";
20
+ if (o === null) return "null";
21
+ return o;
22
+ });
23
+ }
24
+
25
+ // flatten only if flag is true
26
+ if (flattenResult) {
27
+ output = output.flat(Infinity);
28
+ }
29
+
30
+ return output.length === 0 ? [] : output;
31
+ }
32
+
15
33
  export default {
16
34
  getJsonPath,
17
35
  };
@@ -30,7 +30,7 @@ config: StorageConfig): Promise<void> {
30
30
  if(path === "" || key === "_SELF"){
31
31
  return;
32
32
  }
33
- const value = payloadUtils.getJsonPath(payload, path);
33
+ const value = payloadUtils.getJsonPath(payload, path,true);
34
34
  const data = {
35
35
  stored_from: "{{action}}_action",
36
36
  key_path: path,
@@ -182,7 +182,7 @@ export class TypescriptGenerator extends CodeGenerator {
182
182
  for (const name of varNames) {
183
183
  const value = testObject[name];
184
184
  const final = typeof value === "string"
185
- ? `payloadUtils.getJsonPath(testObj, "${value}")`
185
+ ? `payloadUtils.getJsonPath(testObj, "${value}",true)`
186
186
  : ConvertArrayToString(value);
187
187
  variables.push({
188
188
  name: name,
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import { ConfigCompiler } from "./generator/config-compiler.js";
2
- export { ConfigCompiler };
2
+ import { SupportedLanguages } from "./types/compiler-types.js";
3
+ export { ConfigCompiler, SupportedLanguages };
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  import { ConfigCompiler } from "./generator/config-compiler.js";
2
- export { ConfigCompiler };
2
+ import { SupportedLanguages } from "./types/compiler-types.js";
3
+ export { ConfigCompiler, SupportedLanguages };
@@ -1,4 +1,4 @@
1
- import { readFileSync } from "fs";
1
+ import { readFileSync, writeFileSync } from "fs";
2
2
  import path from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { ConfigCompiler } from "./generator/config-compiler.js";
@@ -12,10 +12,16 @@ const main = async () => {
12
12
  const buildYaml = readFileSync(buildPath, "utf-8");
13
13
  const valConfig = JSON.parse(readFileSync(valConfigPath, "utf-8"));
14
14
  await compiler.initialize(buildYaml);
15
+ const validPaths = await compiler.generateValidPaths();
16
+ writeFileSync(path.resolve(__dirname, "../alpha/possible-json-paths.json"), JSON.stringify(validPaths, null, 2), "utf-8");
15
17
  await compiler.generateCode(valConfig, "L1_validations", false, "./alpha/python/");
16
18
  const compilerTy = new ConfigCompiler(SupportedLanguages.Typescript);
17
19
  await compilerTy.initialize(buildYaml);
18
20
  await compilerTy.generateCode(valConfig, "L1_validations", false, "./alpha/typescript/");
21
+ await compilerTy.generateL0Schema("./alpha/typescript/L0_schema/");
22
+ const compilerGo = new ConfigCompiler(SupportedLanguages.Golang);
23
+ await compilerGo.initialize(buildYaml);
24
+ await compilerGo.generateCode(valConfig, "L1_validations", false, "./alpha/golang/");
19
25
  // JavaScript generation example
20
26
  // const compilerJs = new ConfigCompiler(SupportedLanguages.Javascript);
21
27
  // await compilerJs.initialize(buildYaml);
@@ -1,5 +1,6 @@
1
1
  export declare enum SupportedLanguages {
2
2
  Typescript = "typescript",
3
3
  Python = "python",
4
- Javascript = "javascript"
4
+ Javascript = "javascript",
5
+ Golang = "golang"
5
6
  }
@@ -3,4 +3,5 @@ export var SupportedLanguages;
3
3
  SupportedLanguages["Typescript"] = "typescript";
4
4
  SupportedLanguages["Python"] = "python";
5
5
  SupportedLanguages["Javascript"] = "javascript";
6
+ SupportedLanguages["Golang"] = "golang";
6
7
  })(SupportedLanguages || (SupportedLanguages = {}));
@@ -17,6 +17,10 @@ export async function formatCode(code, lang) {
17
17
  // Basic Python formatting - clean up extra whitespace and blank lines
18
18
  return formatPythonCode(code);
19
19
  }
20
+ if (lang === "go") {
21
+ // Basic Go formatting - clean up extra whitespace and blank lines
22
+ return formatGoCode(code);
23
+ }
20
24
  return await prettier.format(code, {
21
25
  parser: lang,
22
26
  tabWidth: 4,
@@ -57,6 +61,42 @@ function formatPythonCode(code) {
57
61
  }
58
62
  return cleanedLines.join("\n") + "\n";
59
63
  }
64
+ function formatGoCode(code) {
65
+ // Similar to Python formatting - basic cleanup
66
+ const lines = code.split("\n");
67
+ const formattedLines = [];
68
+ for (let i = 0; i < lines.length; i++) {
69
+ const line = lines[i];
70
+ // Skip lines that are only whitespace
71
+ if (line.trim() === "") {
72
+ // Only add empty line if the previous line wasn't empty
73
+ if (formattedLines.length > 0 &&
74
+ formattedLines[formattedLines.length - 1].trim() !== "") {
75
+ formattedLines.push("");
76
+ }
77
+ continue;
78
+ }
79
+ // Add the line as-is (preserve existing indentation)
80
+ formattedLines.push(line);
81
+ }
82
+ // Remove multiple consecutive empty lines
83
+ const cleanedLines = [];
84
+ let lastWasEmpty = false;
85
+ for (const line of formattedLines) {
86
+ const isEmpty = line.trim() === "";
87
+ if (isEmpty && lastWasEmpty) {
88
+ continue; // Skip consecutive empty lines
89
+ }
90
+ cleanedLines.push(line);
91
+ lastWasEmpty = isEmpty;
92
+ }
93
+ // Remove trailing empty lines
94
+ while (cleanedLines.length > 0 &&
95
+ cleanedLines[cleanedLines.length - 1].trim() === "") {
96
+ cleanedLines.pop();
97
+ }
98
+ return cleanedLines.join("\n") + "\n";
99
+ }
60
100
  export async function writeAndFormatCode(rootPath, relativeFilePath, content, lang) {
61
101
  const formattedCode = await formatCode(content, lang);
62
102
  writeFileWithFsExtra(rootPath, relativeFilePath, formattedCode);
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "ondc-code-generator",
3
- "version": "0.6.21",
3
+ "version": "0.7.0",
4
4
  "description": "generate code from build.yaml ",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "tsc && copyfiles -u 1 \"src/**/*.{yaml,html,css,mustache}\" dist/",
9
9
  "start": "node ./dist/index.js",
10
- "test": "npm run build && node ./dist/test.js",
10
+ "test": "npm run build && node ./dist/index.test.js",
11
11
  "dev": "nodemon",
12
12
  "clean": "rm -rf dist",
13
13
  "prepare": "npm run clean && npm run build"