5htp-core 0.6.2-7 → 0.6.2-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/client/components/Input.tsx +0 -2
- package/package.json +1 -1
- package/server/app/service/index.ts +32 -5
- package/server/services/disks/index.ts +4 -4
- package/server/services/prisma/Facet.ts +34 -14
- package/server/services/prisma/index.ts +5 -7
- package/server/services/router/request/validation/zod.ts +44 -9
- package/server/services/router/response/index.ts +1 -1
- package/server/services/schema/request.ts +18 -33
- package/server/services/schema/router/index.ts +3 -3
- package/types/icons.d.ts +1 -1
- package/common/data/input/validate.ts +0 -54
- package/server/services/router/request/validation/index.ts +0 -23
- package/server/services/router/request/validation/schema.ts +0 -211
- package/server/services/router/request/validation/validator.ts +0 -117
- package/server/services/router/request/validation/validators.ts +0 -485
|
@@ -13,8 +13,6 @@ import {
|
|
|
13
13
|
|
|
14
14
|
// Core libs
|
|
15
15
|
import { InputBaseProps, useMantineInput } from './utils';
|
|
16
|
-
import { default as Validator } from '../../server/services/router/request/validation/validator';
|
|
17
|
-
import type { SchemaValidators } from '@server/services/router/request/validation/validators';
|
|
18
16
|
|
|
19
17
|
/*----------------------------------
|
|
20
18
|
- TYPES
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
-
"version": "0.6.2-
|
|
4
|
+
"version": "0.6.2-8",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
|
+
// Npm
|
|
6
|
+
import zod from 'zod';
|
|
7
|
+
|
|
5
8
|
// Specific
|
|
6
9
|
import type { Application } from "..";
|
|
7
10
|
import type { Command } from "../commands";
|
|
@@ -10,6 +13,7 @@ import type { TControllerDefinition, TRoute } from '../../services/router';
|
|
|
10
13
|
import { Anomaly } from "@common/errors";
|
|
11
14
|
|
|
12
15
|
export { schema } from '../../services/router/request/validation/zod';
|
|
16
|
+
export type { z } from '../../services/router/request/validation/zod';
|
|
13
17
|
|
|
14
18
|
/*----------------------------------
|
|
15
19
|
- TYPES: OPTIONS
|
|
@@ -52,7 +56,30 @@ export type TServiceArgs<TService extends AnyService> = [
|
|
|
52
56
|
|
|
53
57
|
const LogPrefix = '[service]';
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
type TDecoratorArgs = (
|
|
60
|
+
[path: string] |
|
|
61
|
+
[path: string, schema: zod.ZodSchema] |
|
|
62
|
+
[path: string, schema: zod.ZodSchema, options?: Omit<TControllerDefinition, 'controller'|'schema'|'path'>] |
|
|
63
|
+
[options: Omit<TControllerDefinition, 'controller'>]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
export function Route( ...args: TDecoratorArgs ) {
|
|
67
|
+
|
|
68
|
+
let path: string | undefined;
|
|
69
|
+
let schema: zod.ZodSchema | undefined;
|
|
70
|
+
let options: Omit<TControllerDefinition, 'controller'|'schema'|'path'> = {};
|
|
71
|
+
|
|
72
|
+
if (typeof args[0] === 'object') {
|
|
73
|
+
const { path: path_, schema: schema_, ...options_ } = args[0];
|
|
74
|
+
path = path_;
|
|
75
|
+
schema = schema_;
|
|
76
|
+
options = options_;
|
|
77
|
+
} else {
|
|
78
|
+
path = args[0];
|
|
79
|
+
schema = args[1];
|
|
80
|
+
options = args[2] || {};
|
|
81
|
+
}
|
|
82
|
+
|
|
56
83
|
return function (
|
|
57
84
|
target: any,
|
|
58
85
|
propertyKey: string,
|
|
@@ -61,8 +88,8 @@ export function Route(options: Omit<TControllerDefinition, 'controller'> = {}) {
|
|
|
61
88
|
// Store the original method
|
|
62
89
|
const originalMethod = descriptor.value;
|
|
63
90
|
|
|
64
|
-
if (
|
|
65
|
-
|
|
91
|
+
if (path === undefined)
|
|
92
|
+
path = target.constructor.name + '/' + propertyKey;
|
|
66
93
|
|
|
67
94
|
// Ensure the class has a static property to collect routes
|
|
68
95
|
if (!target.__routes) {
|
|
@@ -72,9 +99,9 @@ export function Route(options: Omit<TControllerDefinition, 'controller'> = {}) {
|
|
|
72
99
|
// Create route object
|
|
73
100
|
const route: TRoute = {
|
|
74
101
|
method: 'POST',
|
|
75
|
-
path: '/api/' +
|
|
102
|
+
path: '/api/' + path,
|
|
76
103
|
controller: originalMethod,
|
|
77
|
-
schema:
|
|
104
|
+
schema: schema,
|
|
78
105
|
options: {
|
|
79
106
|
priority: options.priority || 0
|
|
80
107
|
}
|
|
@@ -34,9 +34,9 @@ export type Services = {
|
|
|
34
34
|
- SERVICE
|
|
35
35
|
----------------------------------*/
|
|
36
36
|
export default class DisksManager<
|
|
37
|
-
MountpointList extends Services
|
|
38
|
-
TConfig extends Config
|
|
39
|
-
TApplication extends Application
|
|
37
|
+
MountpointList extends Services,
|
|
38
|
+
TConfig extends Config,
|
|
39
|
+
TApplication extends Application
|
|
40
40
|
> extends Service<TConfig, Hooks, TApplication> {
|
|
41
41
|
|
|
42
42
|
public default!: Driver;
|
|
@@ -45,7 +45,7 @@ export default class DisksManager<
|
|
|
45
45
|
- LIFECYCLE
|
|
46
46
|
----------------------------------*/
|
|
47
47
|
|
|
48
|
-
public constructor( ...args: TServiceArgs<DisksManager
|
|
48
|
+
public constructor( ...args: TServiceArgs<DisksManager<MountpointList, TConfig, TApplication>>) {
|
|
49
49
|
|
|
50
50
|
super(...args);
|
|
51
51
|
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import type { Prisma, PrismaClient } from '@models/types';
|
|
2
2
|
import * as runtime from '@/var/prisma/runtime/library.js';
|
|
3
3
|
|
|
4
|
+
/*export type TDelegate<R> = {
|
|
5
|
+
findMany(args?: any): Promise<R[]>
|
|
6
|
+
findFirst(args?: any): Promise<R | null>
|
|
7
|
+
}*/
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export type TDelegate<R> = PrismaClient[string];
|
|
15
|
+
|
|
16
|
+
/*export type TExtractPayload<D extends TDelegate<never>> =
|
|
17
|
+
D extends { [K in symbol]: { types: { payload: infer P } } } ? P : never;
|
|
18
|
+
|
|
19
|
+
export type TExtractPayload2<D> =
|
|
20
|
+
D extends { [K: symbol]: { types: Prisma.TypeMap<infer E>['model'][infer M] } }
|
|
21
|
+
? Prisma.TypeMap<E>['model'][M & keyof Prisma.TypeMap<E>['model']]['payload']
|
|
22
|
+
: never;*/
|
|
23
|
+
|
|
24
|
+
export type Transform<S extends TSubset, R, RT> = (
|
|
25
|
+
row: runtime.Types.Result.GetResult<
|
|
26
|
+
Prisma.$ProspectContactLeadPayload,
|
|
27
|
+
ReturnType<S>,
|
|
28
|
+
'findMany'
|
|
29
|
+
>[number]
|
|
30
|
+
) => RT
|
|
31
|
+
|
|
4
32
|
export type TWithStats = {
|
|
5
33
|
$table: string,
|
|
6
34
|
$key: string
|
|
@@ -13,12 +41,10 @@ export type TSubset = (...a: any[]) => Prisma.ProspectContactLeadFindFirstArgs &
|
|
|
13
41
|
}
|
|
14
42
|
|
|
15
43
|
export default class Facet<
|
|
16
|
-
D extends
|
|
17
|
-
findMany(args?: any): Promise<any>
|
|
18
|
-
findFirst(args?: any): Promise<any>
|
|
19
|
-
},
|
|
44
|
+
D extends TDelegate<R>,
|
|
20
45
|
S extends TSubset,
|
|
21
|
-
R
|
|
46
|
+
R, // Result type
|
|
47
|
+
RT // Transformed result type
|
|
22
48
|
> {
|
|
23
49
|
constructor(
|
|
24
50
|
|
|
@@ -28,18 +54,12 @@ export default class Facet<
|
|
|
28
54
|
private readonly subset: S,
|
|
29
55
|
|
|
30
56
|
/* the **ONLY** line that changed ↓↓↓ */
|
|
31
|
-
private readonly transform?:
|
|
32
|
-
row: runtime.Types.Result.GetResult<
|
|
33
|
-
Prisma.$ProspectContactLeadPayload,
|
|
34
|
-
ReturnType<S>,
|
|
35
|
-
'findMany'
|
|
36
|
-
>[number]
|
|
37
|
-
) => R,
|
|
57
|
+
private readonly transform?: Transform<S, R, RT>,
|
|
38
58
|
) { }
|
|
39
59
|
|
|
40
60
|
public async findMany(
|
|
41
61
|
...args: Parameters<S>
|
|
42
|
-
): Promise<
|
|
62
|
+
): Promise<RT[]> {
|
|
43
63
|
|
|
44
64
|
const { withStats, ...subset } = this.subset(...args);
|
|
45
65
|
|
|
@@ -57,7 +77,7 @@ export default class Facet<
|
|
|
57
77
|
|
|
58
78
|
public async findFirst(
|
|
59
79
|
...args: Parameters<S>
|
|
60
|
-
): Promise<
|
|
80
|
+
): Promise<RT | null> {
|
|
61
81
|
|
|
62
82
|
const { withStats, ...subset } = this.subset(...args);
|
|
63
83
|
|
|
@@ -10,7 +10,7 @@ import type { Application } from '@server/app';
|
|
|
10
10
|
import Service from '@server/app/service';
|
|
11
11
|
|
|
12
12
|
// Specific
|
|
13
|
-
import Facet, { TSubset } from './Facet';
|
|
13
|
+
import Facet, { TDelegate, TSubset, Transform } from './Facet';
|
|
14
14
|
|
|
15
15
|
/*----------------------------------
|
|
16
16
|
- TYPES
|
|
@@ -52,13 +52,11 @@ export default class ModelsManager extends Service<Config, Hooks, Application> {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
public Facet<
|
|
55
|
-
D extends
|
|
56
|
-
findMany(args?: any): Promise<any>
|
|
57
|
-
findFirst(args?: any): Promise<any>
|
|
58
|
-
},
|
|
55
|
+
D extends TDelegate<R>,
|
|
59
56
|
S extends TSubset,
|
|
60
|
-
R
|
|
61
|
-
|
|
57
|
+
R,
|
|
58
|
+
RT
|
|
59
|
+
>(...args: [D, S, Transform<S, R, RT>]) {
|
|
62
60
|
|
|
63
61
|
return new Facet(
|
|
64
62
|
this.client,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InputError } from '@common/errors';
|
|
2
|
-
import zod from 'zod';
|
|
2
|
+
import zod, { _ZodType } from 'zod';
|
|
3
3
|
|
|
4
4
|
export type TRichTextValidatorOptions = {
|
|
5
5
|
attachements?: boolean
|
|
@@ -46,18 +46,51 @@ function validateLexicalNode(node: any, opts: TRichTextValidatorOptions ) {
|
|
|
46
46
|
export const schema = {
|
|
47
47
|
...zod,
|
|
48
48
|
|
|
49
|
-
file: (
|
|
49
|
+
file: () => {
|
|
50
50
|
|
|
51
51
|
// Chaine = url ancien fichier = exclusion de la valeur pour conserver l'ancien fichier
|
|
52
52
|
// NOTE: Si la valeur est présente mais undefined, alors on supprimera le fichier
|
|
53
|
-
if (typeof val === 'string')
|
|
54
|
-
return true
|
|
53
|
+
/*if (typeof val === 'string')
|
|
54
|
+
return true;*/
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
return zod.file();
|
|
57
|
+
},
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
int: () => zod.preprocess( val => {
|
|
60
|
+
|
|
61
|
+
if (typeof val === "string")
|
|
62
|
+
return Number.parseInt(val);
|
|
63
|
+
|
|
64
|
+
return val;
|
|
65
|
+
|
|
66
|
+
}, zod.int()),
|
|
67
|
+
|
|
68
|
+
choice: ( choices: string[] | { value: any, label: string }[] | _ZodType, options: { multiple?: boolean } = {} ) => {
|
|
69
|
+
|
|
70
|
+
const normalizeValue = (value: any) => typeof value === 'object' ? value.value : value;
|
|
71
|
+
|
|
72
|
+
const valueType: _ZodType = Array.isArray(choices)
|
|
73
|
+
? zod.enum( choices.map(normalizeValue) )
|
|
74
|
+
: zod.string();
|
|
75
|
+
|
|
76
|
+
const itemType = zod.union([
|
|
77
|
+
|
|
78
|
+
zod.object({ value: valueType, label: zod.string() }),
|
|
79
|
+
|
|
80
|
+
valueType
|
|
81
|
+
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const type = options.multiple ? zod.array( itemType ) : itemType;
|
|
85
|
+
|
|
86
|
+
return type.transform(v => {
|
|
87
|
+
if (options.multiple) {
|
|
88
|
+
return v.map(normalizeValue);
|
|
89
|
+
} else {
|
|
90
|
+
return normalizeValue(v);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
},
|
|
61
94
|
|
|
62
95
|
richText: (opts: TRichTextValidatorOptions = {}) => schema.custom(val => {
|
|
63
96
|
|
|
@@ -94,4 +127,6 @@ export const schema = {
|
|
|
94
127
|
|
|
95
128
|
return true;
|
|
96
129
|
})
|
|
97
|
-
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type { default as z } from 'zod';
|
|
@@ -41,7 +41,7 @@ export type TBasicSSrData = {
|
|
|
41
41
|
export type TRouterContext<TRouter extends ServerRouter = ServerRouter> = (
|
|
42
42
|
// Request context
|
|
43
43
|
{
|
|
44
|
-
app:
|
|
44
|
+
app: TRouter["app"],
|
|
45
45
|
context: TRouterContext<TRouter>, // = this
|
|
46
46
|
request: ServerRequest<TRouter>,
|
|
47
47
|
api: ServerRequest<TRouter>["api"],
|
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
|
+
// Npm
|
|
6
|
+
import zod from 'zod';
|
|
7
|
+
import { SomeType } from 'zod/v4/core';
|
|
8
|
+
|
|
5
9
|
// Core
|
|
6
10
|
import {
|
|
7
11
|
default as Router, RequestService, Request as ServerRequest
|
|
8
12
|
} from '@server/services/router';
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// Specific
|
|
13
|
-
import ServerSchemaValidator from '.';
|
|
14
|
+
// Ap
|
|
15
|
+
import { schema } from '@server/services/router/request/validation/zod';
|
|
14
16
|
|
|
15
17
|
/*----------------------------------
|
|
16
18
|
- SERVICE CONFIG
|
|
@@ -25,38 +27,21 @@ export type TConfig = {
|
|
|
25
27
|
/*----------------------------------
|
|
26
28
|
- SERVICE
|
|
27
29
|
----------------------------------*/
|
|
28
|
-
export default
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
public app = router.app
|
|
35
|
-
) {
|
|
36
|
-
|
|
37
|
-
super(app);
|
|
30
|
+
export default(
|
|
31
|
+
request: ServerRequest<Router>,
|
|
32
|
+
config: TConfig,
|
|
33
|
+
router = request.router,
|
|
34
|
+
app = router.app
|
|
35
|
+
) => ({
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
...schema,
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
fields: TSchemaFieldsA | Schema<TSchemaFieldsA>
|
|
43
|
-
): TValidatedData<TSchemaFieldsA> {
|
|
39
|
+
validate( fields: zod.ZodSchema | { [key: string]: zod.ZodSchema } ) {
|
|
44
40
|
|
|
45
41
|
this.config.debug && console.log(LogPrefix, "Validate request data:", this.request.data);
|
|
46
42
|
|
|
47
|
-
const schema = fields
|
|
48
|
-
|
|
49
|
-
// Les InputError seront propagées vers le middleware dédié à la gestion des erreurs
|
|
50
|
-
const values = schema.validate( this.request.data, {
|
|
51
|
-
debug: this.config.debug,
|
|
52
|
-
validateDeps: false,
|
|
53
|
-
validators: this
|
|
54
|
-
}, []);
|
|
55
|
-
|
|
56
|
-
// For logging
|
|
57
|
-
this.request.validatedData = values;
|
|
58
|
-
|
|
59
|
-
return values;
|
|
60
|
-
}
|
|
43
|
+
const schema = typeof fields === 'object' ? zod.object(fields) : fields;
|
|
61
44
|
|
|
62
|
-
|
|
45
|
+
return schema.parse(this.request.data);
|
|
46
|
+
},
|
|
47
|
+
})
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
RouterService
|
|
9
9
|
} from '@server/services/router';
|
|
10
10
|
|
|
11
|
-
import
|
|
11
|
+
import makeRequestValidators from '../request';
|
|
12
12
|
|
|
13
13
|
/*----------------------------------
|
|
14
14
|
- TYPES
|
|
@@ -22,7 +22,7 @@ export default class SchemaRouterService<
|
|
|
22
22
|
TUser extends {} = {}
|
|
23
23
|
> extends RouterService {
|
|
24
24
|
|
|
25
|
-
public requestService( request: ServerRequest )
|
|
26
|
-
return
|
|
25
|
+
public requestService( request: ServerRequest ) {
|
|
26
|
+
return makeRequestValidators( request, this.config );
|
|
27
27
|
}
|
|
28
28
|
}
|
package/types/icons.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type TIcones = "solid/spinner-third"|"
|
|
1
|
+
export type TIcones = "times"|"solid/spinner-third"|"long-arrow-right"|"check-circle"|"rocket"|"user-circle"|"crosshairs"|"arrow-right"|"user-shield"|"shield-alt"|"chart-line"|"money-bill-wave"|"star"|"link"|"file-alt"|"long-arrow-left"|"plane-departure"|"plus-circle"|"comments-alt"|"chart-bar"|"calendar-alt"|"paper-plane"|"at"|"search"|"lightbulb"|"magnet"|"phone"|"brands/linkedin"|"brands/whatsapp"|"user"|"user-plus"|"sack-dollar"|"info-circle"|"mouse-pointer"|"thumbs-up"|"dollar-sign"|"plus"|"minus"|"trash"|"play"|"stop"|"clock"|"cog"|"ellipsis-h"|"check"|"regular/shield-check"|"angle-down"|"angle-up"|"solid/crown"|"eye"|"pen"|"file"|"envelope"|"coins"|"download"|"exclamation-circle"|"times-circle"|"meh-rolling-eyes"|"arrow-left"|"bars"|"chevron-left"|"bolt"|"key"|"power-off"|"comment-alt"|"question-circle"|"wind"|"minus-circle"|"external-link"|"brands/google"|"broom"|"solid/check-circle"|"solid/exclamation-triangle"|"solid/times-circle"|"hourglass"|"copy"|"users"|"bug"|"binoculars"|"building"|"briefcase"|"map-marker-alt"|"graduation-cap"|"coin"|"angle-left"|"angle-right"|"plug"|"arrow-to-bottom"|"solid/magic"|"industry"|"map-marker"|"calendar"|"fire"|"magic"|"globe"|"code"|"bold"|"italic"|"underline"|"font"|"strikethrough"|"subscript"|"superscript"|"empty-set"|"horizontal-rule"|"page-break"|"image"|"table"|"poll"|"columns"|"sticky-note"|"caret-right"|"unlink"|"align-left"|"align-center"|"align-right"|"align-justify"|"indent"|"outdent"|"list-ul"|"check-square"|"h1"|"h2"|"h3"|"h4"|"list-ol"|"paragraph"|"quote-left"
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// https://github.com/adonisjs/validator
|
|
2
|
-
|
|
3
|
-
/*----------------------------------
|
|
4
|
-
- DEPENDANCES
|
|
5
|
-
----------------------------------*/
|
|
6
|
-
/*----------------------------------
|
|
7
|
-
- CONSTANTES
|
|
8
|
-
----------------------------------*/
|
|
9
|
-
|
|
10
|
-
const debug = false;
|
|
11
|
-
|
|
12
|
-
/*----------------------------------
|
|
13
|
-
- TYPES: DECLARATION SCHEMA
|
|
14
|
-
----------------------------------*/
|
|
15
|
-
|
|
16
|
-
//import type { Choix } from '@client/components/Champs/Base/Choix';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/*----------------------------------
|
|
20
|
-
- FONCTIONS
|
|
21
|
-
----------------------------------*/
|
|
22
|
-
export const isSchema = <TElem extends TSchema | TSchemaChampComplet>(elem: TElem): elem is TSchema => !('type' in elem)
|
|
23
|
-
|
|
24
|
-
export const initDonnees = <TSchemaA extends TSchema>(
|
|
25
|
-
schema: TSchemaA,
|
|
26
|
-
donnees: TObjetDonnees,
|
|
27
|
-
toutConserver: boolean = false
|
|
28
|
-
): Partial<TValidatedData<TSchemaA>> => {
|
|
29
|
-
|
|
30
|
-
// toutConserver = true: on conserve toutes les données, y compris celles n'étant pas été définies dans le schéma
|
|
31
|
-
let retour: Partial<TValidatedData<TSchemaA>> = toutConserver ? { ...donnees } : {}
|
|
32
|
-
|
|
33
|
-
for (const nomChamp in schema) {
|
|
34
|
-
const elem = schema[nomChamp];
|
|
35
|
-
|
|
36
|
-
// Sous-schema
|
|
37
|
-
if (isSchema(elem)) {
|
|
38
|
-
|
|
39
|
-
retour[nomChamp] = initDonnees(elem, donnees[nomChamp] || {}, toutConserver);
|
|
40
|
-
|
|
41
|
-
// Champ
|
|
42
|
-
} else if (elem.defaut !== undefined && donnees[nomChamp] === undefined) {
|
|
43
|
-
|
|
44
|
-
retour[nomChamp] = elem.defaut;
|
|
45
|
-
|
|
46
|
-
} else
|
|
47
|
-
retour[nomChamp] = donnees[nomChamp];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return retour;
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const validate =
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/*----------------------------------
|
|
2
|
-
- DEPENDANCES
|
|
3
|
-
----------------------------------*/
|
|
4
|
-
|
|
5
|
-
import type { TValidatorDefinition } from './validator';
|
|
6
|
-
import type { SchemaValidators } from './validators';
|
|
7
|
-
|
|
8
|
-
/*----------------------------------
|
|
9
|
-
- EXPORT
|
|
10
|
-
----------------------------------*/
|
|
11
|
-
|
|
12
|
-
export { default as Schema } from './schema';
|
|
13
|
-
export type { TSchemaFields, TValidatedData } from './schema';
|
|
14
|
-
|
|
15
|
-
export const field = new Proxy<SchemaValidators>({} as SchemaValidators, {
|
|
16
|
-
get: (target, propKey) => {
|
|
17
|
-
return (...args: any[]) => ([ propKey, args ]);
|
|
18
|
-
}
|
|
19
|
-
}) as unknown as {
|
|
20
|
-
[K in keyof SchemaValidators]: SchemaValidators[K] extends (...args: any[]) => any
|
|
21
|
-
? (...args: Parameters<SchemaValidators[K]>) => TValidatorDefinition<K>
|
|
22
|
-
: SchemaValidators[K];
|
|
23
|
-
};
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
/*----------------------------------
|
|
2
|
-
- DEPENDANCES
|
|
3
|
-
----------------------------------*/
|
|
4
|
-
|
|
5
|
-
// Core
|
|
6
|
-
import { CoreError, TListeErreursSaisie, InputErrorSchema } from '@common/errors';
|
|
7
|
-
|
|
8
|
-
// Specific
|
|
9
|
-
import { default as Validator, EXCLUDE_VALUE, TValidatorDefinition } from './validator';
|
|
10
|
-
import defaultValidators, { SchemaValidators, getFieldValidator } from './validators';
|
|
11
|
-
|
|
12
|
-
/*----------------------------------
|
|
13
|
-
- TYPES
|
|
14
|
-
----------------------------------*/
|
|
15
|
-
|
|
16
|
-
export type TSchemaFields = {
|
|
17
|
-
[fieldName: string]: TSchemaFields | Schema<{}> | Validator<any> | TValidatorDefinition
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type TSchemaOptions = {
|
|
21
|
-
opt?: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type TValidateOptions<TFields extends TSchemaFields = {}> = {
|
|
25
|
-
debug?: boolean,
|
|
26
|
-
throwError?: boolean,
|
|
27
|
-
ignoreMissing?: boolean,
|
|
28
|
-
only?: (keyof TFields)[],
|
|
29
|
-
validateDeps?: boolean,
|
|
30
|
-
autoCorrect?: boolean,
|
|
31
|
-
validators?: SchemaValidators
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type TValidationResult<TFields extends TSchemaFields> = {
|
|
35
|
-
values: TValidatedData<TFields>,
|
|
36
|
-
errorsCount: number,
|
|
37
|
-
erreurs: TListeErreursSaisie
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type TSchemaData<TSchema extends Schema<{}>> =
|
|
41
|
-
TValidationResult<TSchema["fields"]>
|
|
42
|
-
|
|
43
|
-
export type TValidatedData<TFields extends TSchemaFields> = {
|
|
44
|
-
// For each field, the values returned by validator.validate()
|
|
45
|
-
[name in keyof TFields]: TFieldReturnType<TFields[name]>
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
type TFieldReturnType<TField> = TField extends TValidatorDefinition
|
|
49
|
-
? TField[2]
|
|
50
|
-
: TField extends Schema<infer T>
|
|
51
|
-
? TValidatedData<T>
|
|
52
|
-
: never
|
|
53
|
-
|
|
54
|
-
/*----------------------------------
|
|
55
|
-
- CONST
|
|
56
|
-
----------------------------------*/
|
|
57
|
-
|
|
58
|
-
const LogPrefix = '[schema][validator]';
|
|
59
|
-
|
|
60
|
-
/*----------------------------------
|
|
61
|
-
- CLASS
|
|
62
|
-
----------------------------------*/
|
|
63
|
-
export default class Schema<TFields extends TSchemaFields> {
|
|
64
|
-
|
|
65
|
-
public constructor(
|
|
66
|
-
public fields: TFields,
|
|
67
|
-
public options: TSchemaOptions = {}
|
|
68
|
-
) {
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public getFieldValidator(
|
|
73
|
-
fieldName: string,
|
|
74
|
-
validators: SchemaValidators = defaultValidators
|
|
75
|
-
): null | Validator<any> | Schema<{}> {
|
|
76
|
-
|
|
77
|
-
let field = this.fields[fieldName];
|
|
78
|
-
if (field === undefined) {
|
|
79
|
-
|
|
80
|
-
return null;
|
|
81
|
-
|
|
82
|
-
// TValidatorDefinition
|
|
83
|
-
} else
|
|
84
|
-
return getFieldValidator(field);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
public validate<TDonnees extends TObjetDonnees>(
|
|
88
|
-
dataToValidate: Partial<TDonnees>,
|
|
89
|
-
opts: TValidateOptions<TFields> = {},
|
|
90
|
-
chemin: string[] = []
|
|
91
|
-
): TValidatedData<TFields> {
|
|
92
|
-
|
|
93
|
-
const validators = opts.validators || defaultValidators;
|
|
94
|
-
|
|
95
|
-
// Check data type
|
|
96
|
-
if (typeof dataToValidate !== 'object')
|
|
97
|
-
throw new InputErrorSchema({ [chemin.join('.')]: ['Must be an object'] });
|
|
98
|
-
|
|
99
|
-
// Default options
|
|
100
|
-
opts = {
|
|
101
|
-
debug: false,
|
|
102
|
-
throwError: true,
|
|
103
|
-
validateDeps: true,
|
|
104
|
-
autoCorrect: false,
|
|
105
|
-
...opts,
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const keysToValidate = (opts.only || Object.keys(this.fields)) as string[];
|
|
109
|
-
|
|
110
|
-
// Validation de chacune d'entre elles
|
|
111
|
-
const output: Partial<TDonnees> = {};
|
|
112
|
-
let erreurs: TListeErreursSaisie = {};
|
|
113
|
-
let errorsCount = 0;
|
|
114
|
-
for (const fieldName of keysToValidate) {
|
|
115
|
-
|
|
116
|
-
const validator = this.getFieldValidator(fieldName, validators);
|
|
117
|
-
if (validator === null) {
|
|
118
|
-
opts.debug && console.warn(LogPrefix, '[' + fieldName + ']', 'Exclusion (pas présent dans le schéma)');
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Create field path
|
|
123
|
-
const cheminA = [...chemin, fieldName]
|
|
124
|
-
const cheminAstr = cheminA.join('.')
|
|
125
|
-
const valOrigine = dataToValidate[fieldName];
|
|
126
|
-
|
|
127
|
-
// Validation
|
|
128
|
-
try {
|
|
129
|
-
|
|
130
|
-
const val = validator.validate(valOrigine, opts, cheminA);
|
|
131
|
-
|
|
132
|
-
// Exclusion seulement si explicitement demandé
|
|
133
|
-
// IMPORTANT: Conserver les values undefined
|
|
134
|
-
// La présence d'un valeur undefined peut être utile, par exemple, pour indiquer qu'on souhaite supprimer une donnée
|
|
135
|
-
// Exemple: undefinec = suppression fichier | Absende donnée = conservation fihcier actuel
|
|
136
|
-
if (val === EXCLUDE_VALUE)
|
|
137
|
-
opts.debug && console.log(LogPrefix, '[' + cheminA + '] Exclusion demandée');
|
|
138
|
-
// Key not in the input data, we don't create an entry in the output
|
|
139
|
-
else if (fieldName in dataToValidate)
|
|
140
|
-
output[fieldName] = val;
|
|
141
|
-
|
|
142
|
-
opts.debug && console.log(LogPrefix, '[' + cheminA + ']', valOrigine, '=>', val);
|
|
143
|
-
|
|
144
|
-
} catch (error) {
|
|
145
|
-
|
|
146
|
-
opts.debug && console.warn(LogPrefix, '[' + cheminA + ']', valOrigine, '|| CoreError:', error);
|
|
147
|
-
|
|
148
|
-
if (error instanceof InputErrorSchema) {
|
|
149
|
-
|
|
150
|
-
erreurs = { ...erreurs, ...error.errors };
|
|
151
|
-
errorsCount += Object.keys(error.errors).length;
|
|
152
|
-
|
|
153
|
-
} else if (error instanceof CoreError) {
|
|
154
|
-
|
|
155
|
-
erreurs[cheminAstr] = [error.message]
|
|
156
|
-
errorsCount++;
|
|
157
|
-
|
|
158
|
-
} else if (SERVER) {
|
|
159
|
-
|
|
160
|
-
// Server: transmiss error & report bug
|
|
161
|
-
throw error;
|
|
162
|
-
|
|
163
|
-
} else {
|
|
164
|
-
|
|
165
|
-
console.error(LogPrefix, '[' + cheminA + ']', error);
|
|
166
|
-
erreurs[cheminAstr] = ["Technical error while validating data"];
|
|
167
|
-
errorsCount++;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (errorsCount !== 0)
|
|
173
|
-
throw new InputErrorSchema(erreurs);
|
|
174
|
-
|
|
175
|
-
opts.debug && console.log(LogPrefix, '', dataToValidate, '=>', output);
|
|
176
|
-
|
|
177
|
-
return output as TValidatedData<TFields>;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
public validateWithDetails<TDonnees extends TObjetDonnees>(
|
|
181
|
-
|
|
182
|
-
dataToValidate: Partial<TDonnees>,
|
|
183
|
-
allData: TDonnees,
|
|
184
|
-
output: TObjetDonnees = {},
|
|
185
|
-
|
|
186
|
-
opts: TValidateOptions<TFields> = {},
|
|
187
|
-
chemin: string[] = []
|
|
188
|
-
|
|
189
|
-
): TValidationResult<TFields> {
|
|
190
|
-
|
|
191
|
-
let erreurs: TListeErreursSaisie = {};
|
|
192
|
-
let errorsCount = 0;
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
this.validate(dataToValidate, opts, chemin);
|
|
196
|
-
} catch (error) {
|
|
197
|
-
if (error instanceof InputErrorSchema) {
|
|
198
|
-
erreurs = error.errors;
|
|
199
|
-
errorsCount = Object.keys(erreurs).length;
|
|
200
|
-
} else {
|
|
201
|
-
throw error;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
values: output as TValidatedData<TFields>,
|
|
207
|
-
erreurs,
|
|
208
|
-
errorsCount,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/*----------------------------------
|
|
2
|
-
- DEPENDANCES
|
|
3
|
-
----------------------------------*/
|
|
4
|
-
|
|
5
|
-
// Npm
|
|
6
|
-
import type { ComponentChild } from 'preact'
|
|
7
|
-
|
|
8
|
-
// Core
|
|
9
|
-
import { InputError } from '@common/errors';
|
|
10
|
-
|
|
11
|
-
// Specific
|
|
12
|
-
import type { TValidateOptions } from './schema';
|
|
13
|
-
import type { SchemaValidators } from './validators';
|
|
14
|
-
import type { InputBaseProps } from '@client/components/utils';
|
|
15
|
-
|
|
16
|
-
/*----------------------------------
|
|
17
|
-
- TYPES
|
|
18
|
-
----------------------------------*/
|
|
19
|
-
|
|
20
|
-
export type TValidatorDefinition<K extends keyof SchemaValidators = keyof SchemaValidators> = [
|
|
21
|
-
type: string,
|
|
22
|
-
args: any[],
|
|
23
|
-
returnType: string
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
// TODO: remove
|
|
27
|
-
export type TValidatorOptions<TValue> = {
|
|
28
|
-
|
|
29
|
-
rendu?: TFieldRenderer,
|
|
30
|
-
|
|
31
|
-
// I don't remind what is options.activer about
|
|
32
|
-
activer?: (donnees: TObjetDonnees) => boolean,
|
|
33
|
-
onglet?: string, // Sert juste d'identifiant secondaire. Ex: nom onglet correspondant
|
|
34
|
-
|
|
35
|
-
// Executé après le validateur propre au type
|
|
36
|
-
dependances?: string[],
|
|
37
|
-
opt?: true,
|
|
38
|
-
defaut?: TValue,
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export type TFieldRenderer = (props: any) => ComponentChild;
|
|
43
|
-
|
|
44
|
-
type TNonEmptyValue = Exclude<any, undefined | '' | null>
|
|
45
|
-
|
|
46
|
-
type TValidationArgs<TValue, TAllValues extends {}> = [
|
|
47
|
-
// For the value given as input in the validation function,
|
|
48
|
-
// Only the empty values were escluded
|
|
49
|
-
val: TNonEmptyValue,
|
|
50
|
-
validateOptions: TValidateOptions,
|
|
51
|
-
path: string[]
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
type TValidationFunction<TValue, TAllValues extends {} = {}> = (
|
|
55
|
-
...args: TValidationArgs<TValue, TAllValues>
|
|
56
|
-
) => TValue | typeof EXCLUDE_VALUE;
|
|
57
|
-
|
|
58
|
-
type TValidateReturnType<
|
|
59
|
-
TOptions extends TValidatorOptions<TValue>,
|
|
60
|
-
TValue extends any
|
|
61
|
-
> = TOptions extends { opt: true }
|
|
62
|
-
? (undefined | TValue)
|
|
63
|
-
: TValue
|
|
64
|
-
|
|
65
|
-
/*----------------------------------
|
|
66
|
-
- CONST
|
|
67
|
-
----------------------------------*/
|
|
68
|
-
|
|
69
|
-
export const EXCLUDE_VALUE = "action:exclure" as const;
|
|
70
|
-
|
|
71
|
-
/*----------------------------------
|
|
72
|
-
- CLASS
|
|
73
|
-
----------------------------------*/
|
|
74
|
-
export default class Validator<
|
|
75
|
-
TValue,
|
|
76
|
-
TOptions extends TValidatorOptions<TValue> = TValidatorOptions<TValue>,
|
|
77
|
-
//TComponent = React.FunctionComponent< InputBaseProps< TValue > >
|
|
78
|
-
> {
|
|
79
|
-
|
|
80
|
-
public constructor(
|
|
81
|
-
public type: string,
|
|
82
|
-
public validateType: TValidationFunction<TValue>,
|
|
83
|
-
public options: TOptions,
|
|
84
|
-
public componentAttributes: Partial<InputBaseProps<TValue>> = {}
|
|
85
|
-
) {
|
|
86
|
-
|
|
87
|
-
// Basic component attriutes
|
|
88
|
-
this.componentAttributes.required = options?.opt !== true;
|
|
89
|
-
//this.componentAttributes.validator = this;
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
public isEmpty = (val: any) => val === undefined || val === '' || val === null
|
|
94
|
-
|
|
95
|
-
public validate(...[
|
|
96
|
-
val, validateOptions, path
|
|
97
|
-
]: TValidationArgs<TValue, {}>): TValidateReturnType<TOptions, TValue> {
|
|
98
|
-
|
|
99
|
-
// Required value
|
|
100
|
-
if (this.isEmpty(val)) {
|
|
101
|
-
// Optionnel, on skip
|
|
102
|
-
if (this.options.opt === true || (
|
|
103
|
-
validateOptions?.ignoreMissing === true
|
|
104
|
-
&&
|
|
105
|
-
val === undefined
|
|
106
|
-
))
|
|
107
|
-
return undefined as TValidateReturnType<TOptions, TValue>;
|
|
108
|
-
// Requis
|
|
109
|
-
else
|
|
110
|
-
throw new InputError("Please enter a value");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Validate type
|
|
114
|
-
return this.validateType(val, validateOptions, path) as TValidateReturnType<TOptions, TValue>;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
}
|
|
@@ -1,485 +0,0 @@
|
|
|
1
|
-
/*----------------------------------
|
|
2
|
-
- DEPENDANCES
|
|
3
|
-
----------------------------------*/
|
|
4
|
-
|
|
5
|
-
// Npm
|
|
6
|
-
import trim from 'validator/lib/trim';
|
|
7
|
-
import isISO8601 from 'validator/lib/isISO8601';
|
|
8
|
-
import toDate from 'validator/lib/toDate';
|
|
9
|
-
import isEmail from 'validator/lib/isEmail';
|
|
10
|
-
import isURL from 'validator/lib/isURL';
|
|
11
|
-
|
|
12
|
-
import normalizeUrl, { Options as NormalizeUrlOptions } from 'normalize-url';
|
|
13
|
-
|
|
14
|
-
// Core
|
|
15
|
-
import { InputError } from '@common/errors';
|
|
16
|
-
|
|
17
|
-
// Speciific
|
|
18
|
-
import Schema, { TSchemaFields } from './schema'
|
|
19
|
-
import Validator, { TValidatorOptions, EXCLUDE_VALUE, TValidatorDefinition } from './validator'
|
|
20
|
-
|
|
21
|
-
/*----------------------------------
|
|
22
|
-
- TYPES
|
|
23
|
-
----------------------------------*/
|
|
24
|
-
|
|
25
|
-
export type TFileValidator = TValidatorOptions<File> & {
|
|
26
|
-
type?: string[], // Raccourci, ou liste de mimetype
|
|
27
|
-
taille?: number,
|
|
28
|
-
disk?: string, // Disk to upload files to
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
type TSchemaSubtype = Schema<{}> | TSchemaFields | TValidatorDefinition;
|
|
32
|
-
|
|
33
|
-
type TSubtype = TSchemaSubtype | Validator<any> | TValidatorDefinition;
|
|
34
|
-
|
|
35
|
-
/*----------------------------------
|
|
36
|
-
- CONST
|
|
37
|
-
----------------------------------*/
|
|
38
|
-
|
|
39
|
-
export type TRichTextValidatorOptions = {
|
|
40
|
-
attachements?: TFileValidator
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const getFieldValidator = (field: TValidatorDefinition) => {
|
|
44
|
-
|
|
45
|
-
if (Array.isArray(field)) {
|
|
46
|
-
|
|
47
|
-
const [validatorName, validatorArgs] = field;
|
|
48
|
-
const getValidator = validators[validatorName];
|
|
49
|
-
if (getValidator === undefined)
|
|
50
|
-
throw new Error('Unknown validator: ' + validatorName);
|
|
51
|
-
|
|
52
|
-
return getValidator(...validatorArgs);
|
|
53
|
-
|
|
54
|
-
// TSchemaFields
|
|
55
|
-
} else if (field.constructor === Object) {
|
|
56
|
-
|
|
57
|
-
return new Schema(field as TSchemaFields);
|
|
58
|
-
|
|
59
|
-
// Schema
|
|
60
|
-
} else
|
|
61
|
-
return field as Validator<any>
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Recursive function to validate each node
|
|
65
|
-
function validateLexicalNode(node: any, opts: TRichTextValidatorOptions ) {
|
|
66
|
-
|
|
67
|
-
// Each node should be an object with a `type` property
|
|
68
|
-
if (typeof node !== 'object' || !node.type || typeof node.type !== 'string')
|
|
69
|
-
throw new InputError("Invalid rich text value (3).");
|
|
70
|
-
|
|
71
|
-
// Validate text nodes
|
|
72
|
-
if (node.type === 'text') {
|
|
73
|
-
|
|
74
|
-
if (typeof node.text !== 'string')
|
|
75
|
-
throw new InputError("Invalid rich text value (4).");
|
|
76
|
-
|
|
77
|
-
// Validate paragraph, heading, or other structural nodes that may contain children
|
|
78
|
-
} else if (['paragraph', 'heading', 'list', 'listitem'].includes(node.type)) {
|
|
79
|
-
|
|
80
|
-
if (!Array.isArray(node.children) || !node.children.every(children => validateLexicalNode(children, opts))) {
|
|
81
|
-
throw new InputError("Invalid rich text value (5).");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Files upload
|
|
85
|
-
} else if (node.type === 'image') {
|
|
86
|
-
|
|
87
|
-
// Check if allowed
|
|
88
|
-
/*if (opts.attachements === undefined)
|
|
89
|
-
throw new InputError("Image attachments not allowed in this rich text field.");*/
|
|
90
|
-
|
|
91
|
-
// TODO: check mime
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// Upload file
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/*----------------------------------
|
|
103
|
-
- CLASS
|
|
104
|
-
----------------------------------*/
|
|
105
|
-
export class SchemaValidators {
|
|
106
|
-
|
|
107
|
-
/*----------------------------------
|
|
108
|
-
- UTILITIES
|
|
109
|
-
----------------------------------*/
|
|
110
|
-
// Make every field optional
|
|
111
|
-
public partial = <TFields extends TSchemaFields>(schema: TFields, fieldsList?: (keyof TFields)[] ) => {
|
|
112
|
-
|
|
113
|
-
if (fieldsList === undefined)
|
|
114
|
-
fieldsList = Object.keys(schema) as (keyof TFields)[];
|
|
115
|
-
|
|
116
|
-
const partialSchema: Partial<TFields> = {};
|
|
117
|
-
for (const key of fieldsList) {
|
|
118
|
-
|
|
119
|
-
if (!(key in schema))
|
|
120
|
-
throw new Error("The field " + key + " is not in the schema.");
|
|
121
|
-
|
|
122
|
-
// Only if validator
|
|
123
|
-
if (schema[key] instanceof Validator)
|
|
124
|
-
partialSchema[key] = new Validator(schema[key].type, schema[key].validateType, {
|
|
125
|
-
...schema[key].options,
|
|
126
|
-
opt: true
|
|
127
|
-
});
|
|
128
|
-
else
|
|
129
|
-
partialSchema[key] = schema[key];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return partialSchema as TFields;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/*----------------------------------
|
|
136
|
-
- CONTENEURS
|
|
137
|
-
----------------------------------*/
|
|
138
|
-
public object = ( subtype?: TSchemaSubtype, { ...opts }: TValidatorOptions<object> & {
|
|
139
|
-
|
|
140
|
-
} = {}) =>
|
|
141
|
-
new Validator<object>('object', (val, options, path) => {
|
|
142
|
-
|
|
143
|
-
// The value should be an object
|
|
144
|
-
if (typeof val !== 'object' || val.constructor !== Object)
|
|
145
|
-
throw new InputError("This value must be an object.");
|
|
146
|
-
|
|
147
|
-
// If no subtype, return the object as is
|
|
148
|
-
if (subtype === undefined)
|
|
149
|
-
return val;
|
|
150
|
-
|
|
151
|
-
// If subtype is a schema
|
|
152
|
-
const schema = getFieldValidator(subtype) as Schema<{}>;
|
|
153
|
-
|
|
154
|
-
// Validate schema
|
|
155
|
-
const value = schema.validate(val, options, path);
|
|
156
|
-
|
|
157
|
-
return value;
|
|
158
|
-
}, opts)
|
|
159
|
-
|
|
160
|
-
public array = ( subtype: TSubtype, { choice, min, max, ...opts }: TValidatorOptions<any[]> & {
|
|
161
|
-
choice?: any[],
|
|
162
|
-
min?: number,
|
|
163
|
-
max?: number
|
|
164
|
-
} = {}) => new Validator<any[]>('array', (items, options, path) => {
|
|
165
|
-
|
|
166
|
-
// Type
|
|
167
|
-
if (!Array.isArray(items))
|
|
168
|
-
throw new InputError("This value must be a list.");
|
|
169
|
-
|
|
170
|
-
// Items number
|
|
171
|
-
if ((min !== undefined && items.length < min))
|
|
172
|
-
throw new InputError(`Please select at least ${min} items.`);
|
|
173
|
-
if ((max !== undefined && items.length > max))
|
|
174
|
-
throw new InputError(`Please select maximum ${max} items.`);
|
|
175
|
-
|
|
176
|
-
// Verif each item
|
|
177
|
-
if (subtype === undefined)
|
|
178
|
-
return items;
|
|
179
|
-
|
|
180
|
-
const validator = getFieldValidator(subtype);
|
|
181
|
-
|
|
182
|
-
items = items.map( item =>
|
|
183
|
-
validator.validate( item, options, path )
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
return items;
|
|
187
|
-
}, {
|
|
188
|
-
...opts,
|
|
189
|
-
//multiple: true, // Sélection multiple
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
public choice = (choices?: any[], { multiple, ...opts }: TValidatorOptions<any> & {
|
|
193
|
-
multiple?: boolean
|
|
194
|
-
} = {}) => new Validator<any>('choice', (val, options, path) => {
|
|
195
|
-
|
|
196
|
-
// Empty array = undefined if not required
|
|
197
|
-
if (val.length === 0 && opts.opt)
|
|
198
|
-
return undefined;
|
|
199
|
-
|
|
200
|
-
// Normalize for verifications
|
|
201
|
-
const choicesValues = choices?.map(v => typeof v === 'object' ? v.value : v)
|
|
202
|
-
|
|
203
|
-
const checkChoice = ( choice: any ) => {
|
|
204
|
-
|
|
205
|
-
// Choice object = extract value
|
|
206
|
-
// We check for choice objec via the label prop, as the value can be undefined (and so, not transmitted)
|
|
207
|
-
if (typeof choice === 'object' && ('label' in choice) && typeof choice.label === 'string' && typeof choice.value !== 'object')
|
|
208
|
-
choice = choice.value;
|
|
209
|
-
|
|
210
|
-
// If choices list rpovided, check if the choice is in the choices list
|
|
211
|
-
if (choicesValues !== undefined && !choicesValues.includes(choice))
|
|
212
|
-
throw new InputError("Invalid value: " + choice + ". Must be: " + choicesValues.join(', '));
|
|
213
|
-
|
|
214
|
-
return choice;
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Check every choice
|
|
219
|
-
if (Array.isArray( val ))
|
|
220
|
-
val = val.map(checkChoice)
|
|
221
|
-
else
|
|
222
|
-
val = checkChoice( val );
|
|
223
|
-
|
|
224
|
-
return val;
|
|
225
|
-
|
|
226
|
-
}, opts, { choices, multiple })
|
|
227
|
-
|
|
228
|
-
/*----------------------------------
|
|
229
|
-
- CHAINES
|
|
230
|
-
----------------------------------*/
|
|
231
|
-
public string = ({ min, max, in: choices, ...opts }: TValidatorOptions<string> & {
|
|
232
|
-
min?: number,
|
|
233
|
-
max?: number,
|
|
234
|
-
in?: string[]
|
|
235
|
-
} = {}) => new Validator<string>('string', (val, options, path) => {
|
|
236
|
-
|
|
237
|
-
// Check type
|
|
238
|
-
if (val === '')
|
|
239
|
-
return undefined;
|
|
240
|
-
else if (typeof val === 'number')
|
|
241
|
-
return val.toString();
|
|
242
|
-
else if (typeof val !== 'string')
|
|
243
|
-
throw new InputError("This value must be a string.");
|
|
244
|
-
|
|
245
|
-
// Whitespace
|
|
246
|
-
val = trim(val);
|
|
247
|
-
|
|
248
|
-
// In
|
|
249
|
-
if (choices !== undefined && !choices.includes(val))
|
|
250
|
-
throw new InputError(`Invalid value: ${val}. Must be one of: ${choices.join(', ')}`);
|
|
251
|
-
|
|
252
|
-
// Min size
|
|
253
|
-
if (min !== undefined && val.length < min)
|
|
254
|
-
throw new InputError(`Must be at least ` + min + ' characters');
|
|
255
|
-
|
|
256
|
-
// Max size
|
|
257
|
-
if (max !== undefined && val.length > max)
|
|
258
|
-
if (options?.autoCorrect)
|
|
259
|
-
val = val.substring(0, max);
|
|
260
|
-
else
|
|
261
|
-
throw new InputError(`Must be up to ` + max + ' characters');
|
|
262
|
-
|
|
263
|
-
return val;
|
|
264
|
-
|
|
265
|
-
}, opts)
|
|
266
|
-
|
|
267
|
-
public url = (opts: TValidatorOptions<string> & {
|
|
268
|
-
normalize?: NormalizeUrlOptions
|
|
269
|
-
} = {}) =>
|
|
270
|
-
new Validator<string>('url', (inputVal, options, path) => {
|
|
271
|
-
|
|
272
|
-
let val = this.string(opts).validate(inputVal, options, path);
|
|
273
|
-
|
|
274
|
-
// Check if URL
|
|
275
|
-
if (!isURL(val, {
|
|
276
|
-
// https://www.npmjs.com/package/validator
|
|
277
|
-
}))
|
|
278
|
-
throw new InputError(`Please provide a valid URL.`);
|
|
279
|
-
|
|
280
|
-
// Normalize
|
|
281
|
-
if (opts.normalize !== undefined)
|
|
282
|
-
val = normalizeUrl(val, opts.normalize);
|
|
283
|
-
|
|
284
|
-
return val;
|
|
285
|
-
}, opts)
|
|
286
|
-
|
|
287
|
-
public email = (opts: TValidatorOptions<string> & {} = {}) =>
|
|
288
|
-
new Validator<string>('email', (inputVal, options, path) => {
|
|
289
|
-
|
|
290
|
-
let val = this.string(opts).validate(inputVal, options, path);
|
|
291
|
-
|
|
292
|
-
if (!isEmail(val))
|
|
293
|
-
throw new InputError("Please enter a valid email address.");
|
|
294
|
-
|
|
295
|
-
// Disable normalzation !!! We should keep the email as it was entered by the user
|
|
296
|
-
/*const normalizedEmail = normalizeEmail(val, {
|
|
297
|
-
all_lowercase: true,
|
|
298
|
-
gmail_lowercase: true,
|
|
299
|
-
gmail_remove_dots: false,
|
|
300
|
-
gmail_remove_subaddress: true,
|
|
301
|
-
gmail_convert_googlemaildotcom: true,
|
|
302
|
-
|
|
303
|
-
outlookdotcom_lowercase: true,
|
|
304
|
-
outlookdotcom_remove_subaddress: true,
|
|
305
|
-
|
|
306
|
-
yahoo_lowercase: true,
|
|
307
|
-
yahoo_remove_subaddress: true,
|
|
308
|
-
|
|
309
|
-
yandex_lowercase: true,
|
|
310
|
-
|
|
311
|
-
icloud_lowercase: true,
|
|
312
|
-
icloud_remove_subaddress: true,
|
|
313
|
-
});*/
|
|
314
|
-
|
|
315
|
-
const normalizedEmail = val.toLowerCase();
|
|
316
|
-
console.log("validate email, inou", val, normalizedEmail);
|
|
317
|
-
|
|
318
|
-
return normalizedEmail;
|
|
319
|
-
}, opts)
|
|
320
|
-
|
|
321
|
-
/*----------------------------------
|
|
322
|
-
- NOMBRES
|
|
323
|
-
----------------------------------*/
|
|
324
|
-
// On ne spread pas min et max afin quils soient passés dans les props du composant
|
|
325
|
-
public number = (withDecimals: boolean) => ({ ...opts }: TValidatorOptions<number> & {
|
|
326
|
-
min?: number,
|
|
327
|
-
max?: number,
|
|
328
|
-
step?: number,
|
|
329
|
-
} = {}) => new Validator<number>('number', (val, options, path) => {
|
|
330
|
-
|
|
331
|
-
// Tente conversion chaine en nombre
|
|
332
|
-
if (typeof val === 'string')
|
|
333
|
-
val = withDecimals ? parseFloat(val) : parseInt(val);
|
|
334
|
-
|
|
335
|
-
if (opts.min === undefined)
|
|
336
|
-
opts.min = 0;
|
|
337
|
-
|
|
338
|
-
// Type de donnée
|
|
339
|
-
if (Number.isNaN(val) || typeof val !== 'number') {
|
|
340
|
-
if (options?.autoCorrect)
|
|
341
|
-
val = opts.min;
|
|
342
|
-
else
|
|
343
|
-
throw new InputError("This value must be a number.");
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Minimum
|
|
347
|
-
if (val < opts.min)
|
|
348
|
-
if (options?.autoCorrect)
|
|
349
|
-
val = opts.min;
|
|
350
|
-
else
|
|
351
|
-
throw new InputError(`Must be at least ` + opts.min);
|
|
352
|
-
|
|
353
|
-
// Maximum
|
|
354
|
-
if (opts.max !== undefined && val > opts.max)
|
|
355
|
-
if (options?.autoCorrect)
|
|
356
|
-
val = opts.max;
|
|
357
|
-
else
|
|
358
|
-
throw new InputError(`Must be up to ` + opts.max);
|
|
359
|
-
|
|
360
|
-
return val;
|
|
361
|
-
}, {
|
|
362
|
-
// Force une valeur par défaut si requis
|
|
363
|
-
defaut: opts.opt ? undefined : (opts.min || 0),
|
|
364
|
-
...opts,
|
|
365
|
-
})
|
|
366
|
-
|
|
367
|
-
public int = this.number(false)
|
|
368
|
-
|
|
369
|
-
public float = this.number(true)
|
|
370
|
-
|
|
371
|
-
public bool = (opts: TValidatorOptions<boolean> & {} = {}) =>
|
|
372
|
-
new Validator<boolean>('bool', (val, options, path) => {
|
|
373
|
-
|
|
374
|
-
if (typeof val !== 'boolean' && !['true', 'false'].includes(val))
|
|
375
|
-
throw new InputError("This value must be a boolean.");
|
|
376
|
-
|
|
377
|
-
val = !!val;
|
|
378
|
-
|
|
379
|
-
return val;
|
|
380
|
-
}, {
|
|
381
|
-
defaut: false,
|
|
382
|
-
...opts
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
/*----------------------------------
|
|
386
|
-
- AUTRES
|
|
387
|
-
----------------------------------*/
|
|
388
|
-
public date = (opts: TValidatorOptions<Date> & {
|
|
389
|
-
|
|
390
|
-
} = {}) => new Validator<Date>('date', (val, options, path) => {
|
|
391
|
-
|
|
392
|
-
const chaine = typeof val == 'string';
|
|
393
|
-
|
|
394
|
-
// Chaine = format iso
|
|
395
|
-
if (chaine) {
|
|
396
|
-
|
|
397
|
-
if (!isISO8601(val))
|
|
398
|
-
throw new InputError("This value must be a date.");
|
|
399
|
-
|
|
400
|
-
val = toDate(val);
|
|
401
|
-
|
|
402
|
-
} else if (!(val instanceof Date))
|
|
403
|
-
throw new InputError("This value must be a date.");
|
|
404
|
-
|
|
405
|
-
return val;
|
|
406
|
-
|
|
407
|
-
}, {
|
|
408
|
-
//defaut: new Date,
|
|
409
|
-
...opts,
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
public richText(opts: TValidatorOptions<string> & TRichTextValidatorOptions = {}) {
|
|
413
|
-
return new Validator<string>('richText', (val, options, path) => {
|
|
414
|
-
|
|
415
|
-
// We get a stringified json as input since the editor workds with JSON string
|
|
416
|
-
try {
|
|
417
|
-
val = JSON.parse(val);
|
|
418
|
-
} catch (error) {
|
|
419
|
-
console.error("Failed to parse rich text json:", error, val);
|
|
420
|
-
throw new InputError("Invalid rich text format.");
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Check that the root exists and has a valid type
|
|
424
|
-
if (!val || typeof val !== 'object' || typeof val.root !== 'object' || val.root.type !== 'root')
|
|
425
|
-
throw new InputError("Invalid rich text value (1).");
|
|
426
|
-
|
|
427
|
-
// Check if root has children array
|
|
428
|
-
if (!Array.isArray(val.root.children))
|
|
429
|
-
throw new InputError("Invalid rich text value (2).");
|
|
430
|
-
|
|
431
|
-
// Validate each child node in root
|
|
432
|
-
for (const child of val.root.children) {
|
|
433
|
-
validateLexicalNode(child, opts);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return val;
|
|
437
|
-
|
|
438
|
-
}, {
|
|
439
|
-
//defaut: new Date,
|
|
440
|
-
...opts,
|
|
441
|
-
})
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/*----------------------------------
|
|
445
|
-
- FICHIER
|
|
446
|
-
----------------------------------*/
|
|
447
|
-
public file = ({ type, taille, ...opts }: TFileValidator & {
|
|
448
|
-
|
|
449
|
-
} = {}) => new Validator<File>('file', (val, options, path) => {
|
|
450
|
-
|
|
451
|
-
// Chaine = url ancien fichier = exclusion de la valeur pour conserver l'ancien fichier
|
|
452
|
-
// NOTE: Si la valeur est présente mais undefined, alors on supprimera le fichier
|
|
453
|
-
if (typeof val === 'string')
|
|
454
|
-
return EXCLUDE_VALUE;
|
|
455
|
-
|
|
456
|
-
if (!(val instanceof File))
|
|
457
|
-
throw new InputError(`Must be a File (${typeof val} received)`);
|
|
458
|
-
|
|
459
|
-
// MIME
|
|
460
|
-
if (type !== undefined) {
|
|
461
|
-
|
|
462
|
-
const mimeMatch = type.some( t => t === val.type || val.type.startsWith(t + '/') );
|
|
463
|
-
if (!mimeMatch)
|
|
464
|
-
throw new InputError('Only the following formats are allowed: ' + type.join(', ') + '. The file you gave is ' + val.type + '.');
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Taille
|
|
469
|
-
if (taille) {
|
|
470
|
-
const tailleFichier = val.size / 1024 / 1024; // Mo
|
|
471
|
-
if (tailleFichier > taille)
|
|
472
|
-
throw new InputError(`Le fichier ne doit pas faire plus de ${taille} Mo (taille reçue: ${tailleFichier} Mo)`);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return val;
|
|
476
|
-
|
|
477
|
-
}, {
|
|
478
|
-
//defaut: new Date,
|
|
479
|
-
...opts,
|
|
480
|
-
})
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const validators = new SchemaValidators();
|
|
484
|
-
|
|
485
|
-
export default validators;
|