@beseif-solutions/prow-core 1.0.0 → 1.0.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/entities/credentials.d.ts +1 -1
- package/dist/entities/credentials.js +2 -2
- package/dist/entities/events.d.ts +1 -1
- package/dist/entities/events.js +2 -2
- package/dist/entities/source.d.ts +1 -1
- package/dist/entities/source.js +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -6
- package/dist/minified-utils/context.js +20 -11
- package/dist/minified-utils/fields.d.ts +16 -46
- package/dist/minified-utils/fields.js +0 -615
- package/dist/minified-utils/validation.d.ts +31 -0
- package/dist/minified-utils/validation.js +617 -0
- package/package.json +1 -1
- package/src/entities/credentials.ts +2 -1
- package/src/entities/events.ts +2 -1
- package/src/entities/source.ts +2 -1
- package/src/index.ts +3 -2
- package/src/minified-utils/context.ts +22 -13
- package/src/minified-utils/fields.ts +17 -668
- package/src/minified-utils/validation.ts +653 -0
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import _ from "lodash";
|
|
3
|
-
import moment from "moment-timezone";
|
|
4
|
-
import context from "./context";
|
|
5
|
-
import { where, Where } from "./where";
|
|
1
|
+
import { Where } from "./where";
|
|
6
2
|
|
|
7
3
|
export type Condition = {
|
|
8
4
|
condition: Where,
|
|
9
5
|
overrides: Partial<Field>,
|
|
10
6
|
};
|
|
11
7
|
|
|
12
|
-
type
|
|
8
|
+
export type CommonField = {
|
|
13
9
|
key: string,
|
|
14
10
|
altered_by?: Condition[],
|
|
15
11
|
disabled?: boolean,
|
|
@@ -29,9 +25,9 @@ type Common = {
|
|
|
29
25
|
& Record<`experimental_${string}`, any>
|
|
30
26
|
;
|
|
31
27
|
|
|
32
|
-
export type Field<Extend = Record<never, never>> =
|
|
28
|
+
export type Field<Extend = Record<never, never>> = CommonField & InputField<Extend>;
|
|
33
29
|
|
|
34
|
-
type InputField<Extend = Record<never, never>> = (
|
|
30
|
+
export type InputField<Extend = Record<never, never>> = (
|
|
35
31
|
| AnyInputField<Extend>
|
|
36
32
|
| StringInputField<Extend>
|
|
37
33
|
| NumberInputField<Extend>
|
|
@@ -43,9 +39,9 @@ type InputField<Extend = Record<never, never>> = (
|
|
|
43
39
|
| FileInputField<Extend>
|
|
44
40
|
) & Extend;
|
|
45
41
|
|
|
46
|
-
type Any = string | number | boolean | Any[] | { [key: string]: Any };
|
|
42
|
+
export type Any = string | number | boolean | Any[] | { [key: string]: Any };
|
|
47
43
|
|
|
48
|
-
type AnyInputField<Extend = Record<never, never>> = {
|
|
44
|
+
export type AnyInputField<Extend = Record<never, never>> = {
|
|
49
45
|
type: `any`,
|
|
50
46
|
default?: Any | Any[],
|
|
51
47
|
allow_nullish?: boolean, // allow null values
|
|
@@ -54,7 +50,7 @@ type AnyInputField<Extend = Record<never, never>> = {
|
|
|
54
50
|
| {}
|
|
55
51
|
);
|
|
56
52
|
|
|
57
|
-
type StringInputField<Extend = Record<never, never>> = {
|
|
53
|
+
export type StringInputField<Extend = Record<never, never>> = {
|
|
58
54
|
type: `string`,
|
|
59
55
|
default?: string | string[],
|
|
60
56
|
length?: number,
|
|
@@ -70,7 +66,7 @@ type StringInputField<Extend = Record<never, never>> = {
|
|
|
70
66
|
| {}
|
|
71
67
|
);
|
|
72
68
|
|
|
73
|
-
type NumberInputField<Extend = Record<never, never>> = {
|
|
69
|
+
export type NumberInputField<Extend = Record<never, never>> = {
|
|
74
70
|
type: `number`,
|
|
75
71
|
default?: number | number[],
|
|
76
72
|
min?: number,
|
|
@@ -83,7 +79,7 @@ type NumberInputField<Extend = Record<never, never>> = {
|
|
|
83
79
|
| {}
|
|
84
80
|
);
|
|
85
81
|
|
|
86
|
-
type BooleanInputField<Extend = Record<never, never>> = {
|
|
82
|
+
export type BooleanInputField<Extend = Record<never, never>> = {
|
|
87
83
|
type: `boolean`,
|
|
88
84
|
default?: boolean | boolean[],
|
|
89
85
|
} & (
|
|
@@ -93,7 +89,7 @@ type BooleanInputField<Extend = Record<never, never>> = {
|
|
|
93
89
|
| {}
|
|
94
90
|
);
|
|
95
91
|
|
|
96
|
-
type DateTimeInputField<Extend = Record<never, never>> = {
|
|
92
|
+
export type DateTimeInputField<Extend = Record<never, never>> = {
|
|
97
93
|
type: `datetime`,
|
|
98
94
|
timezone?: string,
|
|
99
95
|
default?: string | number | (string | number)[],
|
|
@@ -104,7 +100,7 @@ type DateTimeInputField<Extend = Record<never, never>> = {
|
|
|
104
100
|
| {}
|
|
105
101
|
);
|
|
106
102
|
|
|
107
|
-
type DateInputField<Extend = Record<never, never>> = {
|
|
103
|
+
export type DateInputField<Extend = Record<never, never>> = {
|
|
108
104
|
type: `date`,
|
|
109
105
|
default?: string | string[],
|
|
110
106
|
min?: string,
|
|
@@ -114,7 +110,7 @@ type DateInputField<Extend = Record<never, never>> = {
|
|
|
114
110
|
| {}
|
|
115
111
|
);
|
|
116
112
|
|
|
117
|
-
type TimeInputField<Extend = Record<never, never>> = {
|
|
113
|
+
export type TimeInputField<Extend = Record<never, never>> = {
|
|
118
114
|
type: `time`,
|
|
119
115
|
timezone?: string,
|
|
120
116
|
default?: string | string[],
|
|
@@ -123,7 +119,7 @@ type TimeInputField<Extend = Record<never, never>> = {
|
|
|
123
119
|
| {}
|
|
124
120
|
);
|
|
125
121
|
|
|
126
|
-
type DictInputField<Extend = Record<never, never>> = {
|
|
122
|
+
export type DictInputField<Extend = Record<never, never>> = {
|
|
127
123
|
type: `dict`,
|
|
128
124
|
default?: Record<string, any> | Record<string, any>[],
|
|
129
125
|
items?: Field<Extend>[],
|
|
@@ -136,7 +132,7 @@ export type File = {
|
|
|
136
132
|
content: string, // base64 encoded content
|
|
137
133
|
};
|
|
138
134
|
|
|
139
|
-
type FileInputField<Extend = Record<never, never>> = {
|
|
135
|
+
export type FileInputField<Extend = Record<never, never>> = {
|
|
140
136
|
type: `file`,
|
|
141
137
|
default?: File | File[],
|
|
142
138
|
accept?: string | string[], // mime type
|
|
@@ -146,7 +142,7 @@ type FileInputField<Extend = Record<never, never>> = {
|
|
|
146
142
|
);
|
|
147
143
|
|
|
148
144
|
/* select configuration */
|
|
149
|
-
type SelectField<Values = string | number | boolean | File, Extend = Record<never, never>> = {
|
|
145
|
+
export type SelectField<Values = string | number | boolean | File, Extend = Record<never, never>> = {
|
|
150
146
|
format: `select`,
|
|
151
147
|
// el type string representa el nombre de una función del repositorio que devuelve SelectFieldChoice[]
|
|
152
148
|
choices: SelectFieldChoice<Values, Extend>[] | string,
|
|
@@ -160,658 +156,11 @@ export type SelectFieldChoice<Values = string | number | boolean | File, Extend
|
|
|
160
156
|
} & Extend;
|
|
161
157
|
|
|
162
158
|
/* custom display configuration */
|
|
163
|
-
type CustomDisplayTextField = {
|
|
159
|
+
export type CustomDisplayTextField = {
|
|
164
160
|
format: `textarea` | `rich-text` | `markdown`,
|
|
165
161
|
};
|
|
166
162
|
|
|
167
|
-
type IDETextField = {
|
|
163
|
+
export type IDETextField = {
|
|
168
164
|
format: `code`,
|
|
169
165
|
language?: string, // language for code highlighting
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
type ValidationExtended = {
|
|
173
|
-
original: any,
|
|
174
|
-
value: any,
|
|
175
|
-
} & ({
|
|
176
|
-
valid: true,
|
|
177
|
-
} | {
|
|
178
|
-
valid: false,
|
|
179
|
-
errors: {
|
|
180
|
-
path: string,
|
|
181
|
-
message: string,
|
|
182
|
-
type: string,
|
|
183
|
-
}[],
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
const conditionSchema = () =>
|
|
187
|
-
Joi.object({
|
|
188
|
-
condition: Joi.object().required(),
|
|
189
|
-
overrides: Joi.object().required(),
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const commonFieldSchema = () =>
|
|
193
|
-
Joi.object({
|
|
194
|
-
key: Joi.string().required(),
|
|
195
|
-
altered_by: Joi.array().items(conditionSchema()),
|
|
196
|
-
disabled: Joi.bool(),
|
|
197
|
-
required: Joi.bool(),
|
|
198
|
-
as_password: Joi.bool(),
|
|
199
|
-
show: Joi.bool(),
|
|
200
|
-
t: Joi.string(),
|
|
201
|
-
as_array: Joi.bool(),
|
|
202
|
-
array_min: Joi.when(`as_array`, {
|
|
203
|
-
is: true,
|
|
204
|
-
then: Joi.number(),
|
|
205
|
-
otherwise: Joi.forbidden(),
|
|
206
|
-
}),
|
|
207
|
-
array_max: Joi.when(`as_array`, {
|
|
208
|
-
is: true,
|
|
209
|
-
then: Joi.number(),
|
|
210
|
-
otherwise: Joi.forbidden(),
|
|
211
|
-
}),
|
|
212
|
-
array_length: Joi.when(`as_array`, {
|
|
213
|
-
is: true,
|
|
214
|
-
then: Joi.number(),
|
|
215
|
-
otherwise: Joi.forbidden(),
|
|
216
|
-
}),
|
|
217
|
-
}).concat(Joi.object() // experimental properties for custom front features
|
|
218
|
-
.pattern(Joi.string().regex(/^experimental_/), Joi.any()));
|
|
219
|
-
|
|
220
|
-
const types = [`any`, `string`, `number`, `boolean`, `date`, `datetime`, `time`, `dict`, `file`];
|
|
221
|
-
|
|
222
|
-
const inputFieldBaseSchema = () =>
|
|
223
|
-
Joi.object({
|
|
224
|
-
type: Joi.string().required()
|
|
225
|
-
.valid(...types),
|
|
226
|
-
// switch by type properties
|
|
227
|
-
})
|
|
228
|
-
.concat(commonFieldSchema())
|
|
229
|
-
.unknown();
|
|
230
|
-
|
|
231
|
-
const choiceSchema = (type: Joi.Schema) =>
|
|
232
|
-
Joi.object({
|
|
233
|
-
key: Joi.string().required(),
|
|
234
|
-
value: type.required(),
|
|
235
|
-
group: Joi.string(),
|
|
236
|
-
}).unknown();
|
|
237
|
-
|
|
238
|
-
const choicesSchema = (type: Joi.Schema) =>
|
|
239
|
-
Joi.alternatives(
|
|
240
|
-
Joi.string(),
|
|
241
|
-
Joi.array().min(1)
|
|
242
|
-
.items(choiceSchema(type))
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
const defaultSchema = (type: Joi.Schema) =>
|
|
246
|
-
Joi.when(`as_array`, {
|
|
247
|
-
is: true,
|
|
248
|
-
then: Joi.array().items(type),
|
|
249
|
-
otherwise: type,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
const anySchema = (nullish = true) => {
|
|
253
|
-
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
|
254
|
-
const randId = Math.random().toString(36).substring(2, 15);
|
|
255
|
-
return Joi.alternatives(
|
|
256
|
-
Joi.string(),
|
|
257
|
-
Joi.bool(),
|
|
258
|
-
Joi.number(),
|
|
259
|
-
Joi.array().items(Joi.link(`#${randId}`)),
|
|
260
|
-
Joi.object().pattern(Joi.string(), Joi.link(`#${randId}`)),
|
|
261
|
-
...nullish ? [Joi.valid(null, ``)] : [],
|
|
262
|
-
)
|
|
263
|
-
.id(randId);
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
const anyInputFieldSchema = () =>
|
|
267
|
-
Joi.object({
|
|
268
|
-
default: defaultSchema(anySchema()),
|
|
269
|
-
allow_nullish: Joi.bool().default(true),
|
|
270
|
-
})
|
|
271
|
-
.concat(Joi.object({
|
|
272
|
-
format: Joi.string().valid(`select`),
|
|
273
|
-
choices: Joi.when(`format`, {
|
|
274
|
-
is: `select`,
|
|
275
|
-
then: choicesSchema(anySchema()).required(),
|
|
276
|
-
otherwise: Joi.forbidden(),
|
|
277
|
-
}),
|
|
278
|
-
}));
|
|
279
|
-
|
|
280
|
-
const stringInputFieldSchema = () =>
|
|
281
|
-
Joi.object({
|
|
282
|
-
default: defaultSchema(Joi.string()),
|
|
283
|
-
length: Joi.number(),
|
|
284
|
-
min: Joi.number(),
|
|
285
|
-
max: Joi.number(),
|
|
286
|
-
regexp: Joi.string(),
|
|
287
|
-
pattern: Joi.string().valid(`email`, `uri`),
|
|
288
|
-
allow_empty: Joi.bool().default(false),
|
|
289
|
-
})
|
|
290
|
-
.concat(
|
|
291
|
-
Joi.object({
|
|
292
|
-
format: Joi.string().valid(`select`, `textarea`, `rich-text`, `markdown`, `code`),
|
|
293
|
-
choices: Joi.when(`format`, {
|
|
294
|
-
is: `select`,
|
|
295
|
-
then: choicesSchema(Joi.string()).required(),
|
|
296
|
-
otherwise: Joi.forbidden(),
|
|
297
|
-
}),
|
|
298
|
-
language: Joi.when(`format`, {
|
|
299
|
-
is: `code`,
|
|
300
|
-
then: Joi.string(),
|
|
301
|
-
otherwise: Joi.forbidden(),
|
|
302
|
-
}),
|
|
303
|
-
})
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
const numberInputFieldSchema = () =>
|
|
307
|
-
Joi.object({
|
|
308
|
-
default: defaultSchema(Joi.number()),
|
|
309
|
-
min: Joi.number(),
|
|
310
|
-
max: Joi.number(),
|
|
311
|
-
float: Joi.bool(),
|
|
312
|
-
decimals: Joi.when(`float`, {
|
|
313
|
-
is: true,
|
|
314
|
-
then: Joi.number(),
|
|
315
|
-
otherwise: Joi.forbidden(),
|
|
316
|
-
}),
|
|
317
|
-
})
|
|
318
|
-
.concat(Joi.object({
|
|
319
|
-
format: Joi.string().valid(`select`),
|
|
320
|
-
choices: Joi.when(`format`, {
|
|
321
|
-
is: `select`,
|
|
322
|
-
then: choicesSchema(Joi.number()).required(),
|
|
323
|
-
otherwise: Joi.forbidden(),
|
|
324
|
-
}),
|
|
325
|
-
}));
|
|
326
|
-
|
|
327
|
-
const booleanInputFieldSchema = () =>
|
|
328
|
-
Joi.object({
|
|
329
|
-
default: defaultSchema(Joi.bool()),
|
|
330
|
-
})
|
|
331
|
-
.concat(Joi.object({
|
|
332
|
-
format: Joi.string().valid(`select`, `checkbox`, `switch`),
|
|
333
|
-
choices: Joi.when(`format`, {
|
|
334
|
-
is: `select`,
|
|
335
|
-
then: choicesSchema(Joi.bool()).required(),
|
|
336
|
-
otherwise: Joi.forbidden(),
|
|
337
|
-
}),
|
|
338
|
-
}));
|
|
339
|
-
|
|
340
|
-
const dateInputFieldSchema = () =>
|
|
341
|
-
Joi.object({
|
|
342
|
-
default: defaultSchema(Joi.alternatives(Joi.string(), Joi.number())),
|
|
343
|
-
min: Joi.alternatives(Joi.string(), Joi.number()),
|
|
344
|
-
max: Joi.alternatives(Joi.string(), Joi.number()),
|
|
345
|
-
})
|
|
346
|
-
.concat(Joi.object({
|
|
347
|
-
format: Joi.string().valid(`select`),
|
|
348
|
-
choices: Joi.when(`format`, {
|
|
349
|
-
is: `select`,
|
|
350
|
-
then: choicesSchema(Joi.alternatives(Joi.string(), Joi.number())).required(),
|
|
351
|
-
otherwise: Joi.forbidden(),
|
|
352
|
-
}),
|
|
353
|
-
}));
|
|
354
|
-
|
|
355
|
-
const timeInputFieldSchema = () =>
|
|
356
|
-
Joi.object({
|
|
357
|
-
default: defaultSchema(Joi.alternatives(Joi.string(), Joi.number())),
|
|
358
|
-
})
|
|
359
|
-
.concat(Joi.object({
|
|
360
|
-
format: Joi.string().valid(`select`),
|
|
361
|
-
choices: Joi.when(`format`, {
|
|
362
|
-
is: `select`,
|
|
363
|
-
then: choicesSchema(Joi.alternatives(Joi.string(), Joi.number())).required(),
|
|
364
|
-
otherwise: Joi.forbidden(),
|
|
365
|
-
}),
|
|
366
|
-
}));
|
|
367
|
-
|
|
368
|
-
const dictInputFieldSchema = () =>
|
|
369
|
-
Joi.object({
|
|
370
|
-
default: defaultSchema(Joi.object()),
|
|
371
|
-
items: Joi.array().items(Joi.link(`#field`)).min(1),
|
|
372
|
-
unknown: Joi.when(`items`, {
|
|
373
|
-
is: Joi.exist(),
|
|
374
|
-
then: Joi.bool(),
|
|
375
|
-
otherwise: Joi.forbidden(),
|
|
376
|
-
}),
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
const fileSchema = () => Joi.object({
|
|
380
|
-
name: Joi.string().required(),
|
|
381
|
-
type: Joi.string().required(), // mime type
|
|
382
|
-
content: Joi.string().required(), // base64 encoded content
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
// ÑAPA: translate file to structured dict
|
|
386
|
-
const fileReplacer = (field: Common & FileInputField): Field => ({
|
|
387
|
-
key: field.key,
|
|
388
|
-
type: `dict`,
|
|
389
|
-
unknown: true,
|
|
390
|
-
items: [
|
|
391
|
-
{
|
|
392
|
-
key: `name`,
|
|
393
|
-
type: `string`,
|
|
394
|
-
required: true,
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
key: `type`,
|
|
398
|
-
type: `string`,
|
|
399
|
-
required: true,
|
|
400
|
-
},
|
|
401
|
-
{
|
|
402
|
-
key: `content`,
|
|
403
|
-
type: `string`,
|
|
404
|
-
required: true,
|
|
405
|
-
},
|
|
406
|
-
],
|
|
407
|
-
default: field.default,
|
|
408
|
-
..._.pick(field, [`altered_by`, `disabled`, `required`, `as_password`, `show`, `as_array`, `array_min`, `array_max`, `array_length`]),
|
|
409
|
-
...Object.assign({}, ...Object.entries(field).map(([k, v]) => k.startsWith(`experimental_`) ? { [k]: v } : {})),
|
|
410
|
-
}) as Field;
|
|
411
|
-
|
|
412
|
-
const fileInputFieldSchema = () =>
|
|
413
|
-
Joi.object({
|
|
414
|
-
default: defaultSchema(fileSchema()),
|
|
415
|
-
accept: Joi.alternatives(
|
|
416
|
-
Joi.string(),
|
|
417
|
-
Joi.array().items(Joi.string())
|
|
418
|
-
).optional(),
|
|
419
|
-
})
|
|
420
|
-
.concat(
|
|
421
|
-
Joi.object({
|
|
422
|
-
format: Joi.string().valid(`select`),
|
|
423
|
-
choices: Joi.when(`format`, {
|
|
424
|
-
is: `select`,
|
|
425
|
-
then: choicesSchema(fileSchema()).required(),
|
|
426
|
-
otherwise: Joi.forbidden(),
|
|
427
|
-
}),
|
|
428
|
-
})
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
export const fieldSchema = () =>
|
|
432
|
-
inputFieldBaseSchema()
|
|
433
|
-
.id(`field`)
|
|
434
|
-
.when(Joi.object({ type: Joi.valid(`any`) }).unknown(), {
|
|
435
|
-
then: inputFieldBaseSchema()
|
|
436
|
-
.concat(anyInputFieldSchema()),
|
|
437
|
-
})
|
|
438
|
-
.when(Joi.object({ type: Joi.valid(`string`) }).unknown(), {
|
|
439
|
-
then: inputFieldBaseSchema()
|
|
440
|
-
.concat(stringInputFieldSchema()),
|
|
441
|
-
})
|
|
442
|
-
.when(Joi.object({ type: Joi.valid(`number`) }).unknown(), {
|
|
443
|
-
then: inputFieldBaseSchema()
|
|
444
|
-
.concat(numberInputFieldSchema()),
|
|
445
|
-
})
|
|
446
|
-
.when(Joi.object({ type: Joi.valid(`boolean`) }).unknown(), {
|
|
447
|
-
then: inputFieldBaseSchema()
|
|
448
|
-
.concat(booleanInputFieldSchema()),
|
|
449
|
-
})
|
|
450
|
-
.when(Joi.object({ type: Joi.valid(`date`, `datetime`) }).unknown(), {
|
|
451
|
-
then: inputFieldBaseSchema()
|
|
452
|
-
.concat(dateInputFieldSchema()),
|
|
453
|
-
})
|
|
454
|
-
.when(Joi.object({ type: Joi.valid(`time`) }).unknown(), {
|
|
455
|
-
then: inputFieldBaseSchema()
|
|
456
|
-
.concat(timeInputFieldSchema()),
|
|
457
|
-
})
|
|
458
|
-
.when(Joi.object({ type: Joi.valid(`dict`) }).unknown(), {
|
|
459
|
-
then: inputFieldBaseSchema()
|
|
460
|
-
.concat(dictInputFieldSchema()),
|
|
461
|
-
})
|
|
462
|
-
.when(Joi.object({ type: Joi.valid(`file`) }).unknown(), {
|
|
463
|
-
then: inputFieldBaseSchema()
|
|
464
|
-
.concat(fileInputFieldSchema()),
|
|
465
|
-
})
|
|
466
|
-
// invalid fallback
|
|
467
|
-
.when(Joi.object({ type: Joi.invalid(...types) }).unknown(), {
|
|
468
|
-
then: Joi.forbidden(),
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
export const fieldsSchema = () =>
|
|
472
|
-
Joi.array().items(fieldSchema());
|
|
473
|
-
|
|
474
|
-
const getSimpleFieldSchema = (field: Field) => {
|
|
475
|
-
if (field.as_array) { throw new Error(`Cannot use as_array with simple field schema generator`); }
|
|
476
|
-
|
|
477
|
-
let typeSchema: Joi.Schema;
|
|
478
|
-
switch (field.type) {
|
|
479
|
-
case `any`:
|
|
480
|
-
typeSchema = anySchema(field.allow_nullish);
|
|
481
|
-
break;
|
|
482
|
-
case `boolean`:
|
|
483
|
-
typeSchema = Joi.bool();
|
|
484
|
-
break;
|
|
485
|
-
case `string`:
|
|
486
|
-
typeSchema = Joi.string();
|
|
487
|
-
if (field.length) { typeSchema = (typeSchema as StringSchema).length(field.length); }
|
|
488
|
-
if (field.min > 0) {
|
|
489
|
-
typeSchema = (typeSchema as StringSchema).min(field.min);
|
|
490
|
-
} else if (field.allow_empty) {
|
|
491
|
-
typeSchema = typeSchema.allow(``);
|
|
492
|
-
}
|
|
493
|
-
if (field.max) { typeSchema = (typeSchema as StringSchema).max(field.max); }
|
|
494
|
-
if (field.regexp) { typeSchema = (typeSchema as StringSchema).regex(new RegExp(field.regexp)); }
|
|
495
|
-
if (field.pattern === `email`) { typeSchema = (typeSchema as StringSchema).email(); }
|
|
496
|
-
if (field.pattern === `uri`) { typeSchema = (typeSchema as StringSchema).uri(); }
|
|
497
|
-
|
|
498
|
-
break;
|
|
499
|
-
case `date`:
|
|
500
|
-
case `datetime`:
|
|
501
|
-
case `time`:
|
|
502
|
-
if (field.type === `time`) {
|
|
503
|
-
typeSchema = Joi.string();
|
|
504
|
-
} else {
|
|
505
|
-
typeSchema = Joi.date().strict(false);
|
|
506
|
-
if (field.min) { typeSchema = (typeSchema as DateSchema).min(field.min); }
|
|
507
|
-
if (field.max) { typeSchema = (typeSchema as DateSchema).max(field.max); }
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
typeSchema = typeSchema.external(async (value, helpers) => {
|
|
511
|
-
if (!helpers.original && !field.required) { return helpers.original; }
|
|
512
|
-
|
|
513
|
-
const format = field.type === `date` ? `YYYY-MM-DD` :
|
|
514
|
-
field.type === `time` ? `HH:mmZ` :
|
|
515
|
-
`YYYY-MM-DDTHH:mm:ssZ`;
|
|
516
|
-
const d = (field.type === `datetime` && typeof helpers.original === `number`) ?
|
|
517
|
-
moment.unix(helpers.original).tz(`UTC`)
|
|
518
|
-
: moment.tz(helpers.original, format, `UTC`);
|
|
519
|
-
if (!d.isValid()) { return helpers.message({ external: `invalid format, expected ${format}` }); }
|
|
520
|
-
|
|
521
|
-
const timezone = (field.type === `date` ? `UTC` : field.timezone) || `UTC`;
|
|
522
|
-
return d.tz(timezone).format(format);
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
break;
|
|
526
|
-
case `number`:
|
|
527
|
-
typeSchema = Joi.number();
|
|
528
|
-
if (field.min) { typeSchema = (typeSchema as NumberSchema).min(field.min); }
|
|
529
|
-
if (field.max) { typeSchema = (typeSchema as NumberSchema).max(field.max); }
|
|
530
|
-
// don't check precision if float is not set
|
|
531
|
-
if (typeof field.float === `boolean`) {
|
|
532
|
-
if (!field.float) {
|
|
533
|
-
typeSchema = (typeSchema as NumberSchema).precision(0);
|
|
534
|
-
} else if (field.decimals) {
|
|
535
|
-
typeSchema = (typeSchema as NumberSchema).precision(field.decimals);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
break;
|
|
540
|
-
case `dict`:
|
|
541
|
-
if (field.items) { throw new Error(`No simple field`); }
|
|
542
|
-
typeSchema = Joi.object().unknown(true);
|
|
543
|
-
break;
|
|
544
|
-
case `file`:
|
|
545
|
-
typeSchema = fileSchema();
|
|
546
|
-
break;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
if (`format` in field && field.format === `select`) {
|
|
550
|
-
if (!field.unknown) {
|
|
551
|
-
if (_.isArray(field.choices)) {
|
|
552
|
-
typeSchema = typeSchema.valid(...field.choices.map((c: SelectFieldChoice) => c.value));
|
|
553
|
-
} else {
|
|
554
|
-
// async select values are not checked
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
return getGeneralSchema(field, typeSchema);
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
const getGeneralArraySchema = (field: Field, schema: Joi.ArraySchema) => {
|
|
563
|
-
if (!field.as_array) { throw new Error(`as_array required for array schema generator`); }
|
|
564
|
-
|
|
565
|
-
if (field.array_length) { schema = schema.length(field.array_length); }
|
|
566
|
-
if (field.array_min) { schema = schema.min(field.array_min); }
|
|
567
|
-
if (field.array_max) { schema = schema.max(field.array_max); }
|
|
568
|
-
|
|
569
|
-
return getGeneralSchema(field, schema);
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
const getItemsArrayFieldSchema = (field: Field, alternatives: Joi.Schema[]) => {
|
|
573
|
-
if (!field.as_array) { throw new Error(`as_array required for array schema generator`); }
|
|
574
|
-
|
|
575
|
-
const schema = Joi.array().items(...alternatives);
|
|
576
|
-
return getGeneralArraySchema(field, schema);
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
const getOrderedArrayFieldSchema = (field: Field, items: Joi.Schema[]) => {
|
|
580
|
-
if (!field.as_array) { throw new Error(`as_array required for array schema generator`); }
|
|
581
|
-
|
|
582
|
-
const schema = Joi.array().ordered(...items);
|
|
583
|
-
return getGeneralArraySchema(field, schema);
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
const getObjectFieldSchema = (field: Field, items: { key: string, schema: Joi.Schema }[]) => {
|
|
587
|
-
if (field.type !== `dict`) { throw new Error(`type must be dict for object schema generator`); }
|
|
588
|
-
if (!field.items) { throw new Error(`items required for object schema generator`); }
|
|
589
|
-
|
|
590
|
-
let schema = Joi.object();
|
|
591
|
-
for (const item of items) {
|
|
592
|
-
schema = schema.concat(Joi.object({
|
|
593
|
-
[item.key]: item.schema,
|
|
594
|
-
}));
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
if (field.unknown) { schema = schema.unknown(field.unknown); }
|
|
598
|
-
|
|
599
|
-
return getGeneralSchema(field, schema);
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
const getGeneralSchema = (field: Field, schema: Joi.Schema): Joi.Schema => {
|
|
603
|
-
if (field.required) { schema = schema.required(); }
|
|
604
|
-
if (field.disabled) { schema = schema.forbidden(); }
|
|
605
|
-
return schema;
|
|
606
|
-
};
|
|
607
|
-
|
|
608
|
-
const convertPath = (parts: (string | number)[]): string =>
|
|
609
|
-
parts.map((p, i) => typeof p === `string` ? (i === 0 ? p : `.${p}`) : `[${p}]`).join(``);
|
|
610
|
-
|
|
611
|
-
const secure = async (field: Field, validation: ValidationExtended): Promise<ValidationExtended> =>
|
|
612
|
-
field.as_password ? _.omit(validation, [`value`, `original`]) as any : validation;
|
|
613
|
-
|
|
614
|
-
export type ValidationResult<Extended = Record<string, never>> = {
|
|
615
|
-
valid: boolean,
|
|
616
|
-
fields: Field<Extended & ValidationExtended>[],
|
|
617
|
-
};
|
|
618
|
-
|
|
619
|
-
const relative_condition = (condition: Where, relative: string): typeof condition => {
|
|
620
|
-
try {
|
|
621
|
-
const newCondition = _.cloneDeep(condition);
|
|
622
|
-
for (const key of Object.keys(newCondition)) {
|
|
623
|
-
if (key.startsWith(`@relative`)) {
|
|
624
|
-
const newKey = key.replace(`@relative`, relative ? relative : ``);
|
|
625
|
-
newCondition[newKey] = newCondition[key];
|
|
626
|
-
delete newCondition[key];
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
return newCondition;
|
|
631
|
-
} catch (e) { throw e; }
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
export const alter = (field: Field, relative: string, data: Record<string, any>) => {
|
|
635
|
-
const cloned = _.cloneDeep(field);
|
|
636
|
-
for (const alteration of cloned.altered_by) {
|
|
637
|
-
const condition = relative_condition(alteration.condition, relative);
|
|
638
|
-
|
|
639
|
-
const matches = where(data, [condition]);
|
|
640
|
-
if (matches) { _.assign(cloned, alteration.overrides); }
|
|
641
|
-
}
|
|
642
|
-
return cloned;
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
const recursive_schema = async (field: Field, data: Record<string, any>, options: { alter: boolean, relative?: string, array?: boolean, context: Record<string, any> }): Promise<{ schema: Joi.Schema, field: Field }> => {
|
|
646
|
-
try {
|
|
647
|
-
let newField: Field = _.cloneDeep(field);
|
|
648
|
-
|
|
649
|
-
// alterations over already resolved data (previous fields)
|
|
650
|
-
if (newField.altered_by?.length > 0) {
|
|
651
|
-
newField = alter(newField, options.relative, data);
|
|
652
|
-
|
|
653
|
-
// remove altered_by only if it's not an array -> array alterations must be kept for front-end rendering
|
|
654
|
-
if (options.alter) { delete newField.altered_by; }
|
|
655
|
-
|
|
656
|
-
if (typeof newField.show === `boolean` && !newField.show) { return; }
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
const alteredField: Field = _.cloneDeep(newField);
|
|
660
|
-
|
|
661
|
-
// ÑAPA: if the field is a file, replace it
|
|
662
|
-
if (newField.type === `file`) { newField = fileReplacer(newField); }
|
|
663
|
-
|
|
664
|
-
const relativeKey = options.relative ?
|
|
665
|
-
options.relative.endsWith(`]`) ?
|
|
666
|
-
options.array ? options.relative : `${options.relative}.${newField.key}`
|
|
667
|
-
: `${options.relative}.${newField.key}`
|
|
668
|
-
: newField.key;
|
|
669
|
-
|
|
670
|
-
const value = _.get(data, relativeKey, newField.default);
|
|
671
|
-
|
|
672
|
-
// resolve value with context (even if it's not valid - omit undefined)
|
|
673
|
-
const resolved = context.resolve(newField, value, options.context);
|
|
674
|
-
if (typeof resolved !== `undefined`) { _.set(data, relativeKey, resolved); }
|
|
675
|
-
|
|
676
|
-
// get schema for field with data
|
|
677
|
-
let calculatedSchema: Joi.Schema;
|
|
678
|
-
if (newField.as_array) {
|
|
679
|
-
const noArrayField = _.omit(newField, [`as_array`, `array_min`, `array_max`, `array_length`]) as Field;
|
|
680
|
-
|
|
681
|
-
if (resolved) {
|
|
682
|
-
const itemSchemas: Joi.Schema[] = [];
|
|
683
|
-
if (_.isArray(resolved)) {
|
|
684
|
-
for (let i = 0; i < (resolved || []).length; i++) {
|
|
685
|
-
const { schema: itemSchema } = await recursive_schema(noArrayField, data, { alter: false, relative: `${relativeKey}[${i}]`, array: true, context: options.context });
|
|
686
|
-
if (itemSchema) { itemSchemas.push(itemSchema); }
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
calculatedSchema = getOrderedArrayFieldSchema(newField, itemSchemas);
|
|
691
|
-
} else {
|
|
692
|
-
let genericItemSchema: Joi.Schema;
|
|
693
|
-
if (noArrayField.type === `dict` && noArrayField.items) {
|
|
694
|
-
const items: { key: string, schema: Joi.Schema, field: Field }[] = [];
|
|
695
|
-
|
|
696
|
-
for (const item of noArrayField.items) {
|
|
697
|
-
const recursive = await recursive_schema(item, data, { alter: options.alter && true, relative: `${relativeKey}[0]`, context: options.context });
|
|
698
|
-
if (recursive) { items.push({ key: item.key, schema: recursive.schema, field: recursive.field }); }
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
genericItemSchema = getObjectFieldSchema(noArrayField, items);
|
|
702
|
-
noArrayField.items = items.map((i) => i.field);
|
|
703
|
-
} else {
|
|
704
|
-
genericItemSchema = getSimpleFieldSchema(noArrayField);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
calculatedSchema = getItemsArrayFieldSchema(newField, [genericItemSchema]);
|
|
708
|
-
}
|
|
709
|
-
} else if (newField.type === `dict` && newField.items) {
|
|
710
|
-
const items: { key: string, schema: Joi.Schema, field: Field }[] = [];
|
|
711
|
-
|
|
712
|
-
for (const item of newField.items) {
|
|
713
|
-
const recursive = await recursive_schema(item, data, { alter: options.alter && true, relative: relativeKey, context: options.context });
|
|
714
|
-
if (recursive) { items.push({ key: item.key, schema: recursive.schema, field: recursive.field }); }
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
calculatedSchema = getObjectFieldSchema(newField, items);
|
|
718
|
-
newField.items = items.map((i) => i.field);
|
|
719
|
-
} else {
|
|
720
|
-
calculatedSchema = getSimpleFieldSchema(newField);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
if (!calculatedSchema) { throw new Error(`Could not resolve schema for field ${newField.key}`); }
|
|
724
|
-
|
|
725
|
-
// if disabled schema validation is done but the value is not set
|
|
726
|
-
if (newField.disabled) { _.unset(data, relativeKey); }
|
|
727
|
-
|
|
728
|
-
return { schema: calculatedSchema, field: field.type === `file` ? alteredField : newField };
|
|
729
|
-
} catch (e) { throw e; }
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
const validate_internal = async (fields: Field[], data: Record<string, any>, options: { secure: boolean, break: boolean, context: Record<string, any> }): Promise<ValidationResult> => {
|
|
733
|
-
try {
|
|
734
|
-
const response: ValidationResult<{}> = { valid: true, fields: [] };
|
|
735
|
-
|
|
736
|
-
for (const field of fields) {
|
|
737
|
-
|
|
738
|
-
const value = _.get(data, field.key, field.default);
|
|
739
|
-
|
|
740
|
-
const cloned = _.cloneDeep(data);
|
|
741
|
-
|
|
742
|
-
const recursive = await recursive_schema(field, cloned, { alter: true, context: options.context });
|
|
743
|
-
if (!recursive) { continue; }
|
|
744
|
-
|
|
745
|
-
const resolved = _.get(cloned, field.key, field.default);
|
|
746
|
-
|
|
747
|
-
let validation: any;
|
|
748
|
-
let error: any;
|
|
749
|
-
try {
|
|
750
|
-
validation = await recursive.schema.validateAsync(resolved, { abortEarly: false });
|
|
751
|
-
} catch (e) { error = e; }
|
|
752
|
-
|
|
753
|
-
let extended: ValidationExtended;
|
|
754
|
-
if (error) {
|
|
755
|
-
extended = {
|
|
756
|
-
valid: false,
|
|
757
|
-
errors: error.details ? error.details.map((d) => ({
|
|
758
|
-
type: d.type,
|
|
759
|
-
path: convertPath([field.key, ...d.path]),
|
|
760
|
-
message: d.message,
|
|
761
|
-
})) : [
|
|
762
|
-
{
|
|
763
|
-
type: `unknown`,
|
|
764
|
-
path: field.key,
|
|
765
|
-
message: error.message,
|
|
766
|
-
},
|
|
767
|
-
],
|
|
768
|
-
original: value,
|
|
769
|
-
value: null,
|
|
770
|
-
};
|
|
771
|
-
} else {
|
|
772
|
-
_.set(data, field.key, validation);
|
|
773
|
-
extended = {
|
|
774
|
-
valid: true,
|
|
775
|
-
original: value,
|
|
776
|
-
value: validation,
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
response.valid &&= error ? false : true;
|
|
781
|
-
response.fields.push({
|
|
782
|
-
...recursive.field as any,
|
|
783
|
-
...options.secure ? await secure(field, extended) : extended,
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
return response;
|
|
789
|
-
} catch (e) { throw e; }
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
export const validate = async <I>(fields: Field<I>[], data: Record<string, any>, options: { secure?: boolean, break?: boolean, context?: Record<string, any> } = {}): Promise<ValidationResult<I>> => {
|
|
793
|
-
try {
|
|
794
|
-
const { error } = fieldsSchema().validate(fields);
|
|
795
|
-
if (error) { throw new Error(`Invalid fields definition: ${error.message}`); }
|
|
796
|
-
const defaultOptions = _.assign({ secure: true, break: false, context: {} }, options);
|
|
797
|
-
const response = await validate_internal(fields, data, defaultOptions);
|
|
798
|
-
return response as any;
|
|
799
|
-
} catch (e) { throw e; }
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
const extract_recursive = async (validation: Field<ValidationExtended>[], options: { original?: boolean }, path = ``): Promise<Record<string, any>> => {
|
|
803
|
-
try {
|
|
804
|
-
const response = {};
|
|
805
|
-
for (const field of validation) {
|
|
806
|
-
const key = _.compact([path, field.key]).join(`.`);
|
|
807
|
-
_.set(response, key, options.original ? field.original : field.value);
|
|
808
|
-
}
|
|
809
|
-
return response;
|
|
810
|
-
} catch (e) { throw e; }
|
|
811
|
-
};
|
|
812
|
-
|
|
813
|
-
export const extract = async (validation: Field<ValidationExtended>[], options: { original?: boolean } = {}): Promise<Record<string, any>> => {
|
|
814
|
-
try {
|
|
815
|
-
return await extract_recursive(validation, options);
|
|
816
|
-
} catch (e) { throw e; }
|
|
817
166
|
};
|