ondc-code-generator 0.8.8 → 0.8.9

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 (109) hide show
  1. package/README.md +1 -1
  2. package/alpha/page/index.html +6137 -0
  3. package/alpha/page/style.css +204 -0
  4. package/alpha/readme.md +5939 -0
  5. package/alpha/validPaths.json +14351 -0
  6. package/alpha/validationpkg/examples/search.json +143 -0
  7. package/alpha/validationpkg/examples_output/search/case-001/output.json +12 -0
  8. package/alpha/validationpkg/go.mod +8 -0
  9. package/alpha/validationpkg/go.sum +4 -0
  10. package/dist/bin/cli.js +2 -0
  11. package/dist/generator/config-compiler.js +4 -0
  12. package/dist/types/compiler-types.d.ts +2 -1
  13. package/dist/types/compiler-types.js +1 -0
  14. package/docs/jval-dsl.md +913 -0
  15. package/package.json +1 -1
  16. package/alpha/golang/newPkg/go.mod +0 -3
  17. package/alpha/golang/newPkg/jsonvalidations/cancel.go +0 -1289
  18. package/alpha/golang/newPkg/jsonvalidations/confirm.go +0 -9121
  19. package/alpha/golang/newPkg/jsonvalidations/init.go +0 -4864
  20. package/alpha/golang/newPkg/jsonvalidations/issue.go +0 -4868
  21. package/alpha/golang/newPkg/jsonvalidations/on_cancel.go +0 -7111
  22. package/alpha/golang/newPkg/jsonvalidations/on_confirm.go +0 -8903
  23. package/alpha/golang/newPkg/jsonvalidations/on_init.go +0 -4445
  24. package/alpha/golang/newPkg/jsonvalidations/on_issue.go +0 -2828
  25. package/alpha/golang/newPkg/jsonvalidations/on_issue_status.go +0 -1938
  26. package/alpha/golang/newPkg/jsonvalidations/on_search.go +0 -3356
  27. package/alpha/golang/newPkg/jsonvalidations/on_status.go +0 -8129
  28. package/alpha/golang/newPkg/jsonvalidations/on_track.go +0 -1415
  29. package/alpha/golang/newPkg/jsonvalidations/on_update.go +0 -8700
  30. package/alpha/golang/newPkg/jsonvalidations/search.go +0 -3585
  31. package/alpha/golang/newPkg/jsonvalidations/status.go +0 -1073
  32. package/alpha/golang/newPkg/jsonvalidations/track.go +0 -1073
  33. package/alpha/golang/newPkg/jsonvalidations/update.go +0 -3012
  34. package/alpha/golang/newPkg/main-validator.go +0 -196
  35. package/alpha/golang/newPkg/main-validator_test.go +0 -165
  36. package/alpha/golang/newPkg/storageutils/api_save_utils.go +0 -83
  37. package/alpha/golang/newPkg/storageutils/cancel.go +0 -30
  38. package/alpha/golang/newPkg/storageutils/confirm.go +0 -30
  39. package/alpha/golang/newPkg/storageutils/index.go +0 -132
  40. package/alpha/golang/newPkg/storageutils/init.go +0 -30
  41. package/alpha/golang/newPkg/storageutils/issue.go +0 -30
  42. package/alpha/golang/newPkg/storageutils/on_cancel.go +0 -30
  43. package/alpha/golang/newPkg/storageutils/on_confirm.go +0 -30
  44. package/alpha/golang/newPkg/storageutils/on_init.go +0 -30
  45. package/alpha/golang/newPkg/storageutils/on_issue.go +0 -30
  46. package/alpha/golang/newPkg/storageutils/on_issue_status.go +0 -30
  47. package/alpha/golang/newPkg/storageutils/on_search.go +0 -30
  48. package/alpha/golang/newPkg/storageutils/on_status.go +0 -30
  49. package/alpha/golang/newPkg/storageutils/on_track.go +0 -30
  50. package/alpha/golang/newPkg/storageutils/on_update.go +0 -30
  51. package/alpha/golang/newPkg/storageutils/save_utils.go +0 -75
  52. package/alpha/golang/newPkg/storageutils/search.go +0 -30
  53. package/alpha/golang/newPkg/storageutils/status.go +0 -30
  54. package/alpha/golang/newPkg/storageutils/track.go +0 -30
  55. package/alpha/golang/newPkg/storageutils/update.go +0 -30
  56. package/alpha/golang/validationpkg/go.mod +0 -3
  57. package/alpha/golang/validationpkg/validationutils/json_normalizer.go +0 -152
  58. package/alpha/golang/validationpkg/validationutils/json_path_utils.go +0 -173
  59. package/alpha/golang/validationpkg/validationutils/storage-interface.go +0 -107
  60. package/alpha/golang/validationpkg/validationutils/test-config.go +0 -69
  61. package/alpha/golang/validationpkg/validationutils/validation_utils.go +0 -429
  62. /package/alpha/{golang → docs}/page/index.html +0 -0
  63. /package/alpha/{golang → docs}/page/style.css +0 -0
  64. /package/alpha/{golang → docs}/readme.md +0 -0
  65. /package/alpha/{golang → docs}/validPaths.json +0 -0
  66. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/cancel.go +0 -0
  67. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/confirm.go +0 -0
  68. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/init.go +0 -0
  69. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/issue.go +0 -0
  70. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/on_cancel.go +0 -0
  71. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/on_confirm.go +0 -0
  72. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/on_init.go +0 -0
  73. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/on_issue.go +0 -0
  74. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/on_issue_status.go +0 -0
  75. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/on_search.go +0 -0
  76. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/on_status.go +0 -0
  77. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/on_track.go +0 -0
  78. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/on_update.go +0 -0
  79. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/search.go +0 -0
  80. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/status.go +0 -0
  81. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/track.go +0 -0
  82. /package/alpha/{golang/validationpkg → validationpkg}/jsonvalidations/update.go +0 -0
  83. /package/alpha/{golang/validationpkg → validationpkg}/main-validator.go +0 -0
  84. /package/alpha/{golang/validationpkg → validationpkg}/main-validator_test.go +0 -0
  85. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/api_save_utils.go +0 -0
  86. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/cancel.go +0 -0
  87. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/confirm.go +0 -0
  88. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/index.go +0 -0
  89. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/init.go +0 -0
  90. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/issue.go +0 -0
  91. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/on_cancel.go +0 -0
  92. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/on_confirm.go +0 -0
  93. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/on_init.go +0 -0
  94. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/on_issue.go +0 -0
  95. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/on_issue_status.go +0 -0
  96. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/on_search.go +0 -0
  97. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/on_status.go +0 -0
  98. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/on_track.go +0 -0
  99. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/on_update.go +0 -0
  100. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/save_utils.go +0 -0
  101. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/search.go +0 -0
  102. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/status.go +0 -0
  103. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/track.go +0 -0
  104. /package/alpha/{golang/validationpkg → validationpkg}/storageutils/update.go +0 -0
  105. /package/alpha/{golang/newPkg → validationpkg}/validationutils/json_normalizer.go +0 -0
  106. /package/alpha/{golang/newPkg → validationpkg}/validationutils/json_path_utils.go +0 -0
  107. /package/alpha/{golang/newPkg → validationpkg}/validationutils/storage-interface.go +0 -0
  108. /package/alpha/{golang/newPkg → validationpkg}/validationutils/test-config.go +0 -0
  109. /package/alpha/{golang/newPkg → validationpkg}/validationutils/validation_utils.go +0 -0
