@travetto/schema 5.0.12 → 5.0.14

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 ArcSine Technologies
3
+ Copyright (c) 2020 ArcSine Technologies
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -63,28 +63,28 @@ User:
63
63
 
64
64
  ### Fields
65
65
  This schema provides a powerful base for data binding and validation at runtime. Additionally there may be types that cannot be detected, or some information that the programmer would like to override. Below are the supported field decorators:
66
- * [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L30) defines a field that will be serialized.
67
- * [@Required](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L70) defines a that field should be required
68
- * [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L77) defines the allowable values that a field can have
69
- * [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L98) defines a regular expression that the field value should match
70
- * [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L106) enforces min length of a string
71
- * [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L116) enforces max length of a string
72
- * [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L106) enforces min value for a date or a number
73
- * [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L116) enforces max value for a date or a number
74
- * [@Email](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L143) ensures string field matches basic email regex
75
- * [@Telephone](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L150) ensures string field matches basic telephone regex
76
- * [@Url](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L157) ensures string field matches basic url regex
77
- * [@Ignore](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L196) exclude from auto schema registration
78
- * [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L171) ensures number passed in is only a whole number
79
- * [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L177) ensures number passed in allows fractional values
80
- * [@Currency](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L189) provides support for standard currency
81
- * [@Text](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L85) indicates that a field is expecting natural language input, not just discrete values
82
- * [@LongText](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L90) same as text, but expects longer form content
83
- * [@Readonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L57) defines a that field should not be bindable external to the class
84
- * [@Writeonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L51) defines a that field should not be exported in serialization, but that it can be bound to
85
- * [@Secret](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L63) marks a field as being sensitive. This is used by certain logging activities to ensure sensitive information is not logged out.
86
- * [@Specifier](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L205) attributes additional specifiers to a field, allowing for more specification beyond just the field's type.
87
- * [@SubTypeField](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L211) allows for promoting a given field as the owner of the sub type discriminator (defaults to `type`).
66
+ * [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L25) defines a field that will be serialized.
67
+ * [@Required](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L65) defines a that field should be required
68
+ * [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L72) defines the allowable values that a field can have
69
+ * [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L93) defines a regular expression that the field value should match
70
+ * [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L101) enforces min length of a string
71
+ * [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L111) enforces max length of a string
72
+ * [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L101) enforces min value for a date or a number
73
+ * [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L111) enforces max value for a date or a number
74
+ * [@Email](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L138) ensures string field matches basic email regex
75
+ * [@Telephone](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L145) ensures string field matches basic telephone regex
76
+ * [@Url](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L152) ensures string field matches basic url regex
77
+ * [@Ignore](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L191) exclude from auto schema registration
78
+ * [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L166) ensures number passed in is only a whole number
79
+ * [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L172) ensures number passed in allows fractional values
80
+ * [@Currency](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L184) provides support for standard currency
81
+ * [@Text](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L80) indicates that a field is expecting natural language input, not just discrete values
82
+ * [@LongText](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L85) same as text, but expects longer form content
83
+ * [@Readonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L52) defines a that field should not be bindable external to the class
84
+ * [@Writeonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L46) defines a that field should not be exported in serialization, but that it can be bound to
85
+ * [@Secret](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L58) marks a field as being sensitive. This is used by certain logging activities to ensure sensitive information is not logged out.
86
+ * [@Specifier](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L200) attributes additional specifiers to a field, allowing for more specification beyond just the field's type.
87
+ * [@SubTypeField](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L206) allows for promoting a given field as the owner of the sub type discriminator (defaults to `type`).
88
88
  Additionally, schemas can be nested to form more complex data structures that are able to bound and validated.
89
89
 
90
90
  Just like the class, all fields can be defined with
@@ -102,7 +102,7 @@ import { Match, Min } from '@travetto/schema';
102
102
  const NAME_REGEX = /[A-Z][a-z]+(\s+[A-Z][a-z]+)*/;
103
103
 
