@platforma-sdk/workflow-tengo 2.9.6 → 2.9.8
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/tengo/lib/assets.lib.tengo +52 -52
- package/dist/tengo/lib/assets.swinfo.lib.tengo +45 -45
- package/dist/tengo/lib/constants.lib.tengo +32 -32
- package/dist/tengo/lib/exec.R.internal.lib.tengo +29 -29
- package/dist/tengo/lib/exec.R.lib.tengo +166 -166
- package/dist/tengo/lib/exec.constants.lib.tengo +4 -4
- package/dist/tengo/lib/exec.descriptor.lib.tengo +31 -31
- package/dist/tengo/lib/exec.internal.lib.tengo +297 -297
- package/dist/tengo/lib/exec.lib.tengo +815 -815
- package/dist/tengo/lib/exec.monetization.lib.tengo +130 -130
- package/dist/tengo/lib/exec.package.lib.tengo +212 -212
- package/dist/tengo/lib/exec.python.internal.lib.tengo +6 -6
- package/dist/tengo/lib/exec.python.lib.tengo +146 -146
- package/dist/tengo/lib/exec.runcmd.lib.tengo +371 -371
- package/dist/tengo/lib/exec.runenv.lib.tengo +92 -92
- package/dist/tengo/lib/exec.stream.lib.tengo +40 -40
- package/dist/tengo/lib/file.internal.lib.tengo +77 -77
- package/dist/tengo/lib/file.lib.tengo +57 -57
- package/dist/tengo/lib/ll.lib.tengo +244 -244
- package/dist/tengo/lib/maps.lib.tengo +152 -152
- package/dist/tengo/lib/objects.lib.tengo +13 -13
- package/dist/tengo/lib/oop.lib.tengo +19 -19
- package/dist/tengo/lib/path.lib.tengo +125 -125
- package/dist/tengo/lib/pframes.constants.lib.tengo +28 -28
- package/dist/tengo/lib/pframes.export-pcolumn.lib.tengo +33 -33
- package/dist/tengo/lib/pframes.lib.tengo +43 -43
- package/dist/tengo/lib/pframes.ll.lib.tengo +72 -72
- package/dist/tengo/lib/pframes.pcolumn.lib.tengo +49 -49
- package/dist/tengo/lib/pframes.spec.lib.tengo +96 -96
- package/dist/tengo/lib/pframes.util.lib.tengo +288 -288
- package/dist/tengo/lib/pframes.xsv.lib.tengo +272 -272
- package/dist/tengo/lib/render.lib.tengo +75 -75
- package/dist/tengo/lib/sets.lib.tengo +68 -68
- package/dist/tengo/lib/slices.lib.tengo +258 -155
- package/dist/tengo/lib/smart.lib.tengo +1069 -1069
- package/dist/tengo/lib/test.lib.tengo +161 -161
- package/dist/tengo/lib/tpl.lib.tengo +515 -515
- package/dist/tengo/lib/tpl.state-await.lib.tengo +240 -240
- package/dist/tengo/lib/tpl.util.lib.tengo +9 -9
- package/dist/tengo/lib/validation.lib.tengo +378 -378
- package/dist/tengo/lib/workdir.lib.tengo +331 -331
- package/dist/tengo/lib/workdir.proc.lib.tengo +144 -144
- package/dist/tengo/lib/workdir.save.lib.tengo +130 -130
- package/dist/tengo/lib/workflow.bobject.lib.tengo +26 -26
- package/dist/tengo/lib/workflow.bquery.lib.tengo +165 -165
- package/dist/tengo/lib/workflow.constants.lib.tengo +2 -2
- package/dist/tengo/lib/workflow.context.lib.tengo +32 -32
- package/dist/tengo/lib/workflow.lib.tengo +206 -206
- package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
- package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
- package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
- package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
- package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.aggregate.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.map-pframe.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
- package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
- package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
- package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
- package/package.json +2 -2
|
@@ -7,443 +7,443 @@ smart := import("@platforma-sdk/workflow-tengo:smart")
|
|
|
7
7
|
|
|
8
8
|
json := import("json")
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
//
|
|
11
|
+
// Tools for implementing your own checks:
|
|
12
|
+
//
|
|
13
13
|
|
|
14
14
|
_formatKeyPath := func(current, format, key) {
|
|
15
|
-
if current == "" {
|
|
16
|
-
if !is_string(key) {
|
|
17
|
-
return fmt.sprintf("[%d]", key)
|
|
18
|
-
}
|
|
15
|
+
if current == "" {
|
|
16
|
+
if !is_string(key) {
|
|
17
|
+
return fmt.sprintf("[%d]", key)
|
|
18
|
+
}
|
|
19
19
|
|
|
20
|
-
return key
|
|
21
|
-
}
|
|
20
|
+
return key
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
return fmt.sprintf(format, current, key)
|
|
23
|
+
return fmt.sprintf(format, current, key)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Validation success result.
|
|
28
|
+
*/
|
|
29
29
|
success := { result: true }
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Validation failure result with a message.
|
|
33
|
+
*
|
|
34
|
+
* @param keyPath: string - the current stack state.
|
|
35
|
+
* @param msg: string - the failure message.
|
|
36
|
+
* @param args: ...any - additional arguments for the message.
|
|
37
|
+
*/
|
|
38
38
|
fail := func(keyPath, msg, ...args) {
|
|
39
|
-
return {
|
|
40
|
-
result: false,
|
|
41
|
-
message: keyPath + ": " + fmt.sprintf(msg, args...)
|
|
42
|
-
}
|
|
39
|
+
return {
|
|
40
|
+
result: false,
|
|
41
|
+
message: keyPath + ": " + fmt.sprintf(msg, args...)
|
|
42
|
+
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
isSuccess := func(r) {
|
|
46
|
-
return r.result == true
|
|
46
|
+
return r.result == true
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Returns true if the validation failed.
|
|
51
|
+
*
|
|
52
|
+
* @param r: either fail or success.
|
|
53
|
+
*/
|
|
54
54
|
isFail := func(r) {
|
|
55
|
-
return r.result == false
|
|
55
|
+
return r.result == false
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Checks a condition and returns success or failure.
|
|
60
|
+
*
|
|
61
|
+
* @param condition: bool - the condition to check.
|
|
62
|
+
* @param stack: any - the current stack state.
|
|
63
|
+
* @param msg: string - the failure message if the condition is false.
|
|
64
|
+
* @param args: variadic any - additional arguments for the message.
|
|
65
|
+
*/
|
|
66
66
|
check := func(condition, keyPath, msg, ...args) {
|
|
67
|
-
if condition {
|
|
68
|
-
return success
|
|
69
|
-
}
|
|
67
|
+
if condition {
|
|
68
|
+
return success
|
|
69
|
+
}
|
|
70
70
|
|
|
71
|
-
return fail(keyPath, msg, args...)
|
|
71
|
+
return fail(keyPath, msg, args...)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
//
|
|
75
|
+
// Validation:
|
|
76
|
+
//
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Validates JSON types based on tags when the schema is a string.
|
|
80
|
+
*
|
|
81
|
+
* @param js: any - the JSON to validate.
|
|
82
|
+
* @param tags: { key: string, tags: []tag } - the tags to validate against.
|
|
83
|
+
* @param stack: ...any - the current stack state.
|
|
84
|
+
*/
|
|
85
85
|
_validateTypes := func(js, tags, keyPath, ...stack) {
|
|
86
|
-
k := tags.key
|
|
86
|
+
k := tags.key
|
|
87
87
|
|
|
88
|
-
if k == "any" {
|
|
89
|
-
return success
|
|
90
|
-
}
|
|
88
|
+
if k == "any" {
|
|
89
|
+
return success
|
|
90
|
+
}
|
|
91
91
|
|
|
92
|
-
if k == "alphanumeric" && ((is_string(js) && text.re_match("^[[:alnum:]]*$", js)) || is_int(js)) {
|
|
93
|
-
return success
|
|
94
|
-
}
|
|
92
|
+
if k == "alphanumeric" && ((is_string(js) && text.re_match("^[[:alnum:]]*$", js)) || is_int(js)) {
|
|
93
|
+
return success
|
|
94
|
+
}
|
|
95
95
|
|
|
96
|
-
if k == "string" && is_string(js) {
|
|
97
|
-
regex := tags.tags["regex"]
|
|
98
|
-
if !is_undefined(regex) && !text.re_match(regex, js) {
|
|
99
|
-
return fail(keyPath, "value %q does not conform regex %q", js, regex)
|
|
100
|
-
}
|
|
96
|
+
if k == "string" && is_string(js) {
|
|
97
|
+
regex := tags.tags["regex"]
|
|
98
|
+
if !is_undefined(regex) && !text.re_match(regex, js) {
|
|
99
|
+
return fail(keyPath, "value %q does not conform regex %q", js, regex)
|
|
100
|
+
}
|
|
101
101
|
|
|
102
|
-
return success
|
|
103
|
-
}
|
|
102
|
+
return success
|
|
103
|
+
}
|
|
104
104
|
|
|
105
|
-
if k == "char" && is_char(js) {
|
|
106
|
-
return success
|
|
107
|
-
}
|
|
105
|
+
if k == "char" && is_char(js) {
|
|
106
|
+
return success
|
|
107
|
+
}
|
|
108
108
|
|
|
109
|
-
if k == "bytes" && is_bytes(js) {
|
|
110
|
-
return success
|
|
111
|
-
}
|
|
109
|
+
if k == "bytes" && is_bytes(js) {
|
|
110
|
+
return success
|
|
111
|
+
}
|
|
112
112
|
|
|
113
|
-
if k == "null" && is_undefined(js) {
|
|
114
|
-
return success
|
|
115
|
-
}
|
|
113
|
+
if k == "null" && is_undefined(js) {
|
|
114
|
+
return success
|
|
115
|
+
}
|
|
116
116
|
|
|
117
|
-
if k == "number" && (is_int(js) || is_float(js)) {
|
|
118
|
-
return success
|
|
119
|
-
}
|
|
117
|
+
if k == "number" && (is_int(js) || is_float(js)) {
|
|
118
|
+
return success
|
|
119
|
+
}
|
|
120
120
|
|
|
121
|
-
if k == "bool" && is_bool(js) {
|
|
122
|
-
return success
|
|
123
|
-
}
|
|
121
|
+
if k == "bool" && is_bool(js) {
|
|
122
|
+
return success
|
|
123
|
+
}
|
|
124
124
|
|
|
125
|
-
return fail(keyPath, "js %q is not one of a type: %s", js, tags.key)
|
|
125
|
+
return fail(keyPath, "js %q is not one of a type: %s", js, tags.key)
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Parses JSON tags from a string rule.
|
|
130
|
+
*
|
|
131
|
+
* @param rule: string - a key with tags, e.g. "any,type=string,regex=abc.*"
|
|
132
|
+
* @return parsedRule: { key: string, tags: map } - the key and tags parsed from the rule.
|
|
133
|
+
*/
|
|
134
134
|
_parseTags := func(rule) {
|
|
135
|
-
tagsKV := text.split(rule, ",")
|
|
136
|
-
key := tagsKV[0]
|
|
137
|
-
tagsKV = tagsKV[1:]
|
|
138
|
-
|
|
139
|
-
tags := {}
|
|
140
|
-
for _, kv in tagsKV {
|
|
141
|
-
kAndV := text.split(kv, "=")
|
|
142
|
-
k := kAndV[0]
|
|
143
|
-
if len(kAndV) == 1 {
|
|
144
|
-
tags[k] = true
|
|
145
|
-
} else {
|
|
146
|
-
tags[k] = kAndV[1]
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return ll.toStrict({
|
|
151
|
-
key: key,
|
|
152
|
-
tags: tags
|
|
153
|
-
})
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
135
|
+
tagsKV := text.split(rule, ",")
|
|
136
|
+
key := tagsKV[0]
|
|
137
|
+
tagsKV = tagsKV[1:]
|
|
138
|
+
|
|
139
|
+
tags := {}
|
|
140
|
+
for _, kv in tagsKV {
|
|
141
|
+
kAndV := text.split(kv, "=")
|
|
142
|
+
k := kAndV[0]
|
|
143
|
+
if len(kAndV) == 1 {
|
|
144
|
+
tags[k] = true
|
|
145
|
+
} else {
|
|
146
|
+
tags[k] = kAndV[1]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return ll.toStrict({
|
|
151
|
+
key: key,
|
|
152
|
+
tags: tags
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Validates JSON against a schema.
|
|
158
|
+
*
|
|
159
|
+
* @param js: any - the JSON to validate.
|
|
160
|
+
* @param schema: any - the schema to validate against.
|
|
161
|
+
* @param stack: ...any - the current stack state.
|
|
162
|
+
* @return either fail or success
|
|
163
|
+
*/
|
|
164
164
|
_validateJson := func(js, schema, keyPath, ...stack) {
|
|
165
|
-
if is_undefined(schema) {
|
|
166
|
-
if len(stack) == 0 {
|
|
167
|
-
return fail(keyPath, "key is not defined by schema, but exists in the JSON")
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return fail(keyPath, "key is not defined by schema, but exists in the JSON. Validated item: %#v", stack[0])
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if is_string(schema) {
|
|
176
|
-
if schema == "any" {
|
|
177
|
-
return success
|
|
178
|
-
}
|
|
179
|
-
tags := _parseTags(schema)
|
|
180
|
-
|
|
181
|
-
return _validateTypes(js, tags, keyPath, stack...)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if is_callable(schema) {
|
|
188
|
-
return schema(js, keyPath, stack)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if is_array(schema) && len(schema) > 0 && schema[0] == "or" {
|
|
195
|
-
for i, elem in schema[1:] {
|
|
196
|
-
r := _validateJson(js, elem, keyPath, stack...)
|
|
197
|
-
if isSuccess(r) {
|
|
198
|
-
return r
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return fail(keyPath, "js %q does not fit any of the following rules: %q", js, schema)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if is_array(schema) {
|
|
209
|
-
if !is_array(js) {
|
|
210
|
-
return fail(keyPath, "js %q must be array", js)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
schema = schema[0]
|
|
214
|
-
for i, elem in js {
|
|
215
|
-
r := _validateJson(elem, schema, _formatKeyPath(keyPath, "%s[%d]", i), js, stack...)
|
|
216
|
-
if isFail(r) {
|
|
217
|
-
return r
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return success
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if !ll.isMap(js) {
|
|
229
|
-
return fail(keyPath, "js %#v must be map", js)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
schemaTags := {}
|
|
234
|
-
|
|
235
|
-
schemaRules := {}
|
|
236
|
-
|
|
237
|
-
jsFull := {}
|
|
238
|
-
|
|
239
|
-
for k, v in schema {
|
|
240
|
-
p := _parseTags(k)
|
|
241
|
-
schemaTags[p.key] = p
|
|
242
|
-
schemaRules[p.key] = v
|
|
243
|
-
jsFull[p.key] = undefined
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
any := schemaTags["any"]
|
|
248
|
-
if !is_undefined(any) {
|
|
249
|
-
if is_undefined(jsFull["any"]) { // in case actual object has field named "any"
|
|
250
|
-
delete(jsFull, "any")
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
options := schemaTags["__options__"]
|
|
255
|
-
if is_undefined(options) {
|
|
256
|
-
options = {}
|
|
257
|
-
} else {
|
|
258
|
-
delete(schemaTags, "__options__")
|
|
259
|
-
delete(schemaRules, "__options__")
|
|
260
|
-
if is_undefined(jsFull["__options__"]) { // in case actual object has field named "__options__"
|
|
261
|
-
delete(jsFull, "__options__")
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
for k, v in js {
|
|
267
|
-
jsFull[k] = v
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
for _key, elem in jsFull {
|
|
271
|
-
if any {
|
|
272
|
-
r := _validateJson(elem, schemaRules["any"], _formatKeyPath(keyPath, "%s.%s", _key), js, stack...)
|
|
273
|
-
if isFail(r) {
|
|
274
|
-
return r
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if !is_undefined(any.tags["type"]) {
|
|
278
|
-
r := _validateJson(_key, any.tags["type"], keyPath, stack...)
|
|
279
|
-
if isFail(r) {
|
|
280
|
-
return r
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
continue
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
key := schemaTags[_key]
|
|
289
|
-
|
|
290
|
-
if is_undefined(key) {
|
|
291
|
-
if options.tags["closed"] == true {
|
|
292
|
-
return fail(keyPath, "only keys %v from the schema must be set, found %q", maps.getKeys(schema), _key)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
continue
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if key.tags["type"] {
|
|
299
|
-
r := _validateJson(_key, key.tags["type"], keyPath, stack...)
|
|
300
|
-
if isFail(r) {
|
|
301
|
-
return r
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if is_undefined(elem) {
|
|
306
|
-
if key.tags["omitempty"] || key.tags["optional"] || key.tags["?"] {
|
|
307
|
-
continue
|
|
308
|
-
}
|
|
309
|
-
return fail(keyPath, "value %q does not contain key %q", js, _key)
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
r := _validateJson(elem, schemaRules[_key], _formatKeyPath(keyPath, "%s.%s", _key), js, stack...)
|
|
313
|
-
if isFail(r) {
|
|
314
|
-
return r
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return success
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
165
|
+
if is_undefined(schema) {
|
|
166
|
+
if len(stack) == 0 {
|
|
167
|
+
return fail(keyPath, "key is not defined by schema, but exists in the JSON")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return fail(keyPath, "key is not defined by schema, but exists in the JSON. Validated item: %#v", stack[0])
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// if the schema is a string, then
|
|
174
|
+
// we check the predefined type.
|
|
175
|
+
if is_string(schema) {
|
|
176
|
+
if schema == "any" {
|
|
177
|
+
return success
|
|
178
|
+
}
|
|
179
|
+
tags := _parseTags(schema)
|
|
180
|
+
|
|
181
|
+
return _validateTypes(js, tags, keyPath, stack...)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// if the schema is a function, call it
|
|
185
|
+
// with the element and the path of how we
|
|
186
|
+
// got this element, i.e. recursion history.
|
|
187
|
+
if is_callable(schema) {
|
|
188
|
+
return schema(js, keyPath, stack)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// if the schema is an array with "or" at the beginning,
|
|
192
|
+
// we should check if the element is succeed with
|
|
193
|
+
// any of the following elements.
|
|
194
|
+
if is_array(schema) && len(schema) > 0 && schema[0] == "or" {
|
|
195
|
+
for i, elem in schema[1:] {
|
|
196
|
+
r := _validateJson(js, elem, keyPath, stack...)
|
|
197
|
+
if isSuccess(r) {
|
|
198
|
+
return r
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return fail(keyPath, "js %q does not fit any of the following rules: %q", js, schema)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// if the schema is an array, then check that every
|
|
206
|
+
// element of the json has the schema that is the first element
|
|
207
|
+
// of the schema.
|
|
208
|
+
if is_array(schema) {
|
|
209
|
+
if !is_array(js) {
|
|
210
|
+
return fail(keyPath, "js %q must be array", js)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
schema = schema[0]
|
|
214
|
+
for i, elem in js {
|
|
215
|
+
r := _validateJson(elem, schema, _formatKeyPath(keyPath, "%s[%d]", i), js, stack...)
|
|
216
|
+
if isFail(r) {
|
|
217
|
+
return r
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return success
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// only map left, since schema can't be undefined
|
|
225
|
+
// if the schema is a map, check that every key
|
|
226
|
+
// in json-object is in the schema and vice-versa,
|
|
227
|
+
// unless there are tags "any" or "omitempty".
|
|
228
|
+
if !ll.isMap(js) {
|
|
229
|
+
return fail(keyPath, "js %#v must be map", js)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// schema.key -> tags
|
|
233
|
+
schemaTags := {}
|
|
234
|
+
// schema.key -> rule
|
|
235
|
+
schemaRules := {}
|
|
236
|
+
// js padded with all keys from schema
|
|
237
|
+
jsFull := {}
|
|
238
|
+
|
|
239
|
+
for k, v in schema {
|
|
240
|
+
p := _parseTags(k)
|
|
241
|
+
schemaTags[p.key] = p
|
|
242
|
+
schemaRules[p.key] = v
|
|
243
|
+
jsFull[p.key] = undefined
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// tags that should be applied to all elements
|
|
247
|
+
any := schemaTags["any"]
|
|
248
|
+
if !is_undefined(any) {
|
|
249
|
+
if is_undefined(jsFull["any"]) { // in case actual object has field named "any"
|
|
250
|
+
delete(jsFull, "any")
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
options := schemaTags["__options__"]
|
|
255
|
+
if is_undefined(options) {
|
|
256
|
+
options = {}
|
|
257
|
+
} else {
|
|
258
|
+
delete(schemaTags, "__options__")
|
|
259
|
+
delete(schemaRules, "__options__")
|
|
260
|
+
if is_undefined(jsFull["__options__"]) { // in case actual object has field named "__options__"
|
|
261
|
+
delete(jsFull, "__options__")
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// transferring fields from input map into padded map
|
|
266
|
+
for k, v in js {
|
|
267
|
+
jsFull[k] = v
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for _key, elem in jsFull {
|
|
271
|
+
if any {
|
|
272
|
+
r := _validateJson(elem, schemaRules["any"], _formatKeyPath(keyPath, "%s.%s", _key), js, stack...)
|
|
273
|
+
if isFail(r) {
|
|
274
|
+
return r
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if !is_undefined(any.tags["type"]) {
|
|
278
|
+
r := _validateJson(_key, any.tags["type"], keyPath, stack...)
|
|
279
|
+
if isFail(r) {
|
|
280
|
+
return r
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// tags for the key
|
|
288
|
+
key := schemaTags[_key]
|
|
289
|
+
|
|
290
|
+
if is_undefined(key) {
|
|
291
|
+
if options.tags["closed"] == true {
|
|
292
|
+
return fail(keyPath, "only keys %v from the schema must be set, found %q", maps.getKeys(schema), _key)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if key.tags["type"] {
|
|
299
|
+
r := _validateJson(_key, key.tags["type"], keyPath, stack...)
|
|
300
|
+
if isFail(r) {
|
|
301
|
+
return r
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if is_undefined(elem) {
|
|
306
|
+
if key.tags["omitempty"] || key.tags["optional"] || key.tags["?"] {
|
|
307
|
+
continue
|
|
308
|
+
}
|
|
309
|
+
return fail(keyPath, "value %q does not contain key %q", js, _key)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
r := _validateJson(elem, schemaRules[_key], _formatKeyPath(keyPath, "%s.%s", _key), js, stack...)
|
|
313
|
+
if isFail(r) {
|
|
314
|
+
return r
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return success
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Checks if the given JSON conforms to the given schema.
|
|
323
|
+
*
|
|
324
|
+
* @param js: any - the JSON to check.
|
|
325
|
+
* @param schema: any - the schema to check against.
|
|
326
|
+
* @return either fail or success
|
|
327
|
+
*/
|
|
328
328
|
checkJson := func(js, schema) {
|
|
329
|
-
return _validateJson(js, schema, "")
|
|
329
|
+
return _validateJson(js, schema, "")
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Checks if the given JSON is valid according to the given schema.
|
|
334
|
+
*
|
|
335
|
+
* @param js: any - the JSON to check.
|
|
336
|
+
* @param schema: any - the schema to check against.
|
|
337
|
+
* @return result: bool - the result of the validation (true/false).
|
|
338
|
+
*/
|
|
339
339
|
isValid := func(js, schema) {
|
|
340
|
-
return _validateJson(js, schema, "").result
|
|
340
|
+
return _validateJson(js, schema, "").result
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
343
|
+
/**
|
|
344
|
+
* Asserts that a given JSON conforms to a given schema.
|
|
345
|
+
*
|
|
346
|
+
* @param js: any - the JSON to assert.
|
|
347
|
+
* @param schema: any - the schema to assert against.
|
|
348
|
+
* @param failMsg: string - optional parameter with custom message to be printed on validation error
|
|
349
|
+
*/
|
|
350
350
|
assertJsonSchema := func(js, schema, ...failMsg) {
|
|
351
|
-
r := _validateJson(js, schema, "")
|
|
351
|
+
r := _validateJson(js, schema, "")
|
|
352
352
|
|
|
353
|
-
msg := "JSON validation error"
|
|
354
|
-
if len(failMsg) > 0 {
|
|
355
|
-
msg = failMsg[0]
|
|
356
|
-
}
|
|
353
|
+
msg := "JSON validation error"
|
|
354
|
+
if len(failMsg) > 0 {
|
|
355
|
+
msg = failMsg[0]
|
|
356
|
+
}
|
|
357
357
|
|
|
358
|
-
if isFail(r) {
|
|
359
|
-
ll.panic("%s: %s", msg, r.message)
|
|
358
|
+
if isFail(r) {
|
|
359
|
+
ll.panic("%s: %s", msg, r.message)
|
|
360
|
+
}
|
|
360
361
|
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
362
|
|
|
363
|
+
//
|
|
364
|
+
// Domain-specific schemas and checks:
|
|
365
|
+
//
|
|
371
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Checks if the given element is a resource.
|
|
369
|
+
*
|
|
370
|
+
* @param options: object - resource validation options
|
|
371
|
+
*/
|
|
372
372
|
resource := func(...args) {
|
|
373
373
|
|
|
374
|
-
ops := {}
|
|
375
|
-
if len(args) > 0 {
|
|
376
|
-
ops = args[0]
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
assertJsonSchema(ops, {
|
|
380
|
-
`type,?`: {
|
|
381
|
-
Name: `string`,
|
|
382
|
-
Version: `string`
|
|
383
|
-
}
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
checker := func(elem, keyPath, stack) {
|
|
387
|
-
c := undefined
|
|
388
|
-
|
|
389
|
-
c = check(smart.isResource(elem), keyPath, "the element " + elem + " must be a resource")
|
|
390
|
-
if isFail(c) {
|
|
391
|
-
return c
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if !is_undefined(ops.type) {
|
|
395
|
-
c = check(elem.info().Type.Name == ops.type.Name && elem.info().Type.Version == ops.type.Version, keyPath,
|
|
396
|
-
"the element " + elem + " must have the following type: ", ops.type, " found: ", elem.info().Type)
|
|
397
|
-
if isFail(c) {
|
|
398
|
-
return c
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return c
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return checker
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
374
|
+
ops := {}
|
|
375
|
+
if len(args) > 0 {
|
|
376
|
+
ops = args[0]
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
assertJsonSchema(ops, {
|
|
380
|
+
`type,?`: {
|
|
381
|
+
Name: `string`,
|
|
382
|
+
Version: `string`
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
checker := func(elem, keyPath, stack) {
|
|
387
|
+
c := undefined
|
|
388
|
+
|
|
389
|
+
c = check(smart.isResource(elem), keyPath, "the element " + elem + " must be a resource")
|
|
390
|
+
if isFail(c) {
|
|
391
|
+
return c
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if !is_undefined(ops.type) {
|
|
395
|
+
c = check(elem.info().Type.Name == ops.type.Name && elem.info().Type.Version == ops.type.Version, keyPath,
|
|
396
|
+
"the element " + elem + " must have the following type: ", ops.type, " found: ", elem.info().Type)
|
|
397
|
+
if isFail(c) {
|
|
398
|
+
return c
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return c
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return checker
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Checks if the given element is a resource with specified type.
|
|
410
|
+
*
|
|
411
|
+
* @param rt: ResourceType - desired resource type
|
|
412
|
+
*/
|
|
413
413
|
resourceType := func(rt) {
|
|
414
|
-
return func(elem, keyPath, stack) {
|
|
415
|
-
return check(smart.isResource(elem) && elem.info().Type.Name == rt.Name, keyPath, "the element " + elem + " must be a resource")
|
|
416
|
-
}
|
|
414
|
+
return func(elem, keyPath, stack) {
|
|
415
|
+
return check(smart.isResource(elem) && elem.info().Type.Name == rt.Name, keyPath, "the element " + elem + " must be a resource")
|
|
416
|
+
}
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Checks if the given element is a reference.
|
|
421
|
+
*
|
|
422
|
+
* @param elem: any - the element to check.
|
|
423
|
+
* @param stack: any - the current stack state.
|
|
424
|
+
*/
|
|
425
425
|
reference := func(elem, keyPath, stack) {
|
|
426
|
-
return check(smart.isReference(elem), keyPath, "the element " + elem + " must be a resource or a field")
|
|
426
|
+
return check(smart.isReference(elem), keyPath, "the element " + elem + " must be a resource or a field")
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
429
|
+
/**
|
|
430
|
+
* Schema for reference
|
|
431
|
+
*/
|
|
432
432
|
refSchema := {
|
|
433
|
-
blockId: `string`,
|
|
434
|
-
name: `string`
|
|
433
|
+
blockId: `string`,
|
|
434
|
+
name: `string`
|
|
435
435
|
}
|
|
436
436
|
|
|
437
437
|
export ll.toStrict({
|
|
438
|
-
success: success,
|
|
439
|
-
fail: fail,
|
|
440
|
-
isSuccess: isSuccess,
|
|
441
|
-
isFail: isFail,
|
|
442
|
-
checkJson: checkJson,
|
|
443
|
-
isValid: isValid,
|
|
444
|
-
assertJsonSchema: assertJsonSchema,
|
|
445
|
-
resource: resource,
|
|
446
|
-
reference: reference,
|
|
447
|
-
resourceType: resourceType,
|
|
448
|
-
ref: refSchema
|
|
438
|
+
success: success,
|
|
439
|
+
fail: fail,
|
|
440
|
+
isSuccess: isSuccess,
|
|
441
|
+
isFail: isFail,
|
|
442
|
+
checkJson: checkJson,
|
|
443
|
+
isValid: isValid,
|
|
444
|
+
assertJsonSchema: assertJsonSchema,
|
|
445
|
+
resource: resource,
|
|
446
|
+
reference: reference,
|
|
447
|
+
resourceType: resourceType,
|
|
448
|
+
ref: refSchema
|
|
449
449
|
})
|