@@ -0,0 +1,913 @@
1
+ # JVAL — Full DSL Reference
2
+
3
+ **JVAL** (JSON Validation Language) is a declarative, JSON/YAML-based DSL for writing API payload validation rules. A JVAL config is compiled by the code generator into executable validation functions in TypeScript, Python, JavaScript, or Go.
4
+
5
+ This document covers the complete DSL — the config structure, every field, variable binding, scoping, conditional logic, session/stateful data, nested tests, and how it all maps to generated code.
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ 1. [Top-Level Config Structure](#1-top-level-config-structure)
12
+ 2. [Test Sets](#2-test-sets)
13
+ 3. [Test Object — field reference](#3-test-object--field-reference)
14
+ 4. [Variables](#4-variables)
15
+ 5. [Scope](#5-scope)
16
+ 6. [The `_RETURN_` field](#6-the-_return_-field)
17
+ 7. [The `_CONTINUE_` field (conditional skip)](#7-the-_continue_-field-conditional-skip)
18
+ 8. [Nested `_RETURN_` (grouped tests)](#8-nested-_return_-grouped-tests)
19
+ 9. [Session Data & Cross-Request State](#9-session-data--cross-request-state)
20
+ 10. [External Data & `$._EXTERNAL`](#10-external-data--_external)
21
+ 11. [Generated Output Shape](#11-generated-output-shape)
22
+ 12. [Runtime ValidationConfig](#12-runtime-validationconfig)
23
+ 13. [Complete Annotated Examples](#13-complete-annotated-examples)
24
+ 14. [Common Patterns Cookbook](#14-common-patterns-cookbook)
25
+ 15. [Reserved Keywords](#15-reserved-keywords)
26
+
27
+ ---
28
+
29
+ ## 1. Top-Level Config Structure
30
+
31
+ A JVAL config is a JSON object with exactly two top-level keys:
32
+
33
+ ```json
34
+ {
35
+ "_TESTS_": {
36
+ "<action_name>": [ <test_object>, ... ],
37
+ "<action_name_2>": [ <test_object>, ... ]
38
+ },
39
+ "_SESSION_DATA_": {
40
+ "<action_name>": {
41
+ "<key>": "<json_path_expression>"
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ | Key | Type | Required | Description |
48
+ | ---------------- | ------ | -------- | ------------------------------------------------------------------------------------ |
49
+ | `_TESTS_` | Object | Yes | All validation test sets, keyed by API action name |
50
+ | `_SESSION_DATA_` | Object | Yes | Declares what values to save after each action for stateful cross-request validation |
51
+
52
+ The action names under `_TESTS_` and `_SESSION_DATA_` are arbitrary strings that correspond to API operations (e.g., `search`, `on_search`, `init`, `confirm`, `cancel`, etc.). The code generator emits one validation function per action.
53
+
54
+ ---
55
+
56
+ ## 2. Test Sets
57
+
58
+ Each value under `_TESTS_` is an **ordered array of test objects**. The generated function for that action runs every test object in sequence.
59
+
60
+ ```json
61
+ {
62
+ "_TESTS_": {
63
+ "search": [
64
+ { /* test object 1 */ },
65
+ { /* test object 2 */ },
66
+ ...
67
+ ],
68
+ "on_confirm": [
69
+ { /* test object 1 */ },
70
+ ...
71
+ ]
72
+ }
73
+ }
74
+ ```
75
+
76
+ - Tests within a set are **independent** — a failure in one does not stop subsequent tests from running.
77
+ - The generated function collects all results and returns them together.
78
+ - Test names must be **unique within a test set** (they become function names in generated code).
79
+
80
+ ---
81
+
82
+ ## 3. Test Object — field reference
83
+
84
+ A test object is a JSON object with a mix of **reserved system fields** (prefixed and suffixed with `_`) and **user-defined variable fields**.
85
+
86
+ ### Full schema
87
+
88
+ ```json
89
+ {
90
+ "_NAME_": "<string>",
91
+ "_DESCRIPTION_": "<string>",
92
+ "_SCOPE_": "<json_path>",
93
+ "_CONTINUE_": "<jval_expression>",
94
+ "_ERROR_CODE_": <number>,
95
+ "_SUCCESS_CODE_": <number>,
96
+ "_RETURN_": "<jval_expression>" | [ <test_object>, ... ],
97
+
98
+ "<varName1>": "<json_path>" | ["<string>", ...],
99
+ "<varName2>": "<json_path>" | ["<string>", ...]
100
+ }
101
+ ```
102
+
103
+ ### Reserved fields
104
+
105
+ | Field | Type | Required | Default | Description |
106
+ | ---------------- | --------------- | -------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
107
+ | `_NAME_` | string | **Yes** | — | Unique identifier for this test. Becomes the generated function name. Use `UPPER_SNAKE_CASE` or `snake_case`. No spaces or hyphens. |
108
+ | `_DESCRIPTION_` | string | No | Auto-generated | Human-readable description shown in error output when the test fails. |
109
+ | `_SCOPE_` | JSONPath | No | `"$"` | Narrows the JSON subtree that tests run against. The path must resolve to an **array**; the test logic runs once per element. |
110
+ | `_CONTINUE_` | JVAL expression | No | — | If this expression evaluates to `true` for a scope element, **that element is skipped** (the test is not run for it). Used for conditional/optional validation. |
111
+ | `_ERROR_CODE_` | number | No | `30000` | Error code returned when the test fails. |
112
+ | `_SUCCESS_CODE_` | number | No | `200` | Success code returned when the test passes. |
113
+ | `_RETURN_` | string or array | **Yes** | — | Either a JVAL expression string (leaf test) or an array of nested test objects (grouped tests). |
114
+
115
+ ### User-defined variable fields
116
+
117
+ Any key that is **not** one of the reserved fields above is treated as a variable declaration. See [Section 4](#4-variables).
118
+
119
+ ---
120
+
121
+ ## 4. Variables
122
+
123
+ Variables are the bridge between the JSON payload and the JVAL expression in `_RETURN_`.
124
+
125
+ ### Variable value types
126
+
127
+ #### A. JSONPath string
128
+
129
+ ```json
130
+ "attr": "$.context.action"
131
+ ```
132
+
133
+ At runtime, the generator extracts all matching values from the current scope object using this path. The variable `attr` becomes an array of extracted values (even if only one matches).
134
+
135
+ ```json
136
+ "tags": "$.message.order.tags[*].descriptor.code"
137
+ ```
138
+
139
+ Multiple-match paths (using `[*]`, `..`, filter expressions) produce arrays of all matching values.
140
+
141
+ #### B. String array literal
142
+
143
+ ```json
144
+ "validActions": ["search", "select", "init"]
145
+ ```
146
+
147
+ The variable `validActions` is a fixed list of strings. This is the primary way to supply enum allowlists inline.
148
+
149
+ #### C. External/session data reference
150
+
151
+ ```json
152
+ "prevTransactionId": "$._EXTERNAL.transaction_id"
153
+ ```
154
+
155
+ The `$._EXTERNAL` prefix accesses data from outside the current payload — either from previous API calls (via `_SESSION_DATA_`) or from externally injected data passed at runtime. See [Section 10](#10-external-data--_external).
156
+
157
+ ### Variable name rules
158
+
159
+ - Must match `[a-zA-Z_][a-zA-Z0-9_]*` (same as a standard identifier).
160
+ - Case-sensitive.
161
+ - Must not clash with any reserved field name.
162
+ - Must not use language reserved keywords (JavaScript/TypeScript/Python keywords are all blocked — see [Section 15](#15-reserved-keywords)).
163
+
164
+ ### How variables are resolved in generated code (TypeScript)
165
+
166
+ ```typescript
167
+ // JSONPath variable
168
+ const attr = payloadUtils.getJsonPath(testObj, "$.context.action", true);
169
+
170
+ // Array literal variable
171
+ const validActions = ["search", "select", "init"];
172
+
173
+ // External data variable
174
+ const prevTransactionId = payloadUtils.getJsonPath(
175
+ testObj,
176
+ "$._EXTERNAL.transaction_id",
177
+ true,
178
+ );
179
+ // (testObj._EXTERNAL is injected by the runner before extraction)
180
+ ```
181
+
182
+ ---
183
+
184
+ ## 5. Scope
185
+
186
+ `_SCOPE_` is a JSONPath expression that determines **which part of the payload** a test operates on, and **how many times** the test body runs.
187
+
188
+ ### Default scope
189
+
190
+ If `_SCOPE_` is omitted, it defaults to `"$"` (the root of the payload). A single virtual scope object containing the full payload is used, so the test runs exactly once.
191
+
192
+ ### Array scope
193
+
194
+ When `_SCOPE_` resolves to an array, **the test runs once per element**. Variable paths inside the test object are relative to each element.
195
+
196
+ ```json
197
+ {
198
+ "_NAME_": "CHECK_PAYMENT_TRANSACTION",
199
+ "_SCOPE_": "$.message.order.payments[*]",
200
+ "statusPath": "$.status",
201
+ "transactionId": "$.params.transaction_id",
202
+ "paidStatus": ["PAID"],
203
+ "_CONTINUE_": "!(statusPath all in paidStatus)",
204
+ "_RETURN_": "transactionId are present"
205
+ }
206
+ ```
207
+
208
+ For each payment object in the array:
209
+
210
+ 1. `statusPath` extracts `$.status` from that payment object.
211
+ 2. `transactionId` extracts `$.params.transaction_id` from that payment object.
212
+ 3. `_CONTINUE_` is checked — if the payment is NOT `PAID`, skip this test for that element.
213
+ 4. `_RETURN_` checks `transactionId are present` for PAID payments only.
214
+
215
+ ### Scope with JSONPath filter expressions
216
+
217
+ `_SCOPE_` fully supports JSONPath filter expressions:
218
+
219
+ ```json
220
+ "_SCOPE_": "$.message.order.tags[?(@.descriptor.code=='BAP_TERMS')]"
221
+ ```
222
+
223
+ This iterates only over tags whose `descriptor.code` is `BAP_TERMS`. Within the test object, `$.list[*].descriptor.code` is then relative to that specific tag object.
224
+
225
+ ### Key rule
226
+
227
+ **Variable JSONPaths inside a test object are always relative to the current scope element**, not the root payload. This is because before variable extraction the generator sets the scope object as the current context.
228
+
229
+ ---
230
+
231
+ ## 6. The `_RETURN_` field
232
+
233
+ `_RETURN_` is the core of a test object. It specifies **what boolean condition must hold** for the test to pass.
234
+
235
+ ### Form 1: JVAL expression string (leaf test)
236
+
237
+ ```json
238
+ "_RETURN_": "attr are present && attr all in enumList"
239
+ ```
240
+
241
+ This is a JVAL expression. The full grammar and operator reference is in the `return-complier/docs/` subfolder. Quick summary:
242
+
243
+ - Variables evaluate to arrays extracted from the payload.
244
+ - Unary functions: `<var> are present`, `<var> are unique`
245
+ - Binary functions: `<lhs> all in <rhs>`, `<lhs> any in <rhs>`, `<lhs> none in <rhs>`, `<lhs> follow regex <rhs>`, `<lhs> equal to <rhs>`, `<lhs> greater than <rhs>`, `<lhs> less than <rhs>`
246
+ - Boolean connectives: `&&`, `||`, `!`
247
+ - Parentheses for grouping: `(…)`, with `!` only applicable to parenthesised groups: `!(…)`
248
+
249
+ When `_RETURN_` is a string:
250
+
251
+ - The expression is compiled to a boolean expression in the target language.
252
+ - If the expression evaluates to `false`, the test fails and returns `{ valid: false, code: _ERROR_CODE_, description: "…" }`.
253
+ - If `true`, the test passes and returns `{ valid: true, code: _SUCCESS_CODE_ }`.
254
+
255
+ ### Form 2: Array of test objects (nested/grouped tests)
256
+
257
+ ```json
258
+ "_RETURN_": [
259
+ { "_NAME_": "sub_test_1", ... "_RETURN_": "..." },
260
+ { "_NAME_": "sub_test_2", ... "_RETURN_": "..." }
261
+ ]
262
+ ```
263
+
264
+ When `_RETURN_` is an array, the parent test object becomes a **group wrapper**. The generator:
265
+
266
+ 1. Generates individual sub-test functions for each item.
267
+ 2. The parent function runs all sub-functions and aggregates their results.
268
+ 3. The parent test object itself does not have a JVAL expression — it acts as a container.
269
+
270
+ This is how large conditional blocks are written, for example validating message body fields only when no error is present:
271
+
272
+ ```json
273
+ {
274
+ "_NAME_": "on_search_Message_TESTS",
275
+ "errorBlock": "$.error.code",
276
+ "_CONTINUE_": "errorBlock are present",
277
+ "_RETURN_": [
278
+ {
279
+ "_NAME_": "REQUIRED_CATALOG_NAME",
280
+ "attr": "$.message.catalog.descriptor.name",
281
+ "_RETURN_": "attr are present"
282
+ },
283
+ {
284
+ "_NAME_": "REQUIRED_PROVIDER_ID",
285
+ "attr": "$.message.catalog.providers[*].id",
286
+ "_RETURN_": "attr are present"
287
+ }
288
+ ]
289
+ }
290
+ ```
291
+
292
+ Here if `errorBlock` (i.e. `$.error.code`) is present in the payload, the whole group is skipped. If it is absent, all sub-tests run.
293
+
294
+ ---
295
+
296
+ ## 7. The `_CONTINUE_` field (conditional skip)
297
+
298
+ `_CONTINUE_` is a JVAL expression that acts as a **skip gate**.
299
+
300
+ - Evaluated first, before `_RETURN_`.
301
+ - If `_CONTINUE_` evaluates to **`true`** for a scope element → **skip** (do not run `_RETURN_` for that element, treat it as passing trivially).
302
+ - If `_CONTINUE_` evaluates to **`false`** → proceed to evaluate `_RETURN_`.
303
+
304
+ ### Semantics
305
+
306
+ `_CONTINUE_` means: _"Continue past this test (skip) if this condition holds."_
307
+
308
+ This is used for two main patterns:
309
+
310
+ #### Pattern 1: Optional field validation
311
+
312
+ ```json
313
+ {
314
+ "_NAME_": "VALID_ENUM_VEHICLE_CATEGORY",
315
+ "enumList": ["AUTO_RICKSHAW", "CAB", "TWO_WHEELER"],
316
+ "enumPath": "$.message.intent.fulfillment.vehicle.category",
317
+ "_CONTINUE_": "!(enumPath are present)",
318
+ "_RETURN_": "enumPath all in enumList"
319
+ }
320
+ ```
321
+
322
+ - If `vehicle.category` is **not present** in the payload → `!(enumPath are present)` is `true` → skip. The field is optional, so absence is fine.
323
+ - If `vehicle.category` **is present** → `!(enumPath are present)` is `false` → run `_RETURN_`, validating the value is within the allowed list.
324
+
325
+ #### Pattern 2: Conditional group skipping
326
+
327
+ ```json
328
+ {
329
+ "_NAME_": "on_search_Message_TESTS",
330
+ "errorBlock": "$.error.code",
331
+ "_CONTINUE_": "errorBlock are present",
332
+ "_RETURN_": [ ... ]
333
+ }
334
+ ```
335
+
336
+ - If the payload contains an `error.code` (i.e. it's an error response) → skip the entire sub-test group.
337
+ - If no error → validate the message body.
338
+
339
+ #### Pattern 3: Scoped conditional
340
+
341
+ ```json
342
+ {
343
+ "_NAME_": "FULFILLMENT_STATE_ENDED",
344
+ "_SCOPE_": "$.message.order.fulfillments[*]",
345
+ "auth": "$.stops[*].authorization.status",
346
+ "authStatus": ["CLAIMED"],
347
+ "state": "$.state.descriptor.code",
348
+ "stateCode": ["RIDE_ENDED"],
349
+ "_CONTINUE_": "!(authStatus all in auth)",
350
+ "_RETURN_": "state all in stateCode"
351
+ }
352
+ ```
353
+
354
+ For each fulfillment element:
355
+
356
+ - Skip if its `authorization.status` is NOT `CLAIMED`.
357
+ - If it IS `CLAIMED`, validate that `state.descriptor.code` is `RIDE_ENDED`.
358
+
359
+ ---
360
+
361
+ ## 8. Nested `_RETURN_` (grouped tests)
362
+
363
+ When `_RETURN_` is an array of test objects, it creates a hierarchical validation structure.
364
+
365
+ ### Rules
366
+
367
+ - The **parent test object** has its own `_NAME_`, can have `_SCOPE_`, and **can have `_CONTINUE_`** (applied to the whole group).
368
+ - The parent does NOT have a JVAL expression — it has no `_ERROR_CODE_` or `_SUCCESS_CODE_` that apply to itself as a leaf.
369
+ - Each **child test object** is a fully independent test object with its own `_NAME_`, `_SCOPE_`, `_CONTINUE_`, `_RETURN_`, `_ERROR_CODE_`, etc.
370
+ - Children can themselves have array `_RETURN_`, creating **arbitrary nesting depth**.
371
+ - The parent's result is derived from all child results: `valid = all children valid`.
372
+
373
+ ### Generated structure
374
+
375
+ ```
376
+ parentFunction(input)
377
+ └─ runs all child functions:
378
+ childFunction1(input) → validationOutput[]
379
+ childFunction2(input) → validationOutput[]
380
+ ...
381
+ └─ returns combined array of all child results
382
+ ```
383
+
384
+ The parent function's own result entry has `valid: true` only if every child returned `valid: true`.
385
+
386
+ ### Common usage: error-gated message validation
387
+
388
+ For `on_*` ONDC API responses, a payload may be an error response (contains `error.code`) or a successful one (contains `message.*`). Nested `_RETURN_` with `_CONTINUE_` on the parent is the standard pattern:
389
+
390
+ ```json
391
+ {
392
+ "_NAME_": "on_confirm_Message_TESTS",
393
+ "errorBlock": "$.error.code",
394
+ "_CONTINUE_": "errorBlock are present",
395
+ "_RETURN_": [
396
+ {
397
+ "_NAME_": "REQUIRED_ORDER_ID",
398
+ "attr": "$.message.order.id",
399
+ "_RETURN_": "attr are present"
400
+ },
401
+ {
402
+ "_NAME_": "REQUIRED_ORDER_STATUS",
403
+ "attr": "$.message.order.status",
404
+ "_RETURN_": "attr are present"
405
+ }
406
+ ]
407
+ }
408
+ ```
409
+
410
+ ---
411
+
412
+ ## 9. Session Data & Cross-Request State
413
+
414
+ `_SESSION_DATA_` enables **stateful validation** — saving values from one API call and using them in a later one.
415
+
416
+ ### Structure
417
+
418
+ ```json
419
+ "_SESSION_DATA_": {
420
+ "<action_name>": {
421
+ "<key>": "<json_path>"
422
+ }
423
+ }
424
+ ```
425
+
426
+ - `<action_name>` is the API action after which data is saved (e.g., `"search"`).
427
+ - `<key>` is the name under which the extracted value is stored.
428
+ - `<json_path>` is extracted from the payload of that action.
429
+
430
+ ### Example
431
+
432
+ ```json
433
+ "_SESSION_DATA_": {
434
+ "search": {
435
+ "transaction_id": "$.context.transaction_id"
436
+ }
437
+ }
438
+ ```
439
+
440
+ After a `search` call, `$.context.transaction_id` is extracted from the payload and stored in the session under the key `"transaction_id"`.
441
+
442
+ ### Reading session data in a test
443
+
444
+ Use the `$._EXTERNAL.<key>` path for a variable:
445
+
446
+ ```json
447
+ {
448
+ "_NAME_": "VERIFY_TRANSACTION_CONTINUITY",
449
+ "currentTxnId": "$.context.transaction_id",
450
+ "prevTxnId": "$._EXTERNAL.transaction_id",
451
+ "_RETURN_": "currentTxnId equal to prevTxnId"
452
+ }
453
+ ```
454
+
455
+ This test verifies that the `transaction_id` in the current `on_search` payload matches the one saved from the `search` call.
456
+
457
+ ### Runtime requirement
458
+
459
+ Stateful validations require enabling `stateFullValidations: true` in the `ValidationConfig` at runtime, plus providing a `store` (a `StorageInterface` implementation) and a `uniqueKey` to namespace the session:
460
+
461
+ ```typescript
462
+ await performL1Validations("on_search", payload, {
463
+ stateFullValidations: true,
464
+ uniqueKey: "session-abc-123",
465
+ store: myRedisStorage,
466
+ });
467
+ ```
468
+
469
+ If `stateFullValidations` is `false` (default), variables with `$._EXTERNAL.*` paths are ignored and the validation is treated as stateless.
470
+
471
+ ---
472
+
473
+ ## 10. External Data & `$._EXTERNAL`
474
+
475
+ `$._EXTERNAL` is a special namespace that provides data from **outside the current payload**. It is populated at validation runtime and can come from two sources:
476
+
477
+ ### Source 1: Session/stateful data
478
+
479
+ Values saved via `_SESSION_DATA_` from previous API calls (see Section 9).
480
+
481
+ ### Source 2: Caller-injected data
482
+
483
+ Arbitrary data passed by the caller at runtime:
484
+
485
+ ```typescript
486
+ // TypeScript
487
+ await performL1Validations("search", payload, config, {
488
+ full_ids: { "10102": true, "10103": true },
489
+ });
490
+
491
+ // Go
492
+ results, err := PerformL1_validations(action, payload, cfg, validationutils.ExternalData{
493
+ "full_ids": map[string]bool{ "10102": true },
494
+ })
495
+ ```
496
+
497
+ This is used for data that must come from an external database, orchestration layer, or prior business logic — not from the payload itself.
498
+
499
+ ### Source 3: `_SELF` — the current payload
500
+
501
+ `$._EXTERNAL._SELF` is always automatically populated with the **full normalised payload** being validated. This allows a test to reference the entire payload from within any scoped context.
502
+
503
+ ```json
504
+ {
505
+ "_SCOPE_": "$.message.items[*]",
506
+ "itemId": "$.id",
507
+ "allProviderIds": "$._EXTERNAL._SELF.message.catalog.providers[*].id",
508
+ "_RETURN_": "itemId all in allProviderIds"
509
+ }
510
+ ```
511
+
512
+ Variables referencing `_SELF` are **never collected into load/save session operations** — they are always available without stateful storage.
513
+
514
+ ### Path syntax
515
+
516
+ ```
517
+ $._EXTERNAL.<key>
518
+ $._EXTERNAL._SELF.<further_json_path>
519
+ ```
520
+
521
+ In generated code, before variable extraction the `_EXTERNAL` object is injected onto `testObj`:
522
+
523
+ ```typescript
524
+ testObj._EXTERNAL = input.externalData;
525
+ const allProviderIds = payloadUtils.getJsonPath(
526
+ testObj,
527
+ "$._EXTERNAL._SELF.message.catalog.providers[*].id",
528
+ true,
529
+ );
530
+ ```
531
+
532
+ ---
533
+
534
+ ## 11. Generated Output Shape
535
+
536
+ The code generator produces this directory structure (TypeScript example):
537
+
538
+ ```
539
+ generated/<codeName>/
540
+ ├── index.ts ← Entry: perform<CodeName>(action, payload, config, externalData)
541
+ ├── error.ts ← Error code registry
542
+ ├── api-tests/
543
+ │ ├── search.ts ← search(input): validationOutput
544
+ │ ├── on_search.ts
545
+ │ ├── init.ts
546
+ │ └── ...
547
+ ├── utils/
548
+ │ ├── json-path-utils.ts ← payloadUtils.getJsonPath(...)
549
+ │ ├── validation-utils.ts ← validations.arePresent(...), allIn(...)...
550
+ │ └── json-normalizer.ts ← normalizeKeys(payload)
551
+ ├── types/
552
+ │ └── test-config.ts ← ValidationConfig, validationInput, validationOutput, ExternalData
553
+ ├── interfaces/
554
+ │ └── storage-interface.ts ← StorageInterface
555
+ └── storage-actions/
556
+ ├── index.ts
557
+ ├── search.ts ← perform<CodeName>Save / perform<CodeName>Load per action
558
+ └── ...
559
+ ```
560
+
561
+ ### `validationOutput` type
562
+
563
+ ```typescript
564
+ type validationOutput = {
565
+ testName: string; // The _NAME_ of the test object
566
+ valid: boolean; // true = pass, false = fail
567
+ code: number; // _SUCCESS_CODE_ or _ERROR_CODE_
568
+ description?: string; // Only present on failure; markdown message
569
+ _debugInfo?: {
570
+ // Only present when config._debug = true
571
+ fedConfig: any; // The raw test object JSON
572
+ };
573
+ }[];
574
+ ```
575
+
576
+ ---
577
+
578
+ ## 12. Runtime ValidationConfig
579
+
580
+ The generated entry function accepts a `ValidationConfig` object that controls runtime behaviour:
581
+
582
+ ```typescript
583
+ interface ValidationConfig {
584
+ onlyInvalid: boolean; // If true, only return failed test results (default: true)
585
+ hideParentErrors: boolean; // If true, suppress parent group results that have no description (default: true)
586
+ stateFullValidations: boolean; // If true, load/save session data (default: false)
587
+ skipTests?: string[]; // Array of _NAME_ values to skip entirely
588
+ uniqueKey?: string; // Session namespace key (required if stateFullValidations = true)
589
+ store?: StorageInterface; // Storage backend (required if stateFullValidations = true)
590
+ _debug?: boolean; // If true, include _debugInfo in output (default: false)
591
+ }
592
+ ```
593
+
594
+ ### Calling the generated function
595
+
596
+ ```typescript
597
+ import { performMyValidations } from "./generated/my-validations";
598
+
599
+ const results = await performMyValidations(
600
+ "search", // action name — must match a key in _TESTS_
601
+ payload, // the raw JSON payload object
602
+ {
603
+ onlyInvalid: true,
604
+ hideParentErrors: true,
605
+ stateFullValidations: false,
606
+ },
607
+ {
608
+ // optional external data injected as $._EXTERNAL.*
609
+ full_ids: ["id1", "id2"],
610
+ },
611
+ );
612
+
613
+ // results is validationOutput[]
614
+ // Each entry: { testName, valid, code, description? }
615
+ ```
616
+
617
+ ---
618
+
619
+ ## 13. Complete Annotated Examples
620
+
621
+ ### Example 1: Simple required field + enum check
622
+
623
+ ```json
624
+ {
625
+ "_NAME_": "REQUIRED_CONTEXT_ACTION",
626
+ "attr": "$.context.action",
627
+ "enumList": ["search"],
628
+ "_ERROR_CODE_": 30001,
629
+ "_DESCRIPTION_": "context.action must be present and must be 'search'",
630
+ "_RETURN_": "attr are present && attr all in enumList"
631
+ }
632
+ ```
633
+
634
+ - `attr` extracts `$.context.action` (an array, typically single-element: `["search"]`).
635
+ - `enumList` is the literal array `["search"]`.
636
+ - `_RETURN_` checks both presence and value correctness.
637
+ - If either sub-check fails, returns `{ valid: false, code: 30001, description: "…" }`.
638
+
639
+ ---
640
+
641
+ ### Example 2: Optional field with conditional enum validation
642
+
643
+ ```json
644
+ {
645
+ "_NAME_": "VALID_ENUM_VEHICLE_CATEGORY",
646
+ "enumList": ["AUTO_RICKSHAW", "CAB", "TWO_WHEELER"],
647
+ "enumPath": "$.message.order.fulfillments[*].vehicle.category",
648
+ "_CONTINUE_": "!(enumPath are present)",
649
+ "_RETURN_": "enumPath all in enumList"
650
+ }
651
+ ```
652
+
653
+ - If `vehicle.category` is absent → `_CONTINUE_` is `true` → skip → no error.
654
+ - If `vehicle.category` is present → `_CONTINUE_` is `false` → validate that every value is in the enum list.
655
+
656
+ ---
657
+
658
+ ### Example 3: Scoped validation with conditional
659
+
660
+ ```json
661
+ {
662
+ "_NAME_": "FULFILLMENT_STATE_ENDED",
663
+ "_SCOPE_": "$.message.order.fulfillments[*]",
664
+ "auth": "$.stops[*].authorization.status",
665
+ "authStatus": ["CLAIMED"],
666
+ "state": "$.state.descriptor.code",
667
+ "stateCode": ["RIDE_ENDED"],
668
+ "_CONTINUE_": "!(authStatus all in auth)",
669
+ "_RETURN_": "state all in stateCode"
670
+ }
671
+ ```
672
+
673
+ - Iterates over each fulfillment.
674
+ - For each: extracts `authorization.status` array as `auth`.
675
+ - Skips if it does NOT contain `CLAIMED`.
676
+ - For CLAIMED fulfillments: validates that `state.descriptor.code` is `RIDE_ENDED`.
677
+
678
+ ---
679
+
680
+ ### Example 4: Nested test group (error-gated message validation)
681
+
682
+ ```json
683
+ {
684
+ "_NAME_": "on_search_Message_TESTS",
685
+ "errorBlock": "$.error.code",
686
+ "_CONTINUE_": "errorBlock are present",
687
+ "_RETURN_": [
688
+ {
689
+ "_NAME_": "REQUIRED_CATALOG_NAME",
690
+ "attr": "$.message.catalog.descriptor.name",
691
+ "_RETURN_": "attr are present"
692
+ },
693
+ {
694
+ "_NAME_": "REQUIRED_PROVIDER_ID",
695
+ "attr": "$.message.catalog.providers[*].id",
696
+ "_RETURN_": "attr are present"
697
+ }
698
+ ]
699
+ }
700
+ ```
701
+
702
+ - `errorBlock` extracts `$.error.code`.
703
+ - If error code exists → skip all message validations (error response, not expected to have `message.catalog`).
704
+ - If no error → run all sub-tests checking catalog fields.
705
+
706
+ ---
707
+
708
+ ### Example 5: Tag code validation with scoped sub-tags
709
+
710
+ ```json
711
+ {
712
+ "_NAME_": "validate_BAP_TERMS_tags",
713
+ "validTags": ["BAP_TERMS"],
714
+ "tagPath": "$.message.order.tags[*].descriptor.code",
715
+ "_CONTINUE_": "!(tagPath are present)",
716
+ "_RETURN_": "tagPath all in validTags"
717
+ },
718
+ {
719
+ "_NAME_": "validate_BAP_TERMS_sub_tags",
720
+ "_SCOPE_": "$.message.order.tags[?(@.descriptor.code=='BAP_TERMS')]",
721
+ "subTags": "$.list[*].descriptor.code",
722
+ "validValues": ["BUYER_FINDER_FEES_TYPE", "SETTLEMENT_WINDOW", "SETTLEMENT_TYPE"],
723
+ "_RETURN_": "subTags all in validValues"
724
+ }
725
+ ```
726
+
727
+ Two-phase tag validation pattern:
728
+
729
+ 1. First test: if any top-level tag is present, verify it is from the allowed set.
730
+ 2. Second test: scope into specific tag objects (via filter expression), then validate their `list[*].descriptor.code` against the expected sub-tag names.
731
+
732
+ ---
733
+
734
+ ### Example 6: Cross-request state (transaction ID continuity)
735
+
736
+ Config:
737
+
738
+ ```json
739
+ {
740
+ "_TESTS_": {
741
+ "search": [
742
+ {
743
+ "_NAME_": "REQUIRED_TRANSACTION_ID",
744
+ "attr": "$.context.transaction_id",
745
+ "_RETURN_": "attr are present"
746
+ }
747
+ ],
748
+ "on_search": [
749
+ {
750
+ "_NAME_": "TRANSACTION_ID_CONTINUITY",
751
+ "currentId": "$.context.transaction_id",
752
+ "savedId": "$._EXTERNAL.transaction_id",
753
+ "_RETURN_": "currentId equal to savedId"
754
+ }
755
+ ]
756
+ },
757
+ "_SESSION_DATA_": {
758
+ "search": {
759
+ "transaction_id": "$.context.transaction_id"
760
+ }
761
+ }
762
+ }
763
+ ```
764
+
765
+ Runtime:
766
+
767
+ ```typescript
768
+ const store = new MyRedisStorage();
769
+ const key = "session-xyz";
770
+
771
+ // On search call:
772
+ await performMyValidations("search", searchPayload, {
773
+ stateFullValidations: true,
774
+ store,
775
+ uniqueKey: key,
776
+ });
777
+ // → saves transaction_id from searchPayload to store["session-xyz"]["transaction_id"]
778
+
779
+ // On on_search call:
780
+ await performMyValidations("on_search", onSearchPayload, {
781
+ stateFullValidations: true,
782
+ store,
783
+ uniqueKey: key,
784
+ });
785
+ // → loads transaction_id from store, injects as externalData.transaction_id
786
+ // → test evaluates currentId equal to savedId
787
+ ```
788
+
789
+ ---
790
+
791
+ ## 14. Common Patterns Cookbook
792
+
793
+ ### Presence check
794
+
795
+ ```json
796
+ { "attr": "$.some.field", "_RETURN_": "attr are present" }
797
+ ```
798
+
799
+ ### Enum check (field must be in allowed list)
800
+
801
+ ```json
802
+ {
803
+ "attr": "$.field",
804
+ "allowed": ["A", "B", "C"],
805
+ "_RETURN_": "attr all in allowed"
806
+ }
807
+ ```
808
+
809
+ ### Presence + enum combined
810
+
811
+ ```json
812
+ {
813
+ "attr": "$.field",
814
+ "allowed": ["A", "B"],
815
+ "_RETURN_": "attr are present && attr all in allowed"
816
+ }
817
+ ```
818
+
819
+ ### Optional field enum validation (skip if absent)
820
+
821
+ ```json
822
+ {
823
+ "path": "$.optional.field",
824
+ "allowed": ["X", "Y"],
825
+ "_CONTINUE_": "!(path are present)",
826
+ "_RETURN_": "path all in allowed"
827
+ }
828
+ ```
829
+
830
+ ### Uniqueness check
831
+
832
+ ```json
833
+ { "_SCOPE_": "$.items[*]", "ids": "$.id", "_RETURN_": "ids are unique" }
834
+ ```
835
+
836
+ ### Regex validation
837
+
838
+ ```json
839
+ {
840
+ "phones": "$.contacts[*].phone",
841
+ "pattern": ["^\\+91[0-9]{10}$"],
842
+ "_RETURN_": "phones follow regex pattern"
843
+ }
844
+ ```
845
+
846
+ ### Cross-field relationship
847
+
848
+ ```json
849
+ {
850
+ "itemIds": "$.message.order.items[*].id",
851
+ "catalogIds": "$._EXTERNAL._SELF.message.catalog.providers[*].items[*].id",
852
+ "_RETURN_": "itemIds all in catalogIds"
853
+ }
854
+ ```
855
+
856
+ ### Error-gated group
857
+
858
+ ```json
859
+ {
860
+ "_NAME_": "MESSAGE_TESTS",
861
+ "err": "$.error.code",
862
+ "_CONTINUE_": "err are present",
863
+ "_RETURN_": [ ... sub-tests ... ]
864
+ }
865
+ ```
866
+
867
+ ### Scoped conditional
868
+
869
+ ```json
870
+ {
871
+ "_SCOPE_": "$.payments[*]",
872
+ "status": "$.status",
873
+ "paid": ["PAID"],
874
+ "txnId": "$.params.transaction_id",
875
+ "_CONTINUE_": "!(status all in paid)",
876
+ "_RETURN_": "txnId are present"
877
+ }
878
+ ```
879
+
880
+ ---
881
+
882
+ ## 15. Reserved Keywords
883
+
884
+ The following words **cannot be used as variable names**:
885
+
886
+ - All JavaScript/TypeScript reserved words: `break`, `case`, `catch`, `class`, `const`, `continue`, `debugger`, `default`, `delete`, `do`, `else`, `export`, `extends`, `finally`, `for`, `function`, `if`, `import`, `in`, `instanceof`, `let`, `new`, `return`, `super`, `switch`, `this`, `throw`, `try`, `typeof`, `var`, `void`, `while`, `with`, `yield`, `async`, `await`, `arguments`, `eval`, `of`, `enum`, `implements`, `interface`, `package`, `private`, `protected`, `public`, `static`, `abstract`, `as`, `declare`, `from`, `readonly`, `type`, `namespace`
887
+ - All Python reserved words: `False`, `None`, `True`, `and`, `as`, `assert`, `async`, `await`, `break`, `class`, `continue`, `def`, `del`, `elif`, `else`, `except`, `finally`, `for`, `from`, `global`, `if`, `import`, `in`, `is`, `lambda`, `nonlocal`, `not`, `or`, `pass`, `raise`, `return`, `try`, `while`, `with`, `yield`
888
+ - All JVAL system fields: `_NAME_`, `_RETURN_`, `_SCOPE_`, `_CONTINUE_`, `_ERROR_CODE_`, `_SUCCESS_CODE_`, `_DESCRIPTION_`, `_TESTS_`, `_SESSION_DATA_`
889
+
890
+ ---
891
+
892
+ ## Appendix: Quick field summary card
893
+
894
+ ```
895
+ Test Object
896
+ ├── _NAME_ (required) Unique ID / generated function name
897
+ ├── _DESCRIPTION_ (optional) Error message on failure
898
+ ├── _SCOPE_ (optional, default "$") JSONPath → array to iterate over
899
+ ├── _CONTINUE_ (optional) JVAL expression: if true → skip this element
900
+ ├── _ERROR_CODE_ (optional, default 30000) Failure code
901
+ ├── _SUCCESS_CODE_ (optional, default 200) Success code
902
+ ├── _RETURN_ (required) JVAL string expression OR array of test objects
903
+ └── <varName> User variable: JSONPath string or string[] literal
904
+
905
+ Variable value prefixes
906
+ ├── "$.some.path" Extract from current scope element
907
+ ├── "$._EXTERNAL.<key>" Read from external/session data
908
+ └── "$._EXTERNAL._SELF.*" Access full normalised payload from any scope
909
+
910
+ Config root
911
+ ├── _TESTS_ { action: [TestObject, ...] }
912
+ └── _SESSION_DATA_ { action: { key: "$.json.path" } }
913
+ ```