@lucania/schema 2.0.1 → 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/builder.d.ts +1 -0
- package/build/index.js +145 -48
- package/build/schema/AnySchema.d.ts +3 -2
- package/build/schema/ArraySchema.d.ts +2 -1
- package/build/schema/BaseSchema.d.ts +14 -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/extended.d.ts +2 -0
- package/build/typing/toolbox.d.ts +18 -3
- 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/builder.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export declare namespace Schema {
|
|
|
42
42
|
function OrSet<MemberSchemas extends BaseSchemaAny[]>(subschema: TypedMembers<MemberSchemas>): OrSetSchema<MemberSchemas, true, undefined>;
|
|
43
43
|
function OrSet<MemberSchemas extends BaseSchemaAny[], Required extends boolean>(subschema: TypedMembers<MemberSchemas>, required: Required): OrSetSchema<MemberSchemas, Required, undefined>;
|
|
44
44
|
function OrSet<MemberSchemas extends BaseSchemaAny[], Required extends boolean, Default extends DefaultValue<OrSetSchemaSource<MemberSchemas>>>(subschema: TypedMembers<MemberSchemas>, required: Required, defaultValue: Default): OrSetSchema<MemberSchemas, Required, Default>;
|
|
45
|
+
function Members<Members extends string[]>(...members: Members): TypedMembers<Members>;
|
|
45
46
|
function Members<Members extends any[]>(...members: Members): TypedMembers<Members>;
|
|
46
47
|
type Model<Schema extends BaseSchemaAny> = Schema extends BaseSchema<infer Source, infer Model, infer Require, infer Default> ? (ModelValue<Source, Model, Require, Default>) : never;
|
|
47
48
|
type Source<Schema extends BaseSchemaAny> = Schema extends BaseSchema<infer Source, any, infer Require, infer Default> ? (SourceValue<Source, Require, Default>) : never;
|
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);
|
|
@@ -109,6 +118,9 @@
|
|
|
109
118
|
});
|
|
110
119
|
return this;
|
|
111
120
|
}
|
|
121
|
+
isRequired() {
|
|
122
|
+
return this._required;
|
|
123
|
+
}
|
|
112
124
|
hasDefault() {
|
|
113
125
|
return this._default !== undefined && this._default !== null;
|
|
114
126
|
}
|
|
@@ -129,6 +141,13 @@
|
|
|
129
141
|
_getValueDisplay(value) {
|
|
130
142
|
return String(value);
|
|
131
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
|
+
}
|
|
132
151
|
_getJsonSchemaDescription() {
|
|
133
152
|
const pass = new ValidationPass(this, this._default);
|
|
134
153
|
const descriptionPieces = [];
|
|
@@ -195,8 +214,9 @@
|
|
|
195
214
|
}
|
|
196
215
|
|
|
197
216
|
class AnySchema extends BaseSchema {
|
|
198
|
-
|
|
199
|
-
|
|
217
|
+
get type() { return "any"; }
|
|
218
|
+
_validate(source, pass) {
|
|
219
|
+
return source;
|
|
200
220
|
}
|
|
201
221
|
convert(value, pass) {
|
|
202
222
|
return value;
|
|
@@ -209,16 +229,28 @@
|
|
|
209
229
|
class ArraySchema extends BaseSchema {
|
|
210
230
|
subschema;
|
|
211
231
|
constructor(subschema, required, defaultValue) {
|
|
212
|
-
super(
|
|
232
|
+
super(required, defaultValue);
|
|
213
233
|
this.subschema = subschema;
|
|
214
234
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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));
|
|
222
254
|
}
|
|
223
255
|
}
|
|
224
256
|
return result;
|
|
@@ -244,8 +276,9 @@
|
|
|
244
276
|
}
|
|
245
277
|
|
|
246
278
|
class BooleanSchema extends BaseSchema {
|
|
247
|
-
|
|
248
|
-
|
|
279
|
+
get type() { return "boolean"; }
|
|
280
|
+
_validate(source, pass) {
|
|
281
|
+
return source;
|
|
249
282
|
}
|
|
250
283
|
convert(value, pass) {
|
|
251
284
|
if (typeof value === "number") {
|
|
@@ -276,8 +309,9 @@
|
|
|
276
309
|
|
|
277
310
|
const StandardDate = globalThis.Date;
|
|
278
311
|
class DateSchema extends BaseSchema {
|
|
279
|
-
|
|
280
|
-
|
|
312
|
+
get type() { return "Date"; }
|
|
313
|
+
_validate(source, pass) {
|
|
314
|
+
return source;
|
|
281
315
|
}
|
|
282
316
|
convert(value, pass) {
|
|
283
317
|
if (typeof value === "string") {
|
|
@@ -335,12 +369,24 @@
|
|
|
335
369
|
class DynamicObjectSchema extends BaseSchema {
|
|
336
370
|
subschema;
|
|
337
371
|
constructor(subschema, required, defaultValue) {
|
|
338
|
-
super(
|
|
372
|
+
super(required, defaultValue);
|
|
339
373
|
this.subschema = subschema;
|
|
340
374
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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;
|
|
344
390
|
if (result !== undefined) {
|
|
345
391
|
for (const key in result) {
|
|
346
392
|
const nestedValue = result[key];
|
|
@@ -369,12 +415,19 @@
|
|
|
369
415
|
class EnumerationSchema extends BaseSchema {
|
|
370
416
|
members;
|
|
371
417
|
constructor(members, required, defaultValue) {
|
|
372
|
-
super(
|
|
418
|
+
super(required, defaultValue);
|
|
373
419
|
this.members = members;
|
|
374
420
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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;
|
|
378
431
|
pass.assert(this.members.includes(result), `"${result}" is not a valid enumeration value (Expected: ${this.members.join(", ")}).`);
|
|
379
432
|
return result;
|
|
380
433
|
}
|
|
@@ -391,8 +444,9 @@
|
|
|
391
444
|
}
|
|
392
445
|
|
|
393
446
|
class NumberSchema extends BaseSchema {
|
|
394
|
-
|
|
395
|
-
|
|
447
|
+
get type() { return "number"; }
|
|
448
|
+
_validate(source, pass) {
|
|
449
|
+
return source;
|
|
396
450
|
}
|
|
397
451
|
convert(value, pass) {
|
|
398
452
|
if (typeof value === "bigint") {
|
|
@@ -446,22 +500,40 @@
|
|
|
446
500
|
class ObjectSchema extends BaseSchema {
|
|
447
501
|
subschema;
|
|
448
502
|
constructor(subschema, required, defaultValue) {
|
|
449
|
-
super(
|
|
503
|
+
super(required, defaultValue);
|
|
450
504
|
this.subschema = subschema;
|
|
451
505
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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 = {};
|
|
456
527
|
for (const key in this.subschema) {
|
|
457
528
|
const nestedSchema = this.subschema[key];
|
|
458
|
-
const nestedValue =
|
|
459
|
-
|
|
529
|
+
const nestedValue = input[key];
|
|
530
|
+
output[key] = this.subschema[key].validate(input[key], pass.next([...pass.path, key], nestedSchema, nestedValue));
|
|
460
531
|
}
|
|
461
532
|
}
|
|
462
|
-
return
|
|
533
|
+
return output;
|
|
463
534
|
}
|
|
464
535
|
convert(value, pass) {
|
|
536
|
+
pass.assert(typeof value === "object", `Unable to convert ${ObjectSchema.getType(value)} to object.`);
|
|
465
537
|
const model = {};
|
|
466
538
|
for (const key in this.subschema) {
|
|
467
539
|
const nestedSchema = this.subschema[key];
|
|
@@ -501,12 +573,36 @@
|
|
|
501
573
|
class OrSetSchema extends BaseSchema {
|
|
502
574
|
schemas;
|
|
503
575
|
constructor(schemas, required, defaultValue) {
|
|
504
|
-
super(
|
|
576
|
+
super(required, defaultValue);
|
|
505
577
|
this.schemas = schemas;
|
|
506
578
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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;
|
|
510
606
|
if (result !== undefined) {
|
|
511
607
|
let done = false;
|
|
512
608
|
const failureMessages = [];
|
|
@@ -538,13 +634,14 @@
|
|
|
538
634
|
}
|
|
539
635
|
|
|
540
636
|
class StringSchema extends BaseSchema {
|
|
541
|
-
|
|
542
|
-
super("string", required, defaultValue, additionalValidationPasses);
|
|
543
|
-
}
|
|
637
|
+
get type() { return "string"; }
|
|
544
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.
|
|
545
639
|
// public required() { return new StringSchema(true, this._default, this._additionalValidationPasses); }
|
|
546
640
|
// public optional() { return new StringSchema(false, this._default, this._additionalValidationPasses); }
|
|
547
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
|
+
}
|
|
548
645
|
convert(value, pass) {
|
|
549
646
|
if (value instanceof Date) {
|
|
550
647
|
return value.toISOString();
|
|
@@ -560,8 +657,8 @@
|
|
|
560
657
|
length(minimum, maximum, messageA, messageB) {
|
|
561
658
|
return this.custom((model, pass) => {
|
|
562
659
|
messageB = messageB === undefined ? messageA : messageB;
|
|
563
|
-
pass.assert(model.length
|
|
564
|
-
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);
|
|
565
662
|
return model;
|
|
566
663
|
}, "afterAll");
|
|
567
664
|
}
|
|
@@ -610,7 +707,7 @@
|
|
|
610
707
|
}
|
|
611
708
|
Schema.Array = Array;
|
|
612
709
|
function Enumeration(members, required = true, defaultValue = undefined) {
|
|
613
|
-
return new EnumerationSchema(members
|
|
710
|
+
return new EnumerationSchema(members.$members, required, defaultValue);
|
|
614
711
|
}
|
|
615
712
|
Schema.Enumeration = Enumeration;
|
|
616
713
|
function DynamicObject(subschema, required = true, defaultValue = undefined) {
|
|
@@ -618,11 +715,11 @@
|
|
|
618
715
|
}
|
|
619
716
|
Schema.DynamicObject = DynamicObject;
|
|
620
717
|
function OrSet(members, required = true, defaultValue = undefined) {
|
|
621
|
-
return new OrSetSchema(members
|
|
718
|
+
return new OrSetSchema(members.$members, required, defaultValue);
|
|
622
719
|
}
|
|
623
720
|
Schema.OrSet = OrSet;
|
|
624
721
|
function Members(...members) {
|
|
625
|
-
return {
|
|
722
|
+
return { $members: members };
|
|
626
723
|
}
|
|
627
724
|
Schema.Members = Members;
|
|
628
725
|
})(exports.Schema || (exports.Schema = {}));
|
|
@@ -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,24 +1,35 @@
|
|
|
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;
|
|
25
|
+
isRequired(): Required;
|
|
15
26
|
hasDefault(): boolean;
|
|
16
27
|
isDefaultRuntimeEvaluated(): this is {
|
|
17
28
|
_default: Function;
|
|
18
29
|
};
|
|
19
30
|
getDefault(pass: ValidationPass): Source;
|
|
20
31
|
protected _getValueDisplay(value: Model): string;
|
|
21
|
-
|
|
32
|
+
getJsonSchema(): object;
|
|
22
33
|
protected _getJsonSchemaDescription(): string;
|
|
23
34
|
protected _ensurePass(source: SourceValue<Source, Required, Default>, pass?: ValidationPass): ValidationPass;
|
|
24
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;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { BaseSchema } from "../schema/BaseSchema";
|
|
2
|
+
import { ObjectSchema } from "../schema/ObjectSchema";
|
|
2
3
|
import { DefaultValue } from "./toolbox";
|
|
3
4
|
export type BaseSchemaAny = BaseSchema<any, any, boolean, DefaultValue<any>>;
|
|
5
|
+
export type ObjectSchemaAny = ObjectSchema<any, boolean, DefaultValue<any>>;
|
|
@@ -7,7 +7,7 @@ export type ModelRequirement<Layout extends BaseSchemaAny> = (Layout extends Bas
|
|
|
7
7
|
export type ModelValue<Source, Model, Required extends boolean, Default extends DefaultValue<Source>> = (Required extends true ? Model : Default extends undefined ? Model | undefined : Model);
|
|
8
8
|
export type DefaultValue<Type> = undefined | Type | ((pass: ValidationPass) => Type);
|
|
9
9
|
export type TypedMembers<Members> = {
|
|
10
|
-
|
|
10
|
+
$members: Members;
|
|
11
11
|
};
|
|
12
12
|
export type AdditionalValidatorBeforeType = ("beforeAll" | "beforeDefault" | "afterDefault");
|
|
13
13
|
export type AdditionalValidatorAfterType = ("beforeConversion" | "afterConversion" | "afterAll");
|
|
@@ -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
|
}
|