ondc-code-generator 0.8.0 → 0.8.2
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/generator/config-compiler.js +47 -36
- package/dist/generator/generators/go/go-generator.js +12 -5
- package/dist/generator/generators/go/templates/json-path-utils.mustache +1 -1
- package/dist/generator/generators/go/templates/storage-templates/api-save.mustache +1 -1
- package/dist/generator/generators/go/templates/test-config.mustache +16 -7
- package/dist/generator/generators/go/templates/validation-utils.mustache +107 -24
- package/dist/generator/generators/python/py-generator.js +0 -1
- package/dist/generator/generators/typescript/ts-generator.js +0 -2
- package/dist/generator/validators/tests-config/sub-validations.js +15 -4
- package/package.json +1 -1
- package/sample.md +47 -0
|
@@ -58,43 +58,54 @@ export class ConfigCompiler {
|
|
|
58
58
|
};
|
|
59
59
|
// };
|
|
60
60
|
this.generateCode = async (valConfig, codeName = "L1-Validations", minimal = false, outputPath = "./", absolutePath = false) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
valConfig =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
try {
|
|
62
|
+
console.log("[ CODE GENERATION ] Starting code generation...");
|
|
63
|
+
valConfig = JSON.parse(JSON.stringify(valConfig));
|
|
64
|
+
if (this.generatorConfig?.duplicateVariablesInChildren) {
|
|
65
|
+
valConfig = duplicateVariablesInChildren(valConfig);
|
|
66
|
+
}
|
|
67
|
+
if (minimal) {
|
|
68
|
+
await this.withMinimalValidations(valConfig);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
await this.performValidations(valConfig);
|
|
72
|
+
}
|
|
73
|
+
// Generate code based on the language
|
|
74
|
+
const targetPath = absolutePath
|
|
75
|
+
? outputPath
|
|
76
|
+
: `${outputPath}generated/${codeName}`;
|
|
77
|
+
switch (this.language) {
|
|
78
|
+
case SupportedLanguages.Typescript:
|
|
79
|
+
await new TypescriptGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
|
|
80
|
+
codeName: codeName,
|
|
81
|
+
});
|
|
82
|
+
break;
|
|
83
|
+
case SupportedLanguages.Python:
|
|
84
|
+
await new PythonGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
|
|
85
|
+
codeName: codeName,
|
|
86
|
+
});
|
|
87
|
+
break;
|
|
88
|
+
case SupportedLanguages.Javascript:
|
|
89
|
+
await new JavascriptGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
|
|
90
|
+
codeName: codeName,
|
|
91
|
+
});
|
|
92
|
+
break;
|
|
93
|
+
case SupportedLanguages.Golang:
|
|
94
|
+
await new GoGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
|
|
95
|
+
codeName: codeName,
|
|
96
|
+
});
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
throw new Error("Language not supported");
|
|
100
|
+
}
|
|
101
|
+
console.log("[ CODE GENERATION ] Code generation completed successfully.");
|
|
70
102
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
await new TypescriptGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
|
|
78
|
-
codeName: codeName,
|
|
79
|
-
});
|
|
80
|
-
break;
|
|
81
|
-
case SupportedLanguages.Python:
|
|
82
|
-
await new PythonGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
|
|
83
|
-
codeName: codeName,
|
|
84
|
-
});
|
|
85
|
-
break;
|
|
86
|
-
case SupportedLanguages.Javascript:
|
|
87
|
-
await new JavascriptGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
|
|
88
|
-
codeName: codeName,
|
|
89
|
-
});
|
|
90
|
-
break;
|
|
91
|
-
case SupportedLanguages.Golang:
|
|
92
|
-
await new GoGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
|
|
93
|
-
codeName: codeName,
|
|
94
|
-
});
|
|
95
|
-
break;
|
|
96
|
-
default:
|
|
97
|
-
throw new Error("Language not supported");
|
|
103
|
+
catch (e) {
|
|
104
|
+
console.error(`\n [ CODE GENERATION ERROR ] ${e?.message || e}\n`);
|
|
105
|
+
if (e?.stack) {
|
|
106
|
+
console.error("Stack trace:");
|
|
107
|
+
console.error(e.stack);
|
|
108
|
+
}
|
|
98
109
|
}
|
|
99
110
|
};
|
|
100
111
|
this.generateL0Schema = async (outputPath = "./", type = "typescript", absolutePath = false) => {
|
|
@@ -82,13 +82,17 @@ export class GoGenerator extends CodeGenerator {
|
|
|
82
82
|
const loadData = relevantSessionData[action] || {};
|
|
83
83
|
const saveData = sessionData[action] || {};
|
|
84
84
|
const saveCode = Mustache.render(saveActionTemplate, {
|
|
85
|
-
storeActions: Object.keys(saveData)
|
|
85
|
+
storeActions: Object.keys(saveData)
|
|
86
|
+
.filter((f) => f !== "_SELF")
|
|
87
|
+
.map((key) => {
|
|
86
88
|
return {
|
|
87
89
|
key: key,
|
|
88
90
|
value: saveData[key],
|
|
89
91
|
};
|
|
90
92
|
}),
|
|
91
|
-
loadActions: Object.keys(loadData)
|
|
93
|
+
loadActions: Object.keys(loadData)
|
|
94
|
+
.filter((f) => f !== "_SELF")
|
|
95
|
+
.map((key) => {
|
|
92
96
|
console.log(loadData[key]);
|
|
93
97
|
return {
|
|
94
98
|
key: loadData[key],
|
|
@@ -159,7 +163,7 @@ export class GoGenerator extends CodeGenerator {
|
|
|
159
163
|
normalizedPayload := validationutils.NormalizeKeys(payload)
|
|
160
164
|
|
|
161
165
|
// Set _SELF
|
|
162
|
-
externalData
|
|
166
|
+
externalData["_SELF"] = normalizedPayload
|
|
163
167
|
|
|
164
168
|
// Load stateful data if needed
|
|
165
169
|
if completeConfig.StateFullValidations {
|
|
@@ -256,7 +260,8 @@ ${importList.map((imp) => `\t${imp}`).join("\n")}
|
|
|
256
260
|
for (const api of apis) {
|
|
257
261
|
const keys = Object.keys(this.validationConfig[ConfigSyntax.SessionData][api]);
|
|
258
262
|
for (const key of keys) {
|
|
259
|
-
|
|
263
|
+
const goVarName = key;
|
|
264
|
+
result.push({ name: key, keyName: goVarName });
|
|
260
265
|
}
|
|
261
266
|
}
|
|
262
267
|
result = result.filter((v) => v.name !== "_SELF");
|
|
@@ -287,7 +292,9 @@ ${importList.map((imp) => `\t${imp}`).join("\n")}
|
|
|
287
292
|
}
|
|
288
293
|
let final = "";
|
|
289
294
|
if (value.includes("_EXTERNAL")) {
|
|
290
|
-
|
|
295
|
+
// $._EXTERNAL.some.path -> $.some.path
|
|
296
|
+
const converted = value.replace("$._EXTERNAL", "$");
|
|
297
|
+
final = `validationutils.GetJsonPath(input.ExternalData, "${converted}",true)`;
|
|
291
298
|
}
|
|
292
299
|
else {
|
|
293
300
|
final =
|
|
@@ -73,7 +73,7 @@ func resolveJSONPathData(path string, data interface{}) (string, interface{}, er
|
|
|
73
73
|
if input, ok := data.(ValidationInput); ok {
|
|
74
74
|
if strings.HasPrefix(path, "$._EXTERNAL._SELF") {
|
|
75
75
|
resolvedPath := strings.Replace(path, "$._EXTERNAL._SELF", "$", 1)
|
|
76
|
-
return resolvedPath, input.ExternalData
|
|
76
|
+
return resolvedPath, input.ExternalData["_SELF"], nil
|
|
77
77
|
}else if( strings.HasPrefix(path, "$._EXTERNAL")){
|
|
78
78
|
resolvedPath := strings.Replace(path, "$._EXTERNAL", "$", 1)
|
|
79
79
|
return resolvedPath, input.ExternalData, nil
|
|
@@ -35,7 +35,7 @@ func LoadFor_{{action}}(
|
|
|
35
35
|
if err != nil {
|
|
36
36
|
return result, fmt.Errorf("failed to load key {{{key}}}: %w", err)
|
|
37
37
|
}
|
|
38
|
-
result
|
|
38
|
+
result["{{{key}}}"] = {{{key}}}_value
|
|
39
39
|
{{/loadActions}}
|
|
40
40
|
|
|
41
41
|
fmt.Printf("Loaded external data for action {{action}}: %+v\n", result)
|
|
@@ -43,13 +43,22 @@ type DebugInfo struct {
|
|
|
43
43
|
FedConfig interface{} `json:"fedConfig"`
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// ExternalData holds external data references for validation
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
46
|
+
// ExternalData holds external data references for validation as a map structure.
|
|
47
|
+
//
|
|
48
|
+
// Expected keys:
|
|
49
|
+
// - "_SELF": (interface{}) The current validation context object
|
|
50
|
+
{{#externalData}}
|
|
51
|
+
// - "{{name}}": (any) {{keyName}} data reference
|
|
52
|
+
{{/externalData}}
|
|
53
|
+
//
|
|
54
|
+
// Example usage:
|
|
55
|
+
// data := ExternalData{
|
|
56
|
+
// "_SELF": currentObject,
|
|
57
|
+
{{#externalData}}
|
|
58
|
+
// "{{name}}": {{keyName}}Value,
|
|
59
|
+
{{/externalData}}
|
|
60
|
+
// }
|
|
61
|
+
type ExternalData = map[string]interface{}
|
|
53
62
|
|
|
54
63
|
// ValidationInput represents the input data for validation functions
|
|
55
64
|
type ValidationInput struct {
|
|
@@ -50,18 +50,102 @@ func ArePresent(operand []interface{}) bool {
|
|
|
50
50
|
return NoneIn(operand, []interface{}{"null", "undefined"}) && len(strs) > 0
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// Lookup interface for abstraction
|
|
54
|
+
type lookup interface {
|
|
55
|
+
contains(string) bool
|
|
56
|
+
isEmpty() bool
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Map-based lookup (O(1))
|
|
60
|
+
type mapLookup struct {
|
|
61
|
+
items map[string]bool
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func (m *mapLookup) contains(item string) bool {
|
|
65
|
+
return m.items[item]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func (m *mapLookup) isEmpty() bool {
|
|
69
|
+
return len(m.items) == 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Slice-based lookup (O(n))
|
|
73
|
+
type sliceLookup struct {
|
|
74
|
+
items []string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func (s *sliceLookup) contains(item string) bool {
|
|
78
|
+
for _, v := range s.items {
|
|
79
|
+
if v == item {
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func (s *sliceLookup) isEmpty() bool {
|
|
87
|
+
return len(s.items) == 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Smart lookup creation - auto-detects and optimizes
|
|
91
|
+
func createLookup(right []interface{}) (lookup, bool) {
|
|
92
|
+
// Case 1: Check if right contains a single map
|
|
93
|
+
if len(right) == 1 {
|
|
94
|
+
switch m := right[0].(type) {
|
|
95
|
+
case map[string]interface{}:
|
|
96
|
+
result := make(map[string]bool, len(m))
|
|
97
|
+
for k := range m {
|
|
98
|
+
result[k] = true
|
|
99
|
+
}
|
|
100
|
+
return &mapLookup{items: result}, true
|
|
101
|
+
case map[string]bool:
|
|
102
|
+
return &mapLookup{items: m}, true
|
|
103
|
+
case map[string]string:
|
|
104
|
+
result := make(map[string]bool, len(m))
|
|
105
|
+
for k := range m {
|
|
106
|
+
result[k] = true
|
|
107
|
+
}
|
|
108
|
+
return &mapLookup{items: result}, true
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Case 2: Convert slice to string slice
|
|
113
|
+
rightStrs, ok := toStringSlice(right)
|
|
114
|
+
if !ok {
|
|
115
|
+
return nil, false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Optimization: Convert large slices to maps for better performance
|
|
119
|
+
const threshold = 10
|
|
120
|
+
if len(rightStrs) > threshold {
|
|
121
|
+
m := make(map[string]bool, len(rightStrs))
|
|
122
|
+
for _, v := range rightStrs {
|
|
123
|
+
m[v] = true
|
|
124
|
+
}
|
|
125
|
+
return &mapLookup{items: m}, true
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return &sliceLookup{items: rightStrs}, true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Updated comparison functions
|
|
53
132
|
func AllIn(left []interface{}, right []interface{}) bool {
|
|
54
133
|
leftStrs, ok1 := toStringSlice(left)
|
|
55
|
-
|
|
56
|
-
if !ok1 || !ok2 {
|
|
134
|
+
if !ok1 {
|
|
57
135
|
return false
|
|
58
136
|
}
|
|
59
|
-
|
|
60
|
-
|
|
137
|
+
|
|
138
|
+
rightLookup, ok2 := createLookup(right)
|
|
139
|
+
if !ok2 {
|
|
61
140
|
return false
|
|
62
141
|
}
|
|
142
|
+
|
|
143
|
+
if len(leftStrs) == 0 && !rightLookup.isEmpty() {
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
63
147
|
for _, v := range leftStrs {
|
|
64
|
-
if !contains(
|
|
148
|
+
if !rightLookup.contains(v) {
|
|
65
149
|
return false
|
|
66
150
|
}
|
|
67
151
|
}
|
|
@@ -70,16 +154,21 @@ func AllIn(left []interface{}, right []interface{}) bool {
|
|
|
70
154
|
|
|
71
155
|
func AnyIn(left []interface{}, right []interface{}) bool {
|
|
72
156
|
leftStrs, ok1 := toStringSlice(left)
|
|
73
|
-
|
|
74
|
-
if !ok1 || !ok2 {
|
|
157
|
+
if !ok1 {
|
|
75
158
|
return false
|
|
76
159
|
}
|
|
77
|
-
|
|
78
|
-
|
|
160
|
+
|
|
161
|
+
rightLookup, ok2 := createLookup(right)
|
|
162
|
+
if !ok2 {
|
|
79
163
|
return false
|
|
80
164
|
}
|
|
165
|
+
|
|
166
|
+
if len(leftStrs) == 0 && !rightLookup.isEmpty() {
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
|
|
81
170
|
for _, v := range leftStrs {
|
|
82
|
-
if contains(
|
|
171
|
+
if rightLookup.contains(v) {
|
|
83
172
|
return true
|
|
84
173
|
}
|
|
85
174
|
}
|
|
@@ -88,19 +177,22 @@ func AnyIn(left []interface{}, right []interface{}) bool {
|
|
|
88
177
|
|
|
89
178
|
func NoneIn(left []interface{}, right []interface{}) bool {
|
|
90
179
|
leftStrs, ok1 := toStringSlice(left)
|
|
91
|
-
|
|
92
|
-
if !ok1 || !ok2 {
|
|
180
|
+
if !ok1 {
|
|
93
181
|
return false
|
|
94
182
|
}
|
|
95
|
-
|
|
183
|
+
|
|
184
|
+
rightLookup, ok2 := createLookup(right)
|
|
185
|
+
if !ok2 {
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
|
|
96
189
|
for _, v := range leftStrs {
|
|
97
|
-
if contains(
|
|
190
|
+
if rightLookup.contains(v) {
|
|
98
191
|
return false
|
|
99
192
|
}
|
|
100
193
|
}
|
|
101
194
|
return true
|
|
102
195
|
}
|
|
103
|
-
|
|
104
196
|
func EqualTo(left []interface{}, right []interface{}) bool {
|
|
105
197
|
leftStrs, ok1 := toStringSlice(left)
|
|
106
198
|
rightStrs, ok2 := toStringSlice(right)
|
|
@@ -299,15 +391,6 @@ func isISO8601(str string) bool {
|
|
|
299
391
|
return err == nil
|
|
300
392
|
}
|
|
301
393
|
|
|
302
|
-
func contains(slice []string, item string) bool {
|
|
303
|
-
for _, v := range slice {
|
|
304
|
-
if v == item {
|
|
305
|
-
return true
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return false
|
|
309
|
-
}
|
|
310
|
-
|
|
311
394
|
// StringSliceToInterface converts []string to []interface{}
|
|
312
395
|
func StringSliceToInterface(strs []string) []interface{} {
|
|
313
396
|
result := make([]interface{}, len(strs))
|
|
@@ -23,7 +23,6 @@ export class PythonGenerator extends CodeGenerator {
|
|
|
23
23
|
const tests = this.validationConfig[ConfigSyntax.Tests];
|
|
24
24
|
const relevantSessionData = {};
|
|
25
25
|
collectLoadData(tests, relevantSessionData);
|
|
26
|
-
console.log("Relevant Session Data for Loading:", relevantSessionData);
|
|
27
26
|
const sessionDataUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/save-utils.mustache"), "utf-8");
|
|
28
27
|
const storageInterfaceTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-interface.mustache"), "utf-8");
|
|
29
28
|
const storageTypesTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-types.mustache"), "utf-8");
|
|
@@ -24,8 +24,6 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
24
24
|
const tests = this.validationConfig[ConfigSyntax.Tests];
|
|
25
25
|
const relevantSessionData = {};
|
|
26
26
|
collectLoadData(tests, relevantSessionData);
|
|
27
|
-
console.log("Relevant Session Data for Loading:", relevantSessionData);
|
|
28
|
-
const actions = Object.keys(sessionData);
|
|
29
27
|
const sessionDataUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/save-utils.mustache"), "utf-8");
|
|
30
28
|
const storageInterfaceTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-interface.mustache"), "utf-8");
|
|
31
29
|
const storageTypesTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-types.mustache"), "utf-8");
|
|
@@ -119,7 +119,12 @@ export class VariableValidator extends TestObjectValidator {
|
|
|
119
119
|
if (this.targetObject[TestObjectSyntax.Scope]) {
|
|
120
120
|
const scope = this.targetObject[TestObjectSyntax.Scope];
|
|
121
121
|
const pathWithoutDollar = cleanseDollarDot(path);
|
|
122
|
-
|
|
122
|
+
if (pathWithoutDollar.startsWith("[")) {
|
|
123
|
+
path = `${scope}${pathWithoutDollar}`;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
path = `${scope}.${pathWithoutDollar}`;
|
|
127
|
+
}
|
|
123
128
|
}
|
|
124
129
|
const replaced = replaceBracketsWithAsteriskNested(path);
|
|
125
130
|
if (this.minimal)
|
|
@@ -153,6 +158,12 @@ export class VariableValidator extends TestObjectValidator {
|
|
|
153
158
|
if (!definedExternalValues.includes(externalData)) {
|
|
154
159
|
throw new Error(`${externalData} is not defined in ${ConfigSyntax.SessionData} data at path ${this.validationPath}`);
|
|
155
160
|
}
|
|
161
|
+
if (!path.includes("_SELF")) {
|
|
162
|
+
if (this.targetObject[TestObjectSyntax.Description] === undefined ||
|
|
163
|
+
this.targetObject[TestObjectSyntax.Description] === "") {
|
|
164
|
+
throw new Error(`Variable with path: ${path} is using external data but ${TestObjectSyntax.Description} is not provided at path ${this.validationPath}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
156
167
|
}
|
|
157
168
|
}
|
|
158
169
|
export class ContinueValidator extends TestObjectValidator {
|
|
@@ -204,10 +215,10 @@ export class ReturnValidator extends TestObjectValidator {
|
|
|
204
215
|
}
|
|
205
216
|
function cleanseDollarDot(path) {
|
|
206
217
|
if (path.startsWith(`$.`)) {
|
|
207
|
-
return path.slice(2);
|
|
218
|
+
return path.slice(2).trim();
|
|
208
219
|
}
|
|
209
220
|
else if (path.startsWith(`$`)) {
|
|
210
|
-
return path.slice(1);
|
|
221
|
+
return path.slice(1).trim();
|
|
211
222
|
}
|
|
212
|
-
return path;
|
|
223
|
+
return path.trim();
|
|
213
224
|
}
|
package/package.json
CHANGED
package/sample.md
CHANGED
|
@@ -271,3 +271,50 @@ This document outlines best practices for writing effective JVAL (JSON Validatio
|
|
|
271
271
|
4. **Inconsistent naming** - stick to snake_case throughout
|
|
272
272
|
5. **Hard-coded values in expressions** - use variables for maintainability
|
|
273
273
|
6. **Too broad JSONPath selectors** - be as specific as possible
|
|
274
|
+
|
|
275
|
+
## some complex test examples
|
|
276
|
+
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"_NAME_": "ITEMS_RETURNABLE",
|
|
280
|
+
"_SCOPE_": "$.message.catalog['bpp/providers'][*].items[*]",
|
|
281
|
+
"typeCode": "$.tags[?(@.code=='type')].list[*].code",
|
|
282
|
+
"typeValue": "$.tags[?(@.code=='type')].list[*].value",
|
|
283
|
+
"validCode": ["type"],
|
|
284
|
+
"valueValue": ["customization"],
|
|
285
|
+
"attr": "$['@ondc/org/returnable']",
|
|
286
|
+
"_CONTINUE_": "validCode all in typeCode && valueValue all in typeValue",
|
|
287
|
+
"_RETURN_": "attr are present"
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"_TESTS_": {
|
|
294
|
+
"search": [
|
|
295
|
+
{
|
|
296
|
+
"_NAME_": "sample_testing",
|
|
297
|
+
"ids": "$._EXTERNAL.full_ids",
|
|
298
|
+
"var1": "$.context.domain",
|
|
299
|
+
"_RETURN_": "var1 all in ids"
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
},
|
|
303
|
+
"_SESSION_DATA_": {
|
|
304
|
+
"search": {
|
|
305
|
+
"full_ids": null
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
```go
|
|
312
|
+
|
|
313
|
+
results, runErr := PerformL1_validations(action, payload, cfg, validationutils.ExternalData{
|
|
314
|
+
"full_ids" : map[string]bool{
|
|
315
|
+
"test": true,
|
|
316
|
+
"test2": false,
|
|
317
|
+
},
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
```
|