104
104
  export class ParamUsage {
105
- main(@Match(NAME_REGEX).Param name: string, @Min(20).Param age?: number) {
105
+ main(@Match(NAME_REGEX) name: string, @Min(20) age?: number) {
106
106
  console.log('Valid name and age!', { name, age });
107
107
  }
108
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/schema",
3
- "version": "5.0.12",
3
+ "version": "5.0.14",
4
4
  "description": "Data type registry for runtime validation, reflection and binding.",
5
5
  "keywords": [
6
6
  "schema",
@@ -27,10 +27,10 @@
27
27
  "directory": "module/schema"
28
28
  },
29
29
  "dependencies": {
30
- "@travetto/registry": "^5.0.12"
30
+ "@travetto/registry": "^5.0.14"
31
31
  },
32
32
  "peerDependencies": {
33
- "@travetto/transformer": "^5.0.10"
33
+ "@travetto/transformer": "^5.0.11"
34
34
  },
35
35
  "peerDependenciesMeta": {
36
36
  "@travetto/transformer": {
package/src/bind-util.ts CHANGED
@@ -220,7 +220,7 @@ export class BindUtil {
220
220
  // Ensure its an array
221
221
  if (!Array.isArray(v) && field.array) {
222
222
  if (typeof v === 'string' && v.includes(',')) {
223
- v = v.split(/\s*,\s*/);
223
+ v = v.split(/,/).map(x => x.trim());
224
224
  } else {
225
225
  v = [v];
226
226
  }
package/src/data.ts CHANGED
@@ -91,6 +91,9 @@ export class DataUtil {
91
91
  const retObj: Record<string, unknown> = castTo(ret);
92
92
 
93
93
  for (const key of Object.keys(bObj)) {
94
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
95
+ continue;
96
+ }
94
97
  retObj[key] = this.#deepAssignRaw(retObj[key], bObj[key], mode);
95
98
  }
96
99
  }
@@ -1,24 +1,19 @@
1
- import { Any, castTo, ClassInstance } from '@travetto/runtime';
1
+ import { Any, ClassInstance } from '@travetto/runtime';
2
2
 
3
3
  import { SchemaRegistry } from '../service/registry';
4
4
  import { CommonRegExp } from '../validate/regexp';
5
5
  import { ClassList, FieldConfig } from '../service/types';
6
6
 
7
- type PropType<V> = (<T extends Partial<Record<K, V>>, K extends string>(t: T, k: K, idx?: TypedPropertyDescriptor<Any> | number) => void) & {
8
- Param: (t: unknown, k: string, idx: number) => void;
9
- };
7
+ type PropType<V> = (<T extends Partial<Record<K, V | Function>>, K extends string>(t: T, k: K, idx?: TypedPropertyDescriptor<Any> | number) => void);
10
8
 
11
9
  function prop<V>(obj: Partial<FieldConfig>): PropType<V> {
12
- const fn = (t: ClassInstance, k: string, idx?: number | TypedPropertyDescriptor<Any>): void => {
10
+ return (t: ClassInstance, k: string, idx?: number | TypedPropertyDescriptor<Any>): void => {
13
11
  if (idx !== undefined && typeof idx === 'number') {
14
12
  SchemaRegistry.registerPendingParamFacet(t.constructor, k, idx, obj);
15
13
  } else {
16
14
  SchemaRegistry.registerPendingFieldFacet(t.constructor, k, obj);
17
15
  }
18
16
  };
19
- fn.Param = fn;
20
-
21
- return castTo(fn);
22
17
  }
23
18
 
24
19
  /**
@@ -1,9 +1,9 @@
1
- import { castTo, Class, DeepPartial } from '@travetto/runtime';
1
+ import { Any, castTo, Class, ClassInstance, DeepPartial } from '@travetto/runtime';
2
2
 
3
3
  import { BindUtil } from '../bind-util';
4
4
  import { SchemaRegistry } from '../service/registry';
5
5
  import { ClassConfig, ViewFieldsConfig } from '../service/types';
6
- import { ValidatorFn } from '../validate/types';
6
+ import { MethodValidatorFn, ValidatorFn } from '../validate/types';
7
7
 
8
8
  /**
9
9
  * Register a class as a Schema
@@ -26,12 +26,23 @@ export function Schema(cfg?: Partial<Pick<ClassConfig, 'subTypeName' | 'subTypeF
26
26
  *
27
27
  * @param fn The validator function
28
28
  */
29
- export function Validator<T>(fn: ValidatorFn<T, string>) {
30
- return (target: Class<T>): void => {
29
+ export const Validator = <T>(fn: ValidatorFn<T, string>) =>
30
+ (target: Class<T>, k?: string): void => {
31
31
  SchemaRegistry.getOrCreatePending(target).validators!.push(castTo(fn));
32
32
  };
33
+
34
+ /**
35
+ * Add a custom validator for a given method
36
+ *
37
+ * @param fn The validator function
38
+ */
39
+ export function MethodValidator<T extends (...args: Any[]) => Any>(fn: MethodValidatorFn<Parameters<T>>) {
40
+ return (target: ClassInstance, k: string, prop: TypedPropertyDescriptor<T>): void => {
41
+ SchemaRegistry.registerPendingMethod(target.constructor, k).validators!.push(castTo(fn));
42
+ };
33
43
  }
34
44
 
45
+
35
46
  /**
36
47
  * Register a specific view for a class
37
48
  * @param name The name of the view
@@ -1,9 +1,10 @@
1
- import { Class, AppError, describeFunction, castTo, classConstruct, asFull, castKey } from '@travetto/runtime';
1
+ import { Class, AppError, describeFunction, castTo, classConstruct, asFull, castKey, Any } from '@travetto/runtime';
2
2
  import { MetadataRegistry, RootRegistry, ChangeEvent } from '@travetto/registry';
3
3
 
4
- import { ClassList, FieldConfig, ClassConfig, SchemaConfig, ViewFieldsConfig, ViewConfig } from './types';
4
+ import { ClassList, FieldConfig, ClassConfig, SchemaConfig, ViewFieldsConfig, ViewConfig, SchemaMethodConfig } from './types';
5
5
  import { SchemaChangeListener } from './changes';
6
6
  import { AllViewⲐ } from '../internal/types';
7
+ import { MethodValidatorFn } from '../validate/types';
7
8
 
8
9
  const classToSubTypeName = (cls: Class): string => cls.name
9
10
  .replace(/([A-Z])([A-Z][a-z])/g, (all, l, r) => `${l}_${r.toLowerCase()}`)
@@ -218,7 +219,16 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
218
219
  * @param method
219
220
  */
220
221
  getMethodSchema<T>(cls: Class<T>, method: string): FieldConfig[] {
221
- return (this.get(cls)?.methods?.[method] ?? []).filter(x => !!x).sort((a, b) => a.index! - b.index!);
222
+ return (this.get(cls)?.methods?.[method] ?? {}).fields?.filter(x => !!x).sort((a, b) => a.index! - b.index!) ?? [];
223
+ }
224
+
225
+ /**
226
+ * Get method validators
227
+ * @param cls
228
+ * @param method
229
+ */
230
+ getMethodValidators<T>(cls: Class<T>, method: string): MethodValidatorFn<unknown[]>[] {
231
+ return (this.get(cls)?.methods?.[method] ?? {}).validators ?? [];
222
232
  }
223
233
 
224
234
  /**
@@ -235,6 +245,16 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
235
245
  this.#pendingViews.get(target)!.set(view, generalConfig);
236
246
  }
237
247
 
248
+ /**
249
+ * Register pending method, and establish a method config
250
+ * @param target
251
+ * @param method
252
+ */
253
+ registerPendingMethod(target: Class, method: string): SchemaMethodConfig {
254
+ const methods = this.getOrCreatePending(target)!.methods!;
255
+ return (methods[method] ??= { fields: [], validators: [] });
256
+ }
257
+
238
258
  /**
239
259
  * Register a partial config for a pending method param
240
260
  * @param target The class to target
@@ -243,8 +263,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
243
263
  * @param config The config to register
244
264
  */
245
265
  registerPendingParamFacet(target: Class, method: string, idx: number, config: Partial<FieldConfig>): Class {
246
- const methods = this.getOrCreatePending(target)!.methods!;
247
- const params = (methods[method] ??= []);
266
+ const params = this.registerPendingMethod(target, method).fields;
248
267
  if (config.name === '') {
249
268
  delete config.name;
250
269
  }
@@ -1,7 +1,7 @@
1
1
  import { Any, Class, Primitive } from '@travetto/runtime';
2
2
 
3
3
  import { AllViewⲐ } from '../internal/types';
4
- import { ValidatorFn } from '../validate/types';
4
+ import { MethodValidatorFn, ValidatorFn } from '../validate/types';
5
5
 
6
6
  export type ClassList = Class | [Class];
7
7
 
@@ -26,6 +26,14 @@ export interface DescribableConfig {
26
26
  examples?: string[];
27
27
  }
28
28
 
29
+ /**
30
+ * Basic structure for a method configuration
31
+ */
32
+ export interface SchemaMethodConfig {
33
+ fields: FieldConfig[];
34
+ validators: MethodValidatorFn<unknown[]>[];
35
+ }
36
+
29
37
  /**
30
38
  * Schema configuration
31
39
  */
@@ -85,7 +93,7 @@ export interface ClassConfig extends DescribableConfig {
85
93
  /**
86
94
  * Method parameter configs
87
95
  */
88
- methods: Record<string, FieldConfig[]>;
96
+ methods: Record<string, SchemaMethodConfig>;
89
97
  }
90
98
 
91
99
  /**
@@ -14,7 +14,7 @@ export const CommonRegExp = {
14
14
  email: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
15
15
  telephone: /^(\+?\d{1,3}(\s*-?\s*|\s+))?((\(\d{3}\))|\d{3})(\s*|-|[.])(\d{3})(\s*|-|[.])(\d{4})(\s+(x|ext[.]?)\s*\d+)?$/,
16
16
  url: /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/,
17
- simpleName: /^([a-zA-Z\u0080-\u024F]*(?:. |-| |'))*[a-zA-Z\u0080-\u024F]+$/,
17
+ simpleName: /^([a-zA-Z\u0080-\u024F]{0,100}(?:. |-| |')){0,10}[a-zA-Z\u0080-\u024F]+$/,
18
18
  postalCode: /^\d{5}(?:[-\s]\d{4})?$/
19
19
  };
20
20
 
@@ -65,7 +65,14 @@ export interface ValidationResult {
65
65
  n?: number | Date;
66
66
  }
67
67
 
68
+ type OrPromise<T> = T | Promise<T>;
69
+
68
70
  /**
69
71
  * Validator function
70
72
  */
71
- export type ValidatorFn<T, U> = (value: T, parent?: U) => ValidationError | undefined | Promise<ValidationError | undefined>;
73
+ export type ValidatorFn<T, U> = (value: T, parent?: U) => OrPromise<ValidationError | ValidationError[] | undefined>;
74
+
75
+ /**
76
+ * Method Validator function
77
+ */
78
+ export type MethodValidatorFn<T extends unknown[]> = (...value: T) => OrPromise<ValidationError | ValidationError[] | undefined>;
@@ -233,7 +233,11 @@ export class SchemaValidator {
233
233
  try {
234
234
  const res = await fn(o, view);
235
235
  if (res) {
236
- errors.push(res);
236
+ if (Array.isArray(res)) {
237
+ errors.push(...res);
238
+ } else {
239
+ errors.push(res);
240
+ }
237
241
  }
238
242
  } catch (err: unknown) {
239
243
  if (isValidationError(err)) {
@@ -324,6 +328,16 @@ export class SchemaValidator {
324
328
  return x;
325
329
  }));
326
330
  }
331
+ for (const validator of SchemaRegistry.getMethodValidators(cls, method)) {
332
+ const res = await validator(...params);
333
+ if (res) {
334
+ if (Array.isArray(res)) {
335
+ errors.push(...res);
336
+ } else {
337
+ errors.push(res);
338
+ }
339
+ }
340
+ }
327
341
  if (errors.length) {
328
342
  throw new ValidationResultError(errors);
329
343
  }