ondc-code-generator 0.4.5 → 0.5.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 (31) hide show
  1. package/dist/constants/syntax.d.ts +1 -1
  2. package/dist/constants/syntax.js +98 -2
  3. package/dist/generator/config-compiler.js +7 -3
  4. package/dist/generator/generators/python/py-ast.d.ts +1 -0
  5. package/dist/generator/generators/python/py-ast.js +72 -0
  6. package/dist/generator/generators/python/py-generator.d.ts +19 -0
  7. package/dist/generator/generators/python/py-generator.js +247 -1
  8. package/dist/generator/generators/python/templates/api-test.mustache +29 -0
  9. package/dist/generator/generators/python/templates/api-tests-init.mustache +12 -0
  10. package/dist/generator/generators/python/templates/json-normalizer.mustache +86 -0
  11. package/dist/generator/generators/python/templates/json-path-utils.mustache +33 -0
  12. package/dist/generator/generators/python/templates/master-doc.mustache +40 -0
  13. package/dist/generator/generators/python/templates/requirements.mustache +2 -0
  14. package/dist/generator/generators/python/templates/test-config.mustache +62 -0
  15. package/dist/generator/generators/python/templates/test-object.mustache +29 -0
  16. package/dist/generator/generators/python/templates/validation-code.mustache +35 -0
  17. package/dist/generator/generators/python/templates/validation-utils.mustache +93 -0
  18. package/dist/generator/generators/typescript/templates/api-test.mustache +29 -1
  19. package/dist/generator/generators/typescript/templates/index.mustache +33 -0
  20. package/dist/generator/generators/typescript/templates/json-normalizer.mustache +101 -36
  21. package/dist/generator/generators/typescript/templates/test-config.mustache +45 -5
  22. package/dist/generator/generators/typescript/templates/test-object.mustache +11 -1
  23. package/dist/generator/generators/typescript/templates/validation-code.mustache +12 -12
  24. package/dist/generator/generators/typescript/ts-generator.js +25 -13
  25. package/dist/generator/validators/tests-config/sub-validations.js +3 -3
  26. package/dist/index.js +15 -2
  27. package/dist/types/compiler-types.d.ts +2 -1
  28. package/dist/types/compiler-types.js +1 -0
  29. package/dist/utils/fs-utils.js +43 -0
  30. package/dist/utils/general-utils/string-utils.js +2 -2
  31. package/package.json +1 -1
