ondc-code-generator 0.4.6 → 0.5.1
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/dist/constants/syntax.d.ts +1 -1
- package/dist/constants/syntax.js +98 -2
- package/dist/generator/config-compiler.js +7 -3
- package/dist/generator/generators/python/py-ast.d.ts +1 -0
- package/dist/generator/generators/python/py-ast.js +72 -0
- package/dist/generator/generators/python/py-generator.d.ts +19 -0
- package/dist/generator/generators/python/py-generator.js +247 -1
- package/dist/generator/generators/python/templates/api-test.mustache +29 -0
- package/dist/generator/generators/python/templates/api-tests-init.mustache +12 -0
- package/dist/generator/generators/python/templates/json-normalizer.mustache +86 -0
- package/dist/generator/generators/python/templates/json-path-utils.mustache +33 -0
- package/dist/generator/generators/python/templates/master-doc.mustache +40 -0
- package/dist/generator/generators/python/templates/requirements.mustache +2 -0
- package/dist/generator/generators/python/templates/test-config.mustache +62 -0
- package/dist/generator/generators/python/templates/test-object.mustache +29 -0
- package/dist/generator/generators/python/templates/validation-code.mustache +35 -0
- package/dist/generator/generators/python/templates/validation-utils.mustache +93 -0
- package/dist/generator/generators/typescript/templates/api-test.mustache +29 -1
- package/dist/generator/generators/typescript/templates/index.mustache +33 -0
- package/dist/generator/generators/typescript/templates/test-config.mustache +45 -5
- package/dist/generator/generators/typescript/templates/test-object.mustache +11 -1
- package/dist/generator/generators/typescript/templates/validation-code.mustache +12 -12
- package/dist/generator/generators/typescript/ts-generator.js +25 -13
- package/dist/generator/validators/tests-config/sub-validations.js +3 -3
- package/dist/index.js +15 -2
- package/dist/types/compiler-types.d.ts +2 -1
- package/dist/types/compiler-types.js +1 -0
- package/dist/utils/fs-utils.js +43 -0
- package/dist/utils/general-utils/string-utils.js +2 -2
- 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,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 {{
|
|
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,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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 [{
|
|
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
|
|
12
|
+
let allResults : validationOutput = [];
|
|
13
13
|
for (const fn of testFunctions) {
|
|
14
14
|
const subResult = fn(input);
|
|
15
|
-
|
|
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}}
|
|
@@ -23,13 +23,14 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
23
23
|
for (const key in testConfig) {
|
|
24
24
|
const testObjects = testConfig[key];
|
|
25
25
|
const betaConfig = {
|
|
26
|
-
[TestObjectSyntax.Name]: key,
|
|
26
|
+
[TestObjectSyntax.Name]: key + "Validations",
|
|
27
27
|
[TestObjectSyntax.Return]: testObjects,
|
|
28
28
|
};
|
|
29
29
|
const testFunction = await this.generateTestFunction(betaConfig);
|
|
30
30
|
const apiTestTemplate = readFileSync(path.resolve(__dirname, "./templates/api-test.mustache"), "utf-8");
|
|
31
31
|
const finalCode = Mustache.render(apiTestTemplate, {
|
|
32
32
|
functionCode: testFunction.code,
|
|
33
|
+
apiName: key,
|
|
33
34
|
});
|
|
34
35
|
await writeAndFormatCode(this.rootPath, `./api-tests/${key}.ts`, finalCode, "typescript");
|
|
35
36
|
}
|
|
@@ -63,6 +64,9 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
63
64
|
: undefined,
|
|
64
65
|
validationCode: await this.createValidationLogicCode(testObject),
|
|
65
66
|
successCode: testObject[TestObjectSyntax.SuccessCode] ?? 200,
|
|
67
|
+
errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
|
|
68
|
+
testName: testObject[TestObjectSyntax.Name],
|
|
69
|
+
TEST_OBJECT: `${JSON.stringify(testObject)}`,
|
|
66
70
|
};
|
|
67
71
|
return {
|
|
68
72
|
funcName: testObject[TestObjectSyntax.Name],
|
|
@@ -80,6 +84,8 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
80
84
|
returnStatement: returnStatement,
|
|
81
85
|
errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
|
|
82
86
|
errorDescription: this.CreateErrorMarkdown(testObject, skipList),
|
|
87
|
+
testName: testObject[TestObjectSyntax.Name],
|
|
88
|
+
TEST_OBJECT: `${JSON.stringify(testObject)}`,
|
|
83
89
|
});
|
|
84
90
|
}
|
|
85
91
|
else {
|
|
@@ -96,6 +102,8 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
96
102
|
isNested: true,
|
|
97
103
|
nestedFunctions: functionCodes.map((f) => f.code).join("\n"),
|
|
98
104
|
names: names,
|
|
105
|
+
testName: testObject[TestObjectSyntax.Name],
|
|
106
|
+
TEST_OBJECT: `${JSON.stringify(testObject)}`,
|
|
99
107
|
});
|
|
100
108
|
}
|
|
101
109
|
};
|
|
@@ -158,31 +166,35 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
158
166
|
}
|
|
159
167
|
generateIndexFile(apis, functionName = "L1Validations") {
|
|
160
168
|
functionName = functionName.replace(/[^a-zA-Z0-9_]/g, "");
|
|
161
|
-
|
|
169
|
+
let importsCode = apis
|
|
162
170
|
.map((api) => `import ${api} from "./api-tests/${api}";`)
|
|
163
171
|
.join("\n");
|
|
172
|
+
importsCode += `\nimport { ValidationConfig,validationOutput } from "./types/test-config";`;
|
|
173
|
+
importsCode += `\nimport normalizeKeys from "./utils/json-normalizer";`;
|
|
174
|
+
const masterTemplate = readFileSync(path.resolve(__dirname, "./templates/index.mustache"), "utf-8");
|
|
164
175
|
const masterFunction = `
|
|
165
|
-
export function perform${functionName}(action: string, payload: any,
|
|
166
|
-
|
|
167
|
-
|
|
176
|
+
export function perform${functionName}(action: string, payload: any, config?: Partial<ValidationConfig>, externalData: any = {}) {
|
|
177
|
+
const completeConfig: ValidationConfig = {
|
|
178
|
+
...{ onlyInvalid: true, standardLogs: false, hideParentErrors: true, _debug: false },
|
|
179
|
+
...config,
|
|
180
|
+
};
|
|
181
|
+
const normalizedPayload = normalizeKeys(JSON.parse(JSON.stringify(payload)));
|
|
182
|
+
externalData._SELF = normalizedPayload;
|
|
168
183
|
switch (action) {
|
|
169
184
|
${apis
|
|
170
185
|
.map((api) => `case "${api}": return ${api}({
|
|
171
186
|
payload: normalizedPayload,
|
|
172
187
|
externalData: externalData,
|
|
173
|
-
config:
|
|
174
|
-
runAllValidations: allErrors,
|
|
175
|
-
},
|
|
188
|
+
config: completeConfig,
|
|
176
189
|
});`)
|
|
177
190
|
.join("\n")}
|
|
178
191
|
default:
|
|
179
192
|
throw new Error("Action not found");
|
|
180
193
|
}
|
|
181
194
|
}`;
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
`;
|
|
195
|
+
return Mustache.render(masterTemplate, {
|
|
196
|
+
importsCode: importsCode,
|
|
197
|
+
masterFunction: masterFunction,
|
|
198
|
+
});
|
|
187
199
|
}
|
|
188
200
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TestObjectSyntax,
|
|
1
|
+
import { TestObjectSyntax, ReservedKeywords, ExternalDataSyntax, ConfigSyntax, } from "../../../constants/syntax.js";
|
|
2
2
|
import { buildAst } from "../../../services/return-complier/ast.js";
|
|
3
3
|
import { checkValidVariables } from "../../../services/return-complier/ast-functions/semantic-validations.js";
|
|
4
4
|
import { parseReturnInput } from "../../../services/return-complier/parser.js";
|
|
@@ -31,7 +31,7 @@ export class NameValidator extends TestObjectValidator {
|
|
|
31
31
|
if (name.length < 1) {
|
|
32
32
|
throw new Error(`${TestObjectSyntax.Name} can't be a non-empty string at path ${this.validationPath}`);
|
|
33
33
|
}
|
|
34
|
-
if (
|
|
34
|
+
if (ReservedKeywords.has(name)) {
|
|
35
35
|
throw new Error(`${TestObjectSyntax.Name} can't be a reserved keyword at path ${this.validationPath}`);
|
|
36
36
|
}
|
|
37
37
|
if (!isSnakeCase(name)) {
|
|
@@ -141,7 +141,7 @@ export class VariableValidator extends TestObjectValidator {
|
|
|
141
141
|
this.minimal = minimal;
|
|
142
142
|
}
|
|
143
143
|
validateKey(key) {
|
|
144
|
-
if (
|
|
144
|
+
if (ReservedKeywords.has(key)) {
|
|
145
145
|
throw new Error(`${key} can't be a reserved keyword at path ${this.validationPath}`);
|
|
146
146
|
}
|
|
147
147
|
if (key.includes(" ")) {
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export { ConfigCompiler };
|
|
|
7
7
|
// const __dirname = path.dirname(__filename);
|
|
8
8
|
// import { SupportedLanguages } from "./types/compiler-types.js";
|
|
9
9
|
// const main = async () => {
|
|
10
|
-
// const compiler = new ConfigCompiler(SupportedLanguages.
|
|
10
|
+
// const compiler = new ConfigCompiler(SupportedLanguages.Python);
|
|
11
11
|
// const buildPath = path.resolve(__dirname, "../samples/build.yaml");
|
|
12
12
|
// const valConfigPath = path.resolve(
|
|
13
13
|
// __dirname,
|
|
@@ -16,7 +16,20 @@ export { ConfigCompiler };
|
|
|
16
16
|
// const buildYaml = readFileSync(buildPath, "utf-8");
|
|
17
17
|
// const valConfig = JSON.parse(readFileSync(valConfigPath, "utf-8"));
|
|
18
18
|
// await compiler.initialize(buildYaml);
|
|
19
|
-
// await compiler.generateCode(
|
|
19
|
+
// await compiler.generateCode(
|
|
20
|
+
// valConfig,
|
|
21
|
+
// "L1_validations",
|
|
22
|
+
// false,
|
|
23
|
+
// "./alpha/python/"
|
|
24
|
+
// );
|
|
25
|
+
// const compilerTy = new ConfigCompiler(SupportedLanguages.Typescript);
|
|
26
|
+
// await compilerTy.initialize(buildYaml);
|
|
27
|
+
// await compilerTy.generateCode(
|
|
28
|
+
// valConfig,
|
|
29
|
+
// "L1_validations",
|
|
30
|
+
// false,
|
|
31
|
+
// "./alpha/typescript/"
|
|
32
|
+
// );
|
|
20
33
|
// };
|
|
21
34
|
// (async () => {
|
|
22
35
|
// await main();
|
package/dist/utils/fs-utils.js
CHANGED
|
@@ -9,11 +9,54 @@ export function writeFileWithFsExtra(rootPath, relativeFilePath, content) {
|
|
|
9
9
|
fs.outputFileSync(filePath, content);
|
|
10
10
|
}
|
|
11
11
|
export async function formatCode(code, lang) {
|
|
12
|
+
if (lang === "text") {
|
|
13
|
+
// No formatting for plain text files
|
|
14
|
+
return code;
|
|
15
|
+
}
|
|
16
|
+
if (lang == "python") {
|
|
17
|
+
// Basic Python formatting - clean up extra whitespace and blank lines
|
|
18
|
+
return formatPythonCode(code);
|
|
19
|
+
}
|
|
12
20
|
return await prettier.format(code, {
|
|
13
21
|
parser: lang,
|
|
14
22
|
tabWidth: 4,
|
|
15
23
|
});
|
|
16
24
|
}
|
|
25
|
+
function formatPythonCode(code) {
|
|
26
|
+
const lines = code.split("\n");
|
|
27
|
+
const formattedLines = [];
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const line = lines[i];
|
|
30
|
+
// Skip lines that are only whitespace
|
|
31
|
+
if (line.trim() === "") {
|
|
32
|
+
// Only add empty line if the previous line wasn't empty
|
|
33
|
+
if (formattedLines.length > 0 &&
|
|
34
|
+
formattedLines[formattedLines.length - 1].trim() !== "") {
|
|
35
|
+
formattedLines.push("");
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// Add the line as-is (preserve existing indentation)
|
|
40
|
+
formattedLines.push(line);
|
|
41
|
+
}
|
|
42
|
+
// Remove multiple consecutive empty lines
|
|
43
|
+
const cleanedLines = [];
|
|
44
|
+
let lastWasEmpty = false;
|
|
45
|
+
for (const line of formattedLines) {
|
|
46
|
+
const isEmpty = line.trim() === "";
|
|
47
|
+
if (isEmpty && lastWasEmpty) {
|
|
48
|
+
continue; // Skip consecutive empty lines
|
|
49
|
+
}
|
|
50
|
+
cleanedLines.push(line);
|
|
51
|
+
lastWasEmpty = isEmpty;
|
|
52
|
+
}
|
|
53
|
+
// Remove trailing empty lines
|
|
54
|
+
while (cleanedLines.length > 0 &&
|
|
55
|
+
cleanedLines[cleanedLines.length - 1].trim() === "") {
|
|
56
|
+
cleanedLines.pop();
|
|
57
|
+
}
|
|
58
|
+
return cleanedLines.join("\n") + "\n";
|
|
59
|
+
}
|
|
17
60
|
export async function writeAndFormatCode(rootPath, relativeFilePath, content, lang) {
|
|
18
61
|
const formattedCode = await formatCode(content, lang);
|
|
19
62
|
writeFileWithFsExtra(rootPath, relativeFilePath, formattedCode);
|