@lucania/schema 2.0.2 → 2.0.3
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/README.md +67 -17
- package/build/index.js +139 -45
- package/build/schema/AnySchema.d.ts +3 -2
- package/build/schema/ArraySchema.d.ts +2 -1
- package/build/schema/BaseSchema.d.ts +13 -3
- package/build/schema/BooleanSchema.d.ts +3 -2
- package/build/schema/DateSchema.d.ts +3 -2
- package/build/schema/DynamicObjectSchema.d.ts +2 -1
- package/build/schema/EnumerationSchema.d.ts +2 -1
- package/build/schema/NumberSchema.d.ts +3 -2
- package/build/schema/ObjectSchema.d.ts +2 -1
- package/build/schema/OrSetSchema.d.ts +2 -1
- package/build/schema/StringSchema.d.ts +3 -2
- package/build/typing/toolbox.d.ts +17 -2
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
# Schema
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
|
2
|
+
|
|
3
|
+
| TECHNICAL | |
|
|
4
|
+
| :-------: | -------------------------------------------------------------------------- |
|
|
5
|
+
| _noun_ | _a representation of a plan or theory in the form of an outline or model._ |
|
|
5
6
|
|
|
6
7
|
This library allows you to specify the schema for your data, and get compile time typings _(help from your IDE)_ and runtime validation _(throw errors if your data isn't in the right format)_.
|
|
7
8
|
|
|
8
9
|
With this library, you create objects that serve as a blueprint for what your data should look like. These objects are called `Schema`s.
|
|
9
10
|
|
|
10
|
-
|_Schema Type_|Description|Usage|Example Data|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
| _Schema Type_ | Description | Usage | Example Data |
|
|
12
|
+
| :-------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `StringSchema` | Used to represent a string. | `$.String(required?: boolean, default?: StringSource)` | `"Moaaz"`, `"The cow jumped over the moon."` |
|
|
14
|
+
| `NumberSchema` | Used to represent a number. | `$.Number(required?: boolean, default?: NumberSource)` | `-30`, `0`, `10` |
|
|
15
|
+
| `BooleanSchema` | Used to represent a boolean. | `$.Boolean(required?: boolean, default?: BooleanSource)` | `true`, `false` |
|
|
16
|
+
| `DateSchema` | Used to represent a Date. | `$.Date(required?: boolean, default?: DateSource)` | `new Date(2000, 9, 29)`, `new Date("1998-09-04")` |
|
|
17
|
+
| `ObjectSchema` | Used to represent an Object. | `$.Object(subschema: { <key>: Schema }, required?: boolean, default?: ObjectSource)` | `<depends on subschema>`, `{ name: "Jeremy", age: 23 }`, `{ make: "Toyota", model: "Sienna", year: 2011 }` |
|
|
18
|
+
| `ArraySchema` | Used to represent an array | `$.Array(subschema: Schema, required?: boolean, default?: ArraySource)` | `<depends on subschema>`, `[]`, `[1, 2, 3]`, `["Ben", "Amit", "Dean"]` |
|
|
19
|
+
| `EnumerationSchema` | Used to represent an enumeration value, otherwise known as a set of possible strings values. | `$.Enumeration(members: Members, required?: boolean, default?: EnumerationSource)` | `<depends on members>`, `"MAGENTA"`, `"CA"`, `"male"` |
|
|
20
|
+
| `OrSetSchema` | Used to represent a value that should be validated by one of many possible schemas. This is used to represent a value that is allowed to be multiple types. | `$.OrSet(members: Members, required?: boolean, default?: OrSetSource)` | `<depends on members>` |
|
|
21
|
+
| `DynamicObjectSchema` | Used to represent an object that could have many different keys. | `$.DynamicObject(subschema: Schema, required?: boolean, default?: DynamicObjectSource)` | `{ <any key>: <depends on subschema> }` |
|
|
22
|
+
| `AnySchema` | Used to represent a value that could be any type. | `$.Any(required?: boolean, default?: AnySource)` | `1`, `"Omar"`, `false`, `{}`, `[]`, `<any type>` |
|
|
22
23
|
|
|
23
24
|
## Validation
|
|
25
|
+
|
|
24
26
|
The simplest possible validation:
|
|
27
|
+
|
|
25
28
|
```typescript
|
|
26
29
|
import { $ } from "@lucania/schema";
|
|
27
30
|
|
|
@@ -38,7 +41,9 @@ const dataOfNumberType = numberSchema.validate(dataOfUnknownType);
|
|
|
38
41
|
With this collection of Schemas, we can now start building more complex data structures. There are multiple way to represent different scenarios. This is done with the following constructs.
|
|
39
42
|
|
|
40
43
|
## Creating Hierarchical Schema
|
|
44
|
+
|
|
41
45
|
These schemas can come together to allow you to define a blueprint for more complex data structures.
|
|
46
|
+
|
|
42
47
|
```typescript
|
|
43
48
|
import { $ } from "@lucania/schema";
|
|
44
49
|
|
|
@@ -58,7 +63,6 @@ const PersonSchema = $.Object({
|
|
|
58
63
|
age: $.Number(true).min(16, "You must be at least 16 years old!").max(100, "You must be at most 100 years old!"),
|
|
59
64
|
weapon: WeaponSchema
|
|
60
65
|
});
|
|
61
|
-
|
|
62
66
|
```
|
|
63
67
|
|
|
64
68
|
## Using Schema
|
|
@@ -71,6 +75,52 @@ import fs from "fs";
|
|
|
71
75
|
const personData = JSON.parse(fs.readFileSync("person.json", "utf8"));
|
|
72
76
|
const person = PersonSchema.validate(personData);
|
|
73
77
|
```
|
|
78
|
+
|
|
74
79
|
`person` now has the following compile-time type annotation based on [`PersonSchema`](#creating-hierarchical-schema).
|
|
75
80
|
|
|
76
|
-
At runtime, the object parsed from the `person.json` file will be validated to match this generated typing. If it does not match, a `Schema.ValidationError` will be thrown.
|
|
81
|
+
At runtime, the object parsed from the `person.json` file will be validated to match this generated typing. If it does not match, a `Schema.ValidationError` will be thrown.
|
|
82
|
+
|
|
83
|
+
## Additional Validation Passes
|
|
84
|
+
|
|
85
|
+
Sometimes it's necessary to validate not only a type, but also specifics about the value. I.E. a value is a `$.Number` _and_ is between 0 and 100. You can add additional type-specific validation passes (I.E. `.clamp(...)`) or custom ones (`.custom(...)`):
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const numberData: any = ...;
|
|
89
|
+
|
|
90
|
+
const ScoreSchema = $.Number().clamp(0, 100).custom((data, pass) => {
|
|
91
|
+
pass.assert(data % 2 === 0, "Your score must be even!");
|
|
92
|
+
return data;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// The above custom() validation pass can alternately be defined using the .ensure() shorthand.
|
|
96
|
+
|
|
97
|
+
const ScoreSchema = $.Number().clamp(0, 100).ensure((data, pass) => data % 2 === 0, "Your score must be even!");
|
|
98
|
+
|
|
99
|
+
const number: number = ScoreSchema.validate(numberData);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Custom Schema Types
|
|
103
|
+
|
|
104
|
+
You can define your own Schema types by creating a subclass of `BaseSchema`. But first, `BaseSchema` relies on 4 generic parameters for TypeScript's type checker to understand your Schema at compile time. These parameters are as follows:
|
|
105
|
+
|
|
106
|
+
- Source - Represents the typing for a source input to your schema.
|
|
107
|
+
- Model - Represents the typing for an output value from your schema.
|
|
108
|
+
- Required - Represents the presence optionality of your schema's Source/Model. (Is `undefined` a valid Source and Model)
|
|
109
|
+
- Default - Represents the typing for default values.
|
|
110
|
+
|
|
111
|
+
Typically, when developing your own Schema types, you'll hardcode Source and Model for your specific Schema's requirements, but pass over `Required` and `Default` generics to `BaseSchema` to allow them to be inferred from your Schema definitions.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
export type CowModel = { name: string, numberOfSpots: number };
|
|
115
|
+
|
|
116
|
+
export type CowSource = string | CowModel;
|
|
117
|
+
|
|
118
|
+
export class CowSchema<
|
|
119
|
+
Required extends boolean, // Taking in "Required" generic.
|
|
120
|
+
Default extends DefaultValue<CowSource> // Taking in "Default" generic.
|
|
121
|
+
|
|
122
|
+
// Handing over hardcoded Source, Model and CowSchema generics to BaseSchema.
|
|
123
|
+
> extends BaseSchema<CowSource, CowModel, Required, Default> { ... }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
These declarations are enough to have the TypeScript type checker understand the compile-time typing for `CowSchema`. Next, lets implement our runtime checks.
|
package/build/index.js
CHANGED
|
@@ -51,12 +51,10 @@
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
class BaseSchema {
|
|
54
|
-
type;
|
|
55
54
|
_required;
|
|
56
55
|
_default;
|
|
57
56
|
_additionalValidationPasses;
|
|
58
|
-
constructor(
|
|
59
|
-
this.type = type;
|
|
57
|
+
constructor(required, defaultValue, additionalValidationPasses) {
|
|
60
58
|
this._required = required;
|
|
61
59
|
this._default = defaultValue;
|
|
62
60
|
this._additionalValidationPasses = additionalValidationPasses === undefined ? {
|
|
@@ -82,16 +80,18 @@
|
|
|
82
80
|
if (BaseSchema.getType(result) !== this.type) {
|
|
83
81
|
result = this.convert(result, pass);
|
|
84
82
|
}
|
|
83
|
+
result = this._validate(result, pass);
|
|
85
84
|
result = this._executeAdditionalValidator(result, pass, "afterConversion");
|
|
86
85
|
}
|
|
87
86
|
else {
|
|
88
|
-
result = this._executeAdditionalValidator(result, pass, "afterConversion");
|
|
89
87
|
if (this._required) {
|
|
90
88
|
throw pass.getError(pass.path.length > 0 ? `Missing required value at "${pass.path.join(".")}".` : "Missing required value.");
|
|
91
89
|
}
|
|
92
90
|
else {
|
|
93
91
|
result = undefined;
|
|
94
92
|
}
|
|
93
|
+
result = this._validate(result, pass);
|
|
94
|
+
result = this._executeAdditionalValidator(result, pass, "afterConversion");
|
|
95
95
|
}
|
|
96
96
|
if (this._required || result !== undefined) {
|
|
97
97
|
result = this._executeAdditionalValidator(result, pass, "afterAll");
|
|
@@ -102,6 +102,15 @@
|
|
|
102
102
|
this._additionalValidationPasses[type].push(additionalValidator);
|
|
103
103
|
return this;
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Ensures a condition is true during the associated schema's validation pass.
|
|
107
|
+
*
|
|
108
|
+
* @note See {@link EnsureValidator EnsureValidator}.
|
|
109
|
+
*
|
|
110
|
+
* @param ensureValidator A validator to ensure a condition is passed.
|
|
111
|
+
* @param message The message to use for a thrown {@link ValidationError ValidationError} if ensureValidator fails.
|
|
112
|
+
* @returns The schema instance for chaining.
|
|
113
|
+
*/
|
|
105
114
|
ensure(ensureValidator, message) {
|
|
106
115
|
this.custom((value, pass) => {
|
|
107
116
|
pass.assert(ensureValidator(value, pass), message === undefined ? `Failed to ensure value.` : message);
|
|
@@ -132,6 +141,13 @@
|
|
|
132
141
|
_getValueDisplay(value) {
|
|
133
142
|
return String(value);
|
|
134
143
|
}
|
|
144
|
+
getJsonSchema() {
|
|
145
|
+
console.warn(`"getJsonSchema" is not implemented in the "${this.constructor.name}" schema class!`);
|
|
146
|
+
return {
|
|
147
|
+
type: "unknown",
|
|
148
|
+
description: `"getJsonSchema" is not implemented in the "${this.constructor.name}" schema class!`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
135
151
|
_getJsonSchemaDescription() {
|
|
136
152
|
const pass = new ValidationPass(this, this._default);
|
|
137
153
|
const descriptionPieces = [];
|
|
@@ -198,8 +214,9 @@
|
|
|
198
214
|
}
|
|
199
215
|
|
|
200
216
|
class AnySchema extends BaseSchema {
|
|
201
|
-
|
|
202
|
-
|
|
217
|
+
get type() { return "any"; }
|
|
218
|
+
_validate(source, pass) {
|
|
219
|
+
return source;
|
|
203
220
|
}
|
|
204
221
|
convert(value, pass) {
|
|
205
222
|
return value;
|
|
@@ -212,16 +229,28 @@
|
|
|
212
229
|
class ArraySchema extends BaseSchema {
|
|
213
230
|
subschema;
|
|
214
231
|
constructor(subschema, required, defaultValue) {
|
|
215
|
-
super(
|
|
232
|
+
super(required, defaultValue);
|
|
216
233
|
this.subschema = subschema;
|
|
217
234
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
235
|
+
get type() { return "array"; }
|
|
236
|
+
// public validate(source: SourceValue<ArraySource<Subschema>, Required, Default>, pass?: ValidationPass):
|
|
237
|
+
// ModelValue<ArraySource<Subschema>, ArrayModel<Subschema>, Required, Default> {
|
|
238
|
+
// pass = this._ensurePass(source, pass);
|
|
239
|
+
// const result: any = super.validate(source, pass);
|
|
240
|
+
// if (result !== undefined) {
|
|
241
|
+
// for (const key in result) {
|
|
242
|
+
// const nestedValue = result[key];
|
|
243
|
+
// result[key] = this.subschema.validate(result[key], pass.next([...pass.path, key], this.subschema, nestedValue));
|
|
244
|
+
// }
|
|
245
|
+
// }
|
|
246
|
+
// return result;
|
|
247
|
+
// }
|
|
248
|
+
_validate(source, pass) {
|
|
249
|
+
const result = [];
|
|
250
|
+
if (source !== undefined) {
|
|
251
|
+
for (const key in source) {
|
|
252
|
+
const nestedValue = source[key];
|
|
253
|
+
result[key] = this.subschema.validate(source[key], pass.next([...pass.path, key], this.subschema, nestedValue));
|
|
225
254
|
}
|
|
226
255
|
}
|
|
227
256
|
return result;
|
|
@@ -247,8 +276,9 @@
|
|
|
247
276
|
}
|
|
248
277
|
|
|
249
278
|
class BooleanSchema extends BaseSchema {
|
|
250
|
-
|
|
251
|
-
|
|
279
|
+
get type() { return "boolean"; }
|
|
280
|
+
_validate(source, pass) {
|
|
281
|
+
return source;
|
|
252
282
|
}
|
|
253
283
|
convert(value, pass) {
|
|
254
284
|
if (typeof value === "number") {
|
|
@@ -279,8 +309,9 @@
|
|
|
279
309
|
|
|
280
310
|
const StandardDate = globalThis.Date;
|
|
281
311
|
class DateSchema extends BaseSchema {
|
|
282
|
-
|
|
283
|
-
|
|
312
|
+
get type() { return "Date"; }
|
|
313
|
+
_validate(source, pass) {
|
|
314
|
+
return source;
|
|
284
315
|
}
|
|
285
316
|
convert(value, pass) {
|
|
286
317
|
if (typeof value === "string") {
|
|
@@ -338,12 +369,24 @@
|
|
|
338
369
|
class DynamicObjectSchema extends BaseSchema {
|
|
339
370
|
subschema;
|
|
340
371
|
constructor(subschema, required, defaultValue) {
|
|
341
|
-
super(
|
|
372
|
+
super(required, defaultValue);
|
|
342
373
|
this.subschema = subschema;
|
|
343
374
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
375
|
+
get type() { return "object"; }
|
|
376
|
+
// public validate(source: SourceValue<DynamicObjectSource<Subschema>, Required, Default>, pass?: ValidationPass):
|
|
377
|
+
// ModelValue<DynamicObjectSource<Subschema>, DynamicObjectModel<Subschema>, Required, Default> {
|
|
378
|
+
// pass = this._ensurePass(source, pass);
|
|
379
|
+
// const result: any = super.validate(source, pass);
|
|
380
|
+
// if (result !== undefined) {
|
|
381
|
+
// for (const key in result) {
|
|
382
|
+
// const nestedValue = result[key];
|
|
383
|
+
// result[key] = this.subschema.validate(result[key], pass.next([...pass.path, key], this.subschema, nestedValue));
|
|
384
|
+
// }
|
|
385
|
+
// }
|
|
386
|
+
// return result;
|
|
387
|
+
// }
|
|
388
|
+
_validate(source, pass) {
|
|
389
|
+
const result = source;
|
|
347
390
|
if (result !== undefined) {
|
|
348
391
|
for (const key in result) {
|
|
349
392
|
const nestedValue = result[key];
|
|
@@ -372,12 +415,19 @@
|
|
|
372
415
|
class EnumerationSchema extends BaseSchema {
|
|
373
416
|
members;
|
|
374
417
|
constructor(members, required, defaultValue) {
|
|
375
|
-
super(
|
|
418
|
+
super(required, defaultValue);
|
|
376
419
|
this.members = members;
|
|
377
420
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
421
|
+
get type() { return "string"; }
|
|
422
|
+
// public validate(source: SourceValue<Members[number], Required, Default>, pass?: ValidationPass):
|
|
423
|
+
// ModelValue<Members[number], Members[number], Required, Default> {
|
|
424
|
+
// pass = this._ensurePass(source, pass);
|
|
425
|
+
// const result: any = super.validate(source, pass);
|
|
426
|
+
// pass.assert(this.members.includes(result), `"${result}" is not a valid enumeration value (Expected: ${this.members.join(", ")}).`);
|
|
427
|
+
// return result;
|
|
428
|
+
// }
|
|
429
|
+
_validate(source, pass) {
|
|
430
|
+
const result = source;
|
|
381
431
|
pass.assert(this.members.includes(result), `"${result}" is not a valid enumeration value (Expected: ${this.members.join(", ")}).`);
|
|
382
432
|
return result;
|
|
383
433
|
}
|
|
@@ -394,8 +444,9 @@
|
|
|
394
444
|
}
|
|
395
445
|
|
|
396
446
|
class NumberSchema extends BaseSchema {
|
|
397
|
-
|
|
398
|
-
|
|
447
|
+
get type() { return "number"; }
|
|
448
|
+
_validate(source, pass) {
|
|
449
|
+
return source;
|
|
399
450
|
}
|
|
400
451
|
convert(value, pass) {
|
|
401
452
|
if (typeof value === "bigint") {
|
|
@@ -449,22 +500,40 @@
|
|
|
449
500
|
class ObjectSchema extends BaseSchema {
|
|
450
501
|
subschema;
|
|
451
502
|
constructor(subschema, required, defaultValue) {
|
|
452
|
-
super(
|
|
503
|
+
super(required, defaultValue);
|
|
453
504
|
this.subschema = subschema;
|
|
454
505
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
506
|
+
get type() { return "object"; }
|
|
507
|
+
// public validate(source: SourceValue<ObjectSource<Subschema>, Required, Default>, pass?: ValidationPass):
|
|
508
|
+
// ModelValue<ObjectSource<Subschema>, ObjectModel<Subschema>, Required, Default> {
|
|
509
|
+
// pass = this._ensurePass(source, pass);
|
|
510
|
+
// const input: any = super.validate(source, pass);
|
|
511
|
+
// let output: any = input;
|
|
512
|
+
// if (typeof input === "object" && input !== null) {
|
|
513
|
+
// output = {};
|
|
514
|
+
// for (const key in this.subschema) {
|
|
515
|
+
// const nestedSchema = this.subschema[key];
|
|
516
|
+
// const nestedValue = input[key];
|
|
517
|
+
// output[key] = this.subschema[key].validate(input[key], pass.next([...pass.path, key], nestedSchema, nestedValue));
|
|
518
|
+
// }
|
|
519
|
+
// }
|
|
520
|
+
// return output;
|
|
521
|
+
// }
|
|
522
|
+
_validate(source, pass) {
|
|
523
|
+
const input = source;
|
|
524
|
+
let output = input;
|
|
525
|
+
if (typeof input === "object" && input !== null) {
|
|
526
|
+
output = {};
|
|
459
527
|
for (const key in this.subschema) {
|
|
460
528
|
const nestedSchema = this.subschema[key];
|
|
461
|
-
const nestedValue =
|
|
462
|
-
|
|
529
|
+
const nestedValue = input[key];
|
|
530
|
+
output[key] = this.subschema[key].validate(input[key], pass.next([...pass.path, key], nestedSchema, nestedValue));
|
|
463
531
|
}
|
|
464
532
|
}
|
|
465
|
-
return
|
|
533
|
+
return output;
|
|
466
534
|
}
|
|
467
535
|
convert(value, pass) {
|
|
536
|
+
pass.assert(typeof value === "object", `Unable to convert ${ObjectSchema.getType(value)} to object.`);
|
|
468
537
|
const model = {};
|
|
469
538
|
for (const key in this.subschema) {
|
|
470
539
|
const nestedSchema = this.subschema[key];
|
|
@@ -504,12 +573,36 @@
|
|
|
504
573
|
class OrSetSchema extends BaseSchema {
|
|
505
574
|
schemas;
|
|
506
575
|
constructor(schemas, required, defaultValue) {
|
|
507
|
-
super(
|
|
576
|
+
super(required, defaultValue);
|
|
508
577
|
this.schemas = schemas;
|
|
509
578
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
579
|
+
get type() { return "string"; }
|
|
580
|
+
// public validate(source: SourceValue<OrSetSchemaSource<MemberSchemas>, Required, Default>, pass?: ValidationPass):
|
|
581
|
+
// ModelValue<OrSetSchemaSource<MemberSchemas>, OrSetSchemaModel<MemberSchemas>, Required, Default> {
|
|
582
|
+
// pass = this._ensurePass(source, pass);
|
|
583
|
+
// let result: any = super.validate(source, pass);
|
|
584
|
+
// if (result !== undefined) {
|
|
585
|
+
// let done = false;
|
|
586
|
+
// const failureMessages: string[] = [];
|
|
587
|
+
// for (let i = 0; i < this.schemas.length && !done; i++) {
|
|
588
|
+
// const schema = this.schemas[i];
|
|
589
|
+
// try {
|
|
590
|
+
// result = schema.validate(result, pass);
|
|
591
|
+
// done = true;
|
|
592
|
+
// } catch (error) {
|
|
593
|
+
// if (error instanceof Error) {
|
|
594
|
+
// failureMessages.push(`Schema #${i + 1}: ${error.message}`);
|
|
595
|
+
// } else {
|
|
596
|
+
// failureMessages.push(`Schema #${i + 1}: ${String(error)}`);
|
|
597
|
+
// }
|
|
598
|
+
// pass.assert(failureMessages.length !== this.schemas.length, `Supplied value didn't match any schemas in or-set.\n${failureMessages.join("\n")}`);
|
|
599
|
+
// }
|
|
600
|
+
// }
|
|
601
|
+
// }
|
|
602
|
+
// return result;
|
|
603
|
+
// }
|
|
604
|
+
_validate(source, pass) {
|
|
605
|
+
let result = source;
|
|
513
606
|
if (result !== undefined) {
|
|
514
607
|
let done = false;
|
|
515
608
|
const failureMessages = [];
|
|
@@ -541,13 +634,14 @@
|
|
|
541
634
|
}
|
|
542
635
|
|
|
543
636
|
class StringSchema extends BaseSchema {
|
|
544
|
-
|
|
545
|
-
super("string", required, defaultValue, additionalValidationPasses);
|
|
546
|
-
}
|
|
637
|
+
get type() { return "string"; }
|
|
547
638
|
// Not sure if these will be needed with constructor option. If they are required, it adds a lot of boilerplate to all of the Schema classes.
|
|
548
639
|
// public required() { return new StringSchema(true, this._default, this._additionalValidationPasses); }
|
|
549
640
|
// public optional() { return new StringSchema(false, this._default, this._additionalValidationPasses); }
|
|
550
641
|
// public default<Default extends DefaultValue<StringSource>>(defaultValue: Default) { return new StringSchema(this._required, defaultValue, this._additionalValidationPasses); }
|
|
642
|
+
_validate(source, pass) {
|
|
643
|
+
return source;
|
|
644
|
+
}
|
|
551
645
|
convert(value, pass) {
|
|
552
646
|
if (value instanceof Date) {
|
|
553
647
|
return value.toISOString();
|
|
@@ -563,8 +657,8 @@
|
|
|
563
657
|
length(minimum, maximum, messageA, messageB) {
|
|
564
658
|
return this.custom((model, pass) => {
|
|
565
659
|
messageB = messageB === undefined ? messageA : messageB;
|
|
566
|
-
pass.assert(model.length
|
|
567
|
-
pass.assert(model.length
|
|
660
|
+
pass.assert(model.length >= minimum, messageA === undefined ? `String "${model}: failed minimum length check. (${minimum})` : messageA);
|
|
661
|
+
pass.assert(model.length <= maximum, messageB === undefined ? `String "${model}: failed maximum length check. (${maximum})` : messageB);
|
|
568
662
|
return model;
|
|
569
663
|
}, "afterAll");
|
|
570
664
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { DefaultValue } from "../typing/toolbox";
|
|
1
|
+
import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
|
|
2
2
|
import { ValidationPass } from "../error/ValidationPass";
|
|
3
3
|
import { BaseSchema } from "./BaseSchema";
|
|
4
4
|
export declare class AnySchema<Required extends boolean, Default extends DefaultValue<any>> extends BaseSchema<any, any, Required, Default> {
|
|
5
|
-
|
|
5
|
+
get type(): string;
|
|
6
|
+
protected _validate(source: SourceValue<any, Required, Default>, pass: ValidationPass): ModelValue<any, any, Required, Default>;
|
|
6
7
|
convert(value: any, pass: ValidationPass): any;
|
|
7
8
|
getJsonSchema(): object;
|
|
8
9
|
}
|
|
@@ -7,7 +7,8 @@ export type ArrayModel<Subschema extends BaseSchemaAny> = (Subschema extends Bas
|
|
|
7
7
|
export declare class ArraySchema<Subschema extends BaseSchemaAny, Required extends boolean, Default extends DefaultValue<ArraySource<Subschema>>> extends BaseSchema<ArraySource<Subschema>, ArrayModel<Subschema>, Required, Default> {
|
|
8
8
|
readonly subschema: Subschema;
|
|
9
9
|
constructor(subschema: Subschema, required: Required, defaultValue: Default);
|
|
10
|
-
|
|
10
|
+
get type(): string;
|
|
11
|
+
protected _validate(source: SourceValue<ArraySource<Subschema>, Required, Default>, pass: ValidationPass): ModelValue<ArraySource<Subschema>, ArrayModel<Subschema>, Required, Default>;
|
|
11
12
|
convert(value: ArraySource<Subschema>, pass: ValidationPass): ArrayModel<Subschema>;
|
|
12
13
|
getJsonSchema(): object;
|
|
13
14
|
}
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { ValidationPass } from "../error/ValidationPass";
|
|
2
2
|
import type { AdditionalValidationPasses, AdditionalValidator, AdditionalValidatorAfterType, AdditionalValidatorBeforeType, DefaultValue, EnsureValidator, ModelValue, SourceValue } from "../typing/toolbox";
|
|
3
3
|
export declare abstract class BaseSchema<Source, Model, Required extends boolean, Default extends DefaultValue<Source>> {
|
|
4
|
-
readonly type: string;
|
|
5
4
|
protected readonly _required: Required;
|
|
6
5
|
protected readonly _default: Default;
|
|
7
6
|
protected readonly _additionalValidationPasses: AdditionalValidationPasses<Source, Model>;
|
|
8
|
-
constructor(
|
|
7
|
+
constructor(required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<Source, Model>);
|
|
8
|
+
abstract get type(): string;
|
|
9
9
|
validate(source: SourceValue<Source, Required, Default>, pass?: ValidationPass): ModelValue<Source, Model, Required, Default>;
|
|
10
|
+
protected abstract _validate(source: SourceValue<Source, Required, Default>, pass: ValidationPass): ModelValue<Source, Model, Required, Default>;
|
|
10
11
|
abstract convert(value: Source, pass: ValidationPass): Model;
|
|
11
12
|
custom(additionalValidator: AdditionalValidator<SourceValue<Source, Required, Default>>, type: AdditionalValidatorBeforeType): this;
|
|
12
13
|
custom(additionalValidator: AdditionalValidator<Model>, type: AdditionalValidatorAfterType): this;
|
|
13
14
|
custom(additionalValidator: AdditionalValidator<Model>): this;
|
|
15
|
+
/**
|
|
16
|
+
* Ensures a condition is true during the associated schema's validation pass.
|
|
17
|
+
*
|
|
18
|
+
* @note See {@link EnsureValidator EnsureValidator}.
|
|
19
|
+
*
|
|
20
|
+
* @param ensureValidator A validator to ensure a condition is passed.
|
|
21
|
+
* @param message The message to use for a thrown {@link ValidationError ValidationError} if ensureValidator fails.
|
|
22
|
+
* @returns The schema instance for chaining.
|
|
23
|
+
*/
|
|
14
24
|
ensure(ensureValidator: EnsureValidator<Model>, message?: string): this;
|
|
15
25
|
isRequired(): Required;
|
|
16
26
|
hasDefault(): boolean;
|
|
@@ -19,7 +29,7 @@ export declare abstract class BaseSchema<Source, Model, Required extends boolean
|
|
|
19
29
|
};
|
|
20
30
|
getDefault(pass: ValidationPass): Source;
|
|
21
31
|
protected _getValueDisplay(value: Model): string;
|
|
22
|
-
|
|
32
|
+
getJsonSchema(): object;
|
|
23
33
|
protected _getJsonSchemaDescription(): string;
|
|
24
34
|
protected _ensurePass(source: SourceValue<Source, Required, Default>, pass?: ValidationPass): ValidationPass;
|
|
25
35
|
private _executeAdditionalValidator;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { DefaultValue } from "../typing/toolbox";
|
|
1
|
+
import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
|
|
2
2
|
import { ValidationPass } from "../error/ValidationPass";
|
|
3
3
|
import { BaseSchema } from "./BaseSchema";
|
|
4
4
|
export type BooleanSource = boolean | number | string | null | undefined;
|
|
5
5
|
export declare class BooleanSchema<Required extends boolean, Default extends DefaultValue<BooleanSource>> extends BaseSchema<BooleanSource, boolean, Required, Default> {
|
|
6
|
-
|
|
6
|
+
get type(): string;
|
|
7
|
+
protected _validate(source: SourceValue<BooleanSource, Required, Default>, pass: ValidationPass): ModelValue<BooleanSource, boolean, Required, Default>;
|
|
7
8
|
convert(value: BooleanSource, pass: ValidationPass): boolean;
|
|
8
9
|
getJsonSchema(): object;
|
|
9
10
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { DefaultValue } from "../typing/toolbox";
|
|
1
|
+
import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
|
|
2
2
|
import { ValidationPass } from "../error/ValidationPass";
|
|
3
3
|
import { BaseSchema } from "./BaseSchema";
|
|
4
4
|
type StandardDate = globalThis.Date;
|
|
5
5
|
declare const StandardDate: DateConstructor;
|
|
6
6
|
export type DateSource = string | number | StandardDate;
|
|
7
7
|
export declare class DateSchema<Required extends boolean, Default extends DefaultValue<DateSource>> extends BaseSchema<DateSource, StandardDate, Required, Default> {
|
|
8
|
-
|
|
8
|
+
get type(): string;
|
|
9
|
+
protected _validate(source: SourceValue<DateSource, Required, Default>, pass: ValidationPass): ModelValue<DateSource, Date, Required, Default>;
|
|
9
10
|
convert(value: DateSource, pass: ValidationPass): StandardDate;
|
|
10
11
|
before(date: Date, message?: string): this;
|
|
11
12
|
after(date: Date, message?: string): this;
|
|
@@ -11,7 +11,8 @@ export type DynamicObjectModel<Subschema extends BaseSchemaAny> = ({
|
|
|
11
11
|
export declare class DynamicObjectSchema<Subschema extends BaseSchemaAny, Required extends boolean, Default extends DefaultValue<DynamicObjectSource<Subschema>>> extends BaseSchema<DynamicObjectSource<Subschema>, DynamicObjectModel<Subschema>, Required, Default> {
|
|
12
12
|
readonly subschema: Subschema;
|
|
13
13
|
constructor(subschema: Subschema, required: Required, defaultValue: Default);
|
|
14
|
-
|
|
14
|
+
get type(): string;
|
|
15
|
+
_validate(source: SourceValue<DynamicObjectSource<Subschema>, Required, Default>, pass: ValidationPass): ModelValue<DynamicObjectSource<Subschema>, DynamicObjectModel<Subschema>, Required, Default>;
|
|
15
16
|
convert(source: DynamicObjectSource<Subschema>, pass: ValidationPass): DynamicObjectModel<Subschema>;
|
|
16
17
|
getJsonSchema(): object;
|
|
17
18
|
}
|
|
@@ -4,7 +4,8 @@ import { BaseSchema } from "./BaseSchema";
|
|
|
4
4
|
export declare class EnumerationSchema<Members extends string[], Required extends boolean, Default extends DefaultValue<Members[number]>> extends BaseSchema<Members[number], Members[number], Required, Default> {
|
|
5
5
|
readonly members: Members;
|
|
6
6
|
constructor(members: Members, required: Required, defaultValue: Default);
|
|
7
|
-
|
|
7
|
+
get type(): string;
|
|
8
|
+
_validate(source: SourceValue<Members[number], Required, Default>, pass: ValidationPass): ModelValue<Members[number], Members[number], Required, Default>;
|
|
8
9
|
convert(value: Members[number], pass: ValidationPass): Members[number];
|
|
9
10
|
getJsonSchema(): object;
|
|
10
11
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { DefaultValue } from "../typing/toolbox";
|
|
1
|
+
import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
|
|
2
2
|
import { ValidationPass } from "../error/ValidationPass";
|
|
3
3
|
import { BaseSchema } from "./BaseSchema";
|
|
4
4
|
export type NumberSource = number | bigint | string | boolean | null | undefined | Date;
|
|
5
5
|
export declare class NumberSchema<Required extends boolean, Default extends DefaultValue<NumberSource>> extends BaseSchema<NumberSource, number, Required, Default> {
|
|
6
|
-
|
|
6
|
+
get type(): string;
|
|
7
|
+
protected _validate(source: SourceValue<NumberSource, Required, Default>, pass: ValidationPass): ModelValue<NumberSource, number, Required, Default>;
|
|
7
8
|
convert(value: NumberSource, pass: ValidationPass): number;
|
|
8
9
|
min(minimum: number, message?: string): this;
|
|
9
10
|
max(maximum: number, message?: string): this;
|
|
@@ -18,7 +18,8 @@ export type ObjectModel<Subschema extends ObjectSubschema> = (Merge<{
|
|
|
18
18
|
export declare class ObjectSchema<Subschema extends ObjectSubschema, Required extends boolean, Default extends DefaultValue<ObjectSource<Subschema>>> extends BaseSchema<ObjectSource<Subschema>, ObjectModel<Subschema>, Required, Default> {
|
|
19
19
|
readonly subschema: Subschema;
|
|
20
20
|
constructor(subschema: Subschema, required: Required, defaultValue: Default);
|
|
21
|
-
|
|
21
|
+
get type(): string;
|
|
22
|
+
protected _validate(source: SourceValue<ObjectSource<Subschema>, Required, Default>, pass: ValidationPass): ModelValue<ObjectSource<Subschema>, ObjectModel<Subschema>, Required, Default>;
|
|
22
23
|
convert(value: ObjectSource<Subschema>, pass: ValidationPass): ObjectModel<Subschema>;
|
|
23
24
|
extend<ExtensionSubschema extends ObjectSubschema, ExtensionDefault extends DefaultValue<ObjectSource<ExtensionSubschema>>>(schema: ObjectSchema<ExtensionSubschema, Required, ExtensionDefault>): ObjectSchema<Subschema & ExtensionSubschema, Required, any>;
|
|
24
25
|
getJsonSchema(): object;
|
|
@@ -11,7 +11,8 @@ export type OrSetSchemaModel<MemberSchemas extends BaseSchemaAny[]> = ({
|
|
|
11
11
|
export declare class OrSetSchema<MemberSchemas extends BaseSchemaAny[], Required extends boolean, Default extends DefaultValue<OrSetSchemaSource<MemberSchemas>>> extends BaseSchema<OrSetSchemaSource<MemberSchemas>, OrSetSchemaModel<MemberSchemas>, Required, Default> {
|
|
12
12
|
readonly schemas: MemberSchemas;
|
|
13
13
|
constructor(schemas: MemberSchemas, required: Required, defaultValue: Default);
|
|
14
|
-
|
|
14
|
+
get type(): string;
|
|
15
|
+
_validate(source: SourceValue<OrSetSchemaSource<MemberSchemas>, Required, Default>, pass: ValidationPass): ModelValue<OrSetSchemaSource<MemberSchemas>, OrSetSchemaModel<MemberSchemas>, Required, Default>;
|
|
15
16
|
convert(value: OrSetSchemaSource<MemberSchemas>, pass: ValidationPass): OrSetSchemaModel<MemberSchemas>;
|
|
16
17
|
getJsonSchema(): object;
|
|
17
18
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ValidationPass } from "../error/ValidationPass";
|
|
2
|
-
import {
|
|
2
|
+
import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
|
|
3
3
|
import { BaseSchema } from "./BaseSchema";
|
|
4
4
|
export type StringSource = string | number | boolean | null | undefined | Date;
|
|
5
5
|
export declare class StringSchema<Required extends boolean, Default extends DefaultValue<StringSource>> extends BaseSchema<StringSource, string, Required, Default> {
|
|
6
|
-
|
|
6
|
+
get type(): string;
|
|
7
|
+
protected _validate(source: SourceValue<StringSource, Required, Default>, pass: ValidationPass): ModelValue<StringSource, string, Required, Default>;
|
|
7
8
|
convert(value: StringSource, pass: ValidationPass): string;
|
|
8
9
|
length(minimum: number, maximum: number): this;
|
|
9
10
|
length(minimum: number, maximum: number, message: string): this;
|
|
@@ -21,9 +21,21 @@ export type AdditionalValidatorAfterType = ("beforeConversion" | "afterConversio
|
|
|
21
21
|
* - afterAll: Validator executed after "afterConversion" and as the last task in the validation pipeline.
|
|
22
22
|
*/
|
|
23
23
|
export type AdditionalValidatorType = AdditionalValidatorBeforeType | AdditionalValidatorAfterType;
|
|
24
|
+
/**
|
|
25
|
+
* Represents a custom pass allowing any additional value specificity code, or value augmentations.
|
|
26
|
+
*
|
|
27
|
+
* @note Use "{@link ValidationPass pass}.assert" and/or "throw {@link ValidationPass pass}.getError" to build your value specificity checks.
|
|
28
|
+
*
|
|
29
|
+
* @param {Type} data The data being validated.
|
|
30
|
+
* @param {ValidationPass} pass A reference to the current validation pass. Used to interact with the validation pass.
|
|
31
|
+
* @returns `data` with any desired augmentations.
|
|
32
|
+
*/
|
|
24
33
|
export type AdditionalValidator<Type> = (data: Type, pass: ValidationPass) => Type;
|
|
25
34
|
/**
|
|
26
|
-
* Represents a pass to ensure that your data meets
|
|
35
|
+
* Represents a pass to ensure that your data meets conditions.
|
|
36
|
+
* @param data The data being validated.
|
|
37
|
+
* @param pass A reference to the current validation pass. Used to interact with the validation pass.
|
|
38
|
+
* @returns `true` if your data meets the desired conditions, `false` otherwise.
|
|
27
39
|
*/
|
|
28
40
|
export type EnsureValidator<Type> = (data: Type, pass: ValidationPass) => boolean;
|
|
29
41
|
export type AdditionalValidationPasses<Source, Model> = {
|
|
@@ -34,4 +46,7 @@ export type AdditionalValidationPasses<Source, Model> = {
|
|
|
34
46
|
afterConversion: AdditionalValidator<Model>[];
|
|
35
47
|
afterAll: AdditionalValidator<Model>[];
|
|
36
48
|
};
|
|
37
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Intersects two types.
|
|
51
|
+
*/
|
|
52
|
+
export type Merge<A, B> = (keyof A extends never ? B : keyof B extends never ? A : A & B);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lucania/schema",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "A schema module for compile-time and runtime type checking.",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://github.com/lucania-software/schema#readme",
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@babel/preset-env": "^7.
|
|
33
|
-
"@rollup/plugin-typescript": "^11.1.
|
|
34
|
-
"@types/node": "^20.
|
|
35
|
-
"nodemon": "^3.
|
|
36
|
-
"rollup": "^4.
|
|
37
|
-
"tslib": "^2.6.
|
|
38
|
-
"typescript": "^5.
|
|
32
|
+
"@babel/preset-env": "^7.24.7",
|
|
33
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
34
|
+
"@types/node": "^20.14.2",
|
|
35
|
+
"nodemon": "^3.1.3",
|
|
36
|
+
"rollup": "^4.18.0",
|
|
37
|
+
"tslib": "^2.6.3",
|
|
38
|
+
"typescript": "^5.4.5"
|
|
39
39
|
}
|
|
40
40
|
}
|