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.
- 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/json-normalizer.mustache +101 -36
- 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,36 +1,101 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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}}
|