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.
@@ -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-7",
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
- export function Route(options: Omit<TControllerDefinition, 'controller'> = {}) {
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 (options.path === undefined)
65
- options.path = target.constructor.name + '/' + propertyKey;
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/' + options.path,
102
+ path: '/api/' + path,
76
103
  controller: originalMethod,
77
- schema: options.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 = Config,
39
- TApplication extends Application = 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<R[]> {
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<R | null> {
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
- >(...args: [D, S, R]) {
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: (builder: (file: zod.ZodType<File>) => any) => schema.custom(val => {
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
- // Default file validation
57
- const fileInstance = zod.file();
56
+ return zod.file();
57
+ },
58
58
 
59
- return builder(fileInstance).parse(val);
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: Application,
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
- import Schema, { TSchemaFields, TValidatedData } from '@server/services/router/request/validation/schema';
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 class RequestValidator extends ServerSchemaValidator implements RequestService {
29
-
30
- public constructor(
31
- public request: ServerRequest<Router>,
32
- public config: TConfig,
33
- public router = request.router,
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
- public validate<TSchemaFieldsA extends TSchemaFields>(
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 instanceof Schema ? fields : new 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 RequestValidator, { TConfig } from '../request';
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 ): RequestValidator {
26
- return new RequestValidator( request, this.config );
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"|"times"|"brands/whatsapp"|"info-circle"|"check-circle"|"exclamation-circle"|"times-circle"|"search"|"check"|"angle-down"|"arrow-left"|"arrow-right"|"meh-rolling-eyes"|"eye"|"trash"|"unlink"|"pen"|"link"|"file"|"bold"|"italic"|"underline"|"strikethrough"|"subscript"|"superscript"|"code"|"plus"|"font"|"empty-set"|"plus-circle"|"horizontal-rule"|"page-break"|"image"|"table"|"poll"|"columns"|"sticky-note"|"caret-right"|"align-left"|"align-center"|"align-right"|"align-justify"|"indent"|"outdent"|"list-ul"|"check-square"|"h1"|"h2"|"h3"|"h4"|"list-ol"|"paragraph"|"quote-left"
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;