@@ -0,0 +1,40 @@
1
+ """
2
+ Perform Level-1 validations for a given `action`.
3
+
4
+ Args:
5
+ action: string identifier of the action to validate against.
6
+ payload: Any JSON-like structure to validate (dict/list/nested).
7
+ config: Partial configuration. Can be:
8
+ - a plain dict (snake_case or camelCase keys),
9
+ - a dataclass/pydantic/object with fields,
10
+ - or a `ValidationConfig` TypedDict.
11
+ Missing fields are filled with defaults.
12
+ external_data: Optional dict of extra data available to rules.
13
+ `_SELF` is automatically set to the normalized payload.
14
+
15
+ Config fields (merged with defaults):
16
+ - only_invalid (bool, default: True)
17
+ - hide_parent_errors (bool, default: True)
18
+ - _debug (bool, default: False)
19
+
20
+ Returns:
21
+ ValidationOutput: a list of validation results, each shaped like:
22
+ {
23
+ "test_name": str,
24
+ "valid": bool,
25
+ "code": int,
26
+ "description"?: str,
27
+ "_debug_info"?: {
28
+ "fed_config"?: str,
29
+ "output_code"?: Any
30
+ }
31
+ }
32
+
33
+ Raises:
34
+ ValueError: if the action is unknown.
35
+
36
+ Example:
37
+ >>> out = perform_l1_validations("search", payload, {"only_invalid": False})
38
+ >>> out[0]["test_name"]
39
+ 'REGEX_CONTEXT_LOCATION_CITY_CODE'
40
+ """
@@ -0,0 +1,2 @@
1
+ # requirements.txt
2
+ jsonpath-ng>=1.6.0
@@ -0,0 +1,62 @@
1
+ from typing import Dict, Any, List
2
+ from typing import TypedDict, Optional
3
+
4
+
5
+ # External data type definitions
6
+ ExternalData = Dict[str, Any]
7
+
8
+
9
+ class ValidationConfig(TypedDict, total=False):
10
+ """Config options for validations.
11
+ Attributes:
12
+ only_invalid (bool): If True, only invalid results will be returned.
13
+ hide_parent_errors (Optional[bool]): If True, parent errors will be hidden.
14
+ _debug (Optional[bool]): If True, debug mode will be enabled.
15
+ """
16
+ only_invalid: Optional[bool]
17
+ hide_parent_errors: Optional[bool]
18
+ _debug: Optional[bool]
19
+
20
+ # Input structure for validation functions
21
+ ValidationInput = Dict[str, Any]
22
+
23
+ class DebugInfo(TypedDict, total=False):
24
+ """
25
+ Diagnostic information useful for debugging.
26
+
27
+ Attributes:
28
+ fed_config: The configuration used to generate the validation.
29
+ output_code: The identifier/code of the validation rule that was executed.
30
+ """
31
+ fed_config: Any
32
+ output_code: Any
33
+
34
+
35
+ class ValidationResult(TypedDict, total=False):
36
+ """
37
+ Represents the output of a single validation test.
38
+
39
+ Attributes:
40
+ test_name: The name of the validation test.
41
+ valid: Whether the test passed (True) or failed (False).
42
+ code: Numeric code representing the result or error type.
43
+ description: Optional. Additional details about the test result.
44
+ _debug_info: Optional. Diagnostic information useful for debugging.
45
+ """
46
+ test_name: str
47
+ valid: bool
48
+ code: int
49
+ description: Optional[str]
50
+ _debug_info: Optional[DebugInfo]
51
+
52
+
53
+ # Represents the output of a validation run:
54
+ # a list of individual validation results.
55
+ ValidationOutput = List[ValidationResult]
56
+
57
+ # Test function array type
58
+ TestFunctionArray = List[callable]
59
+
60
+ {{#externalData}}
61
+ # {{name}}: Any
62
+ {{/externalData}}
@@ -0,0 +1,29 @@
1
+ def {{name}}(input_data):
2
+ scope = payload_utils["get_json_path"](input_data["payload"], "{{{scopePath}}}")
3
+ sub_results = []
4
+ valid = True
5
+
6
+ for {{testName}}_obj in scope:
7
+ {{testName}}_obj["_EXTERNAL"] = input_data["external_data"]
8
+ {{#variables}}
9
+ {{name}} = {{{value}}}
10
+ {{/variables}}
11
+
12
+ {{#hasContinue}}
13
+ skip_check = {{{skipCheckStatement}}}
14
+ if skip_check:
15
+ continue
16
+ {{/hasContinue}}
17
+
18
+ {{{validationCode}}}
19
+ # del {{testName}}_obj["_EXTERNAL"]
20
+
21
+ return [{
22
+ "test_name": "{{testName}}",
23
+ "valid": valid,
24
+ "code": {{successCode}} if valid else {{errorCode}},
25
+ "_debug_info": {
26
+ "fed_config": r"""
27
+ {{{TEST_OBJECT}}}
28
+ """
29
+ }}] + sub_results
@@ -0,0 +1,35 @@
1
+ {{#isNested}}
2
+ {{{nestedFunctions}}}
3
+
4
+ test_functions = [
5
+ {{#names}}
6
+ {{name}},
7
+ {{/names}}
8
+ ]
9
+
10
+ all_results = []
11
+ for fn in test_functions:
12
+ sub_result = fn(input_data)
13
+ all_results.extend(sub_result)
14
+
15
+
16
+ sub_results = all_results
17
+ valid = all(r["valid"] for r in sub_results)
18
+ {{/isNested}}
19
+ {{^isNested}}
20
+ validate = {{{returnStatement}}}
21
+
22
+ if not validate:
23
+ del {{testName}}_obj["_EXTERNAL"]
24
+ return [{
25
+ "test_name": "{{testName}}",
26
+ "valid": False,
27
+ "code": {{errorCode}},
28
+ "description": r"""{{{errorDescription}}}""",
29
+ "_debug_info": {
30
+ "fed_config": r"""
31
+ {{{TEST_OBJECT}}}
32
+ """
33
+ }
34
+ }]
35
+ {{/isNested}}
@@ -0,0 +1,93 @@
1
+ import re
2
+ from datetime import datetime
3
+ from typing import List
4
+
5
+ def are_unique(operand: List[str]) -> bool:
6
+ return len(set(operand)) == len(operand)
7
+
8
+ def are_present(operand: List[str]) -> bool:
9
+ return none_in(operand, ["null", "undefined", None]) and len(operand) > 0
10
+
11
+ def all_in(left: List[str], right: List[str]) -> bool:
12
+ if len(left) == 0 and len(right) != 0:
13
+ return False
14
+ return all(v in right for v in left)
15
+
16
+ def any_in(left: List[str], right: List[str]) -> bool:
17
+ if len(left) == 0 and len(right) != 0:
18
+ return False
19
+ return any(v in right for v in left)
20
+
21
+ def none_in(left: List[str], right: List[str]) -> bool:
22
+ return all(v not in right for v in left)
23
+
24
+ def equal_to(left: List[str], right: List[str]) -> bool:
25
+ if len(left) != len(right):
26
+ return False
27
+ return all(v == right[i] for i, v in enumerate(left))
28
+
29
+ def is_iso8601(s: str) -> bool:
30
+ iso8601_regex = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$'
31
+ if not re.match(iso8601_regex, s):
32
+ return False
33
+ try:
34
+ datetime.fromisoformat(s.replace('Z', '+00:00'))
35
+ return True
36
+ except ValueError:
37
+ return False
38
+
39
+ def greater_than(left: List[str], right: List[str]) -> bool:
40
+ def are_all_iso(arr): return all(is_iso8601(v) for v in arr)
41
+ def are_all_numbers(arr): return all(is_number(v) for v in arr)
42
+
43
+ if are_all_iso(left) and are_all_iso(right):
44
+ left_dates = [datetime.fromisoformat(d.replace('Z', '+00:00')).timestamp() for d in left]
45
+ right_dates = [datetime.fromisoformat(d.replace('Z', '+00:00')).timestamp() for d in right]
46
+ return all(ld > right_dates[i] if i < len(right_dates) else True for i, ld in enumerate(left_dates))
47
+ elif are_all_numbers(left) and are_all_numbers(right):
48
+ left_numbers = [float(n) for n in left]
49
+ right_numbers = [float(n) for n in right]
50
+ return all(ln > right_numbers[i] if i < len(right_numbers) else True for i, ln in enumerate(left_numbers))
51
+ return False
52
+
53
+ def less_than(left: List[str], right: List[str]) -> bool:
54
+ def are_all_iso(arr): return all(is_iso8601(v) for v in arr)
55
+ def are_all_numbers(arr): return all(is_number(v) for v in arr)
56
+
57
+ if are_all_iso(left) and are_all_iso(right):
58
+ left_dates = [datetime.fromisoformat(d.replace('Z', '+00:00')).timestamp() for d in left]
59
+ right_dates = [datetime.fromisoformat(d.replace('Z', '+00:00')).timestamp() for d in right]
60
+ return all(ld < right_dates[i] if i < len(right_dates) else True for i, ld in enumerate(left_dates))
61
+ elif are_all_numbers(left) and are_all_numbers(right):
62
+ left_numbers = [float(n) for n in left]
63
+ right_numbers = [float(n) for n in right]
64
+ return all(ln < right_numbers[i] if i < len(right_numbers) else True for i, ln in enumerate(left_numbers))
65
+ return False
66
+
67
+ def follow_regex(left: List[str], regex_array: List[str]) -> bool:
68
+ if len(left) == 0 and len(regex_array) != 0:
69
+ return False
70
+ for regex in regex_array:
71
+ re_obj = re.compile(regex)
72
+ if any(not re_obj.match(v) for v in left):
73
+ return False
74
+ return True
75
+
76
+ def is_number(s: str) -> bool:
77
+ try:
78
+ float(s)
79
+ return True
80
+ except ValueError:
81
+ return False
82
+
83
+ validation_utils = {
84
+ "are_unique": are_unique,
85
+ "are_present": are_present,
86
+ "all_in": all_in,
87
+ "any_in": any_in,
88
+ "none_in": none_in,
89
+ "equal_to": equal_to,
90
+ "follow_regex": follow_regex,
91
+ "greater_than": greater_than,
92
+ "less_than": less_than,
93
+ }
@@ -4,4 +4,32 @@ import { testFunctionArray, validationInput, validationOutput } from "../types/t
4
4
 
5
5
 
6
6
 
7
- export default {{{functionCode}}}
7
+ export default function {{apiName}}(input: validationInput): validationOutput{
8
+
9
+ let totalResults = {{apiName}}Validations(input);
10
+
11
+ if (input.config._debug === false) {
12
+ totalResults.forEach((r) => {
13
+ delete r._debugInfo;
14
+ });
15
+ }
16
+ if(input.config.hideParentErrors === true) {
17
+ // delete results with valid false and no description
18
+ totalResults = totalResults.filter((r) => !(r.valid === false && !r.description));
19
+ }
20
+ if (input.config.onlyInvalid === true) {
21
+ const res = totalResults.filter((r) => r.valid === false);
22
+ if(res.length === 0) {
23
+ const targetSuccess = totalResults.find((r) => r.testName === "{{apiName}}_validations");
24
+ if(!targetSuccess) {
25
+ throw new Error("Critical: Overall test result not found");
26
+ }
27
+ return [targetSuccess];
28
+ }
29
+ return res;
30
+ }
31
+
32
+ return totalResults;
33
+ }
34
+
35
+ {{{functionCode}}}
@@ -0,0 +1,33 @@
1
+ {{{importsCode}}}
2
+
3
+ /**
4
+ * Perform Level-1 validations against a payload for a given action.
5
+ *
6
+ * @remarks
7
+ * **Output shape** — {@link validationOutput} is an array of:
8
+ * - `testName: string`
9
+ * - `valid: boolean`
10
+ * - `code: number`
11
+ * - `description?: string`
12
+ * - `_debugInfo?: { fedConfig?: string; outputCode?: unknown }`
13
+ *
14
+ * **Config** — {@link ValidationConfig} (partial accepted here):
15
+ * - `onlyInvalid` (default `true`)
16
+ * - `hideParentErrors` (default `true`)
17
+ * - `_debug` (default `false`)
18
+ *
19
+ * @param action - One of {@link Action}.
20
+ * @param payload - The JSON payload to validate.
21
+ * @param config - Partial {@link ValidationConfig}. Merged with defaults.
22
+ * @param externalData - Extra data accessible to rules (we set `_SELF` to the normalized payload).
23
+ * @returns {@link validationOutput}
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { performL1_validations } from "@your/pkg";
28
+ *
29
+ * const out = performL1_validations("search", payload, { onlyInvalid: false });
30
+ * // e.g. out[0] => { testName, valid, code, description?, _debugInfo? }
31
+ * ```
32
+ */
33
+ {{{masterFunction}}}
@@ -1,36 +1,101 @@
1
- export default function normalizeKeys(obj: any) {
2
- if (Array.isArray(obj)) {
3
- // Find all keys across all objects in the array
4
- const allKeys = new Set();
5
- obj.forEach((item) => {
6
- if (typeof item === "object" && item !== null) {
7
- Object.keys(item).forEach((key) => allKeys.add(key));
8
- }
9
- });
10
-
11
- // Add missing keys with null
12
- return obj.map((item) => {
13
- if (typeof item === "object" && item !== null) {
14
- const newItem = { ...item };
15
- allKeys.forEach((key: any) => {
16
- if (!(key in newItem)) {
17
- newItem[key] = null;
18
- }
19
- });
20
- // Recursively normalize nested objects/arrays
21
- for (const k in newItem) {
22
- newItem[k] = normalizeKeys(newItem[k]);
23
- }
24
- return newItem;
25
- }
26
- return item;
27
- });
28
- } else if (typeof obj === "object" && obj !== null) {
29
- const newObj: any = {};
30
- for (const key in obj) {
31
- newObj[key] = normalizeKeys(obj[key]);
32
- }
33
- return newObj;
34
- }
35
- return obj; // primitive values
36
- }
1
+ type JSONObject = Record<string, any>;
2
+
3
+ /**
4
+ * Normalize keys so that:
5
+ * - All objects with the same property name (e.g., "price") share the union of keys seen anywhere.
6
+ * - All objects inside the same array share the union of keys at that array level.
7
+ * Missing keys are filled with `null`.
8
+ */
9
+ export default function normalizeKeys(input: any): any {
10
+ // 1) Collect templates by property name: e.g., "price" -> Set<"currency" | "value" | ...>
11
+ const templatesByPropName: Record<string, Set<string>> = Object.create(null);
12
+
13
+ const collectTemplates = (node: any): void => {
14
+ if (Array.isArray(node)) {
15
+ // Recurse into array items
16
+ node.forEach(collectTemplates);
17
+
18
+ // Also collect a per-array union (used later when applying)
19
+ // We won't store this globally; we'll recompute per-array on apply.
20
+ return;
21
+ }
22
+ if (node && typeof node === "object") {
23
+ // For each property: if it's an object (non-array), record its keys under the property name
24
+ for (const [k, v] of Object.entries(node)) {
25
+ if (v && typeof v === "object" && !Array.isArray(v)) {
26
+ const set = (templatesByPropName[k] ||= new Set<string>());
27
+ Object.keys(v).forEach((childKey) => set.add(childKey));
28
+ }
29
+ collectTemplates(v);
30
+ }
31
+ }
32
+ };
33
+
34
+ // 2) Apply templates and within-array unions
35
+ const applyTemplates = (node: any): any => {
36
+ if (Array.isArray(node)) {
37
+ // Compute union of keys across all object elements for this array level
38
+ const arrayUnion = new Set<string>();
39
+ for (const item of node) {
40
+ if (item && typeof item === "object" && !Array.isArray(item)) {
41
+ Object.keys(item).forEach((k) => arrayUnion.add(k));
42
+ }
43
+ }
44
+
45
+ return node.map((item) => {
46
+ if (item && typeof item === "object" && !Array.isArray(item)) {
47
+ // Ensure array-level union keys
48
+ const next: JSONObject = { ...item };
49
+ arrayUnion.forEach((k) => {
50
+ if (!(k in next)) next[k] = null;
51
+ });
52
+
53
+ // Now apply templates per property name for nested objects
54
+ for (const [k, v] of Object.entries(next)) {
55
+ // If this property holds an object, align it to the template for that property name
56
+ if (v && typeof v === "object" && !Array.isArray(v)) {
57
+ next[k] = fillFromTemplate(k, v);
58
+ } else {
59
+ next[k] = applyTemplates(v);
60
+ }
61
+ }
62
+ return next;
63
+ }
64
+ return applyTemplates(item);
65
+ });
66
+ }
67
+
68
+ if (node && typeof node === "object") {
69
+ const out: JSONObject = {};
70
+ for (const [k, v] of Object.entries(node)) {
71
+ if (v && typeof v === "object" && !Array.isArray(v)) {
72
+ // Align object to the template for this property name (k)
73
+ out[k] = fillFromTemplate(k, v);
74
+ } else {
75
+ out[k] = applyTemplates(v);
76
+ }
77
+ }
78
+ return out;
79
+ }
80
+
81
+ return node; // primitives unchanged
82
+ };
83
+
84
+ // Helper: apply the template for a given property name `prop`
85
+ const fillFromTemplate = (prop: string, obj: any): any => {
86
+ const templ = templatesByPropName[prop];
87
+ // Recurse first on children so nested arrays/objects also normalize
88
+ const base: JSONObject = applyTemplates(obj);
89
+ if (!templ) return base; // no known template keys for this prop
90
+
91
+ const filled: JSONObject = { ...base };
92
+ templ.forEach((k) => {
93
+ if (!(k in filled)) filled[k] = null;
94
+ });
95
+ return filled;
96
+ };
97
+
98
+ // Run passes
99
+ collectTemplates(input);
100
+ return applyTemplates(input);
101
+ }
@@ -1,14 +1,54 @@
1
-
1
+ /**
2
+ * Configuration options for running validation routines.
3
+ *
4
+ * @property onlyInvalid - If true, only invalid results will be returned.
5
+ * @property hideParentErrors - Optional. Hides nested error details if set to true.
6
+ * @property _debug - Optional. Enables debug mode for additional diagnostic information.
7
+ */
2
8
  export interface ValidationConfig {
3
- runAllValidations: boolean;
9
+ onlyInvalid: boolean;
10
+ hideParentErrors: boolean;
11
+ _debug: boolean;
4
12
  }
5
13
 
14
+
15
+ /**
16
+ * Represents the output of a validation run.
17
+ *
18
+ * Each element in the array corresponds to a single validation test result.
19
+ *
20
+ * Object shape:
21
+ * ```ts
22
+ * {
23
+ * testName: string;
24
+ * valid: boolean;
25
+ * code: number;
26
+ * description?: string;
27
+ * _debugInfo?: {
28
+ * fedConfig: any;
29
+ * };
30
+ * }
31
+ * ```
32
+ *
33
+ * ### Properties
34
+ * - **testName** — The name of the validation test.
35
+ * - **valid** — Whether the test passed (`true`) or failed (`false`).
36
+ * - **code** — Numeric code representing the result or error type.
37
+ * - **description** — *(Optional)* Additional details about the test result.
38
+ * - **_debugInfo** — *(Optional)* Diagnostic information useful for debugging.
39
+ * - **fedConfig** — The configuration used to generate the validation.
40
+ */
6
41
  export type validationOutput = {
7
- valid: boolean;
8
- code: number;
9
- description?: string;
42
+ testName: string;
43
+ valid: boolean;
44
+ code: number;
45
+ description?: string;
46
+ _debugInfo?: {
47
+ fedConfig: any;
48
+ };
10
49
  }[];
11
50
 
51
+
12
52
  /*
13
53
  {% comment %} export type ExternalData = {
14
54
  {{#externalData}}
@@ -16,5 +16,15 @@ function {{name}}(input: validationInput): validationOutput {
16
16
  {{{validationCode}}}
17
17
  delete testObj._EXTERNAL;
18
18
  }
19
- return [{valid: valid,code: {{successCode}} },...subResults];
19
+ return [{
20
+ testName: "{{testName}}",
21
+ valid: valid,
22
+ code: valid ? {{successCode}} : {{errorCode}},
23
+ _debugInfo: {
24
+ fedConfig:
25
+ `
26
+ {{{TEST_OBJECT}}}
27
+ `,
28
+ }
29
+ },...subResults];
20
30
  }
@@ -9,20 +9,13 @@ const testFunctions: testFunctionArray = [
9
9
  {{/names}}
10
10
  ];
11
11
 
12
- let invalidResults: validationOutput = [];
12
+ let allResults : validationOutput = [];
13
13
  for (const fn of testFunctions) {
14
14
  const subResult = fn(input);
15
- // .filter(r => !r.valid);
16
- invalidResults = [...invalidResults, ...subResult];
17
- if(!input.config.runAllValidations && invalidResults.length > 0) {
18
- return invalidResults;
19
- }
20
- }
21
- if(invalidResults.length > 0) {
22
- // return invalidResults;
23
- subResults = invalidResults;
24
- valid = subResults.every(r => r.valid);
15
+ allResults = [...allResults , ...subResult];
25
16
  }
17
+ subResults = allResults ;
18
+ valid = subResults.every((r) => r.valid);
26
19
 
27
20
  {{/isNested}}
28
21
 
@@ -32,9 +25,16 @@ const validate = {{{returnStatement}}}
32
25
  if(!validate){
33
26
  delete testObj._EXTERNAL;
34
27
  return [{
28
+ testName: "{{testName}}",
35
29
  valid: false,
36
30
  code: {{errorCode}},
37
- description: `{{{errorDescription}}}`
31
+ description: `{{{errorDescription}}}`,
32
+ _debugInfo: {
33
+ fedConfig:
34
+ `
35
+ {{{TEST_OBJECT}}}
36
+ `,
37
+ }
38
38
  }]
39
39
  }
40
40
  {{/isNested}}