justus 0.0.7 → 0.1.1

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.
@@ -1,22 +1,32 @@
1
1
  import ts from 'typescript'
2
2
  import {
3
3
  AllOfValidator,
4
+ any,
4
5
  AnyArrayValidator,
5
6
  AnyNumberValidator,
6
7
  AnyObjectValidator,
7
8
  AnyStringValidator,
8
9
  AnyValidator,
10
+ array,
9
11
  ArrayValidator,
10
12
  assertSchema,
13
+ boolean,
11
14
  BooleanValidator,
12
15
  ConstantValidator,
16
+ date,
13
17
  DateValidator,
14
18
  getValidator,
19
+ NeverValidator,
20
+ number,
15
21
  NumberValidator,
22
+ object,
16
23
  ObjectValidator,
17
24
  OneOfValidator,
25
+ OptionalValidator,
26
+ string,
18
27
  StringValidator,
19
28
  TupleValidator,
29
+ url,
20
30
  URLValidator,
21
31
  Validation,
22
32
  Validator,
@@ -42,14 +52,13 @@ type ValidatorConstructor<V extends Validator = Validator> = { // <T = any> = {
42
52
  * ========================================================================== */
43
53
 
44
54
  /** Registry of all `Validator` constructors and related `TypeGenerator`s. */
45
- const generators = new Map<Function, TypeGenerator<any>>()
55
+ const generators = new Map<Function | Validator, TypeGenerator<any>>()
46
56
 
47
57
  /** Register a `TypeGenerator` function for a `Validator`. */
48
58
  export function registerTypeGenerator<V extends Validator>(
49
- validator: ValidatorConstructor<V>,
59
+ validator: V | ValidatorConstructor<V>,
50
60
  generator: TypeGenerator<V>,
51
61
  ): void {
52
- assertSchema(validator.prototype instanceof Validator, 'Not a `Validator` class')
53
62
  generators.set(validator, generator)
54
63
  }
55
64
 
@@ -84,7 +93,7 @@ export function generateTypes(validations: Record<string, Validation>): string {
84
93
 
85
94
  // Create a type alias declaration with the name of the export
86
95
  const modifiers = [ ts.factory.createModifier(ts.SyntaxKind.ExportKeyword) ]
87
- const decl = ts.factory.createTypeAliasDeclaration(undefined, modifiers, name, [], type)
96
+ const decl = ts.factory.createTypeAliasDeclaration(modifiers, name, [], type)
88
97
  types.push(decl)
89
98
  }
90
99
 
@@ -107,7 +116,7 @@ function generateTypeNode(
107
116
  const reference = references.get(validator)
108
117
  if (reference) return ts.factory.createTypeReferenceNode(reference)
109
118
 
110
- const generator = generators.get(validator.constructor)
119
+ const generator = generators.get(validator) || generators.get(validator.constructor)
111
120
  assertSchema(!! generator, `Type generator for "${validator.constructor.name}" not found`)
112
121
  return generator(validator, references)
113
122
  }
@@ -125,15 +134,13 @@ const stringType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
125
134
  const undefinedType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
126
135
  const recordType = ts.factory.createMappedTypeNode(
127
136
  undefined, // readonly
128
- ts.factory.createTypeParameterDeclaration('key', stringType),
137
+ ts.factory.createTypeParameterDeclaration([], 'key', stringType),
129
138
  undefined, // name type
130
139
  undefined, // question token
131
140
  anyType, // type of the mapped key
132
141
  undefined) // members
133
142
 
134
- // Modifiers and tokens
135
-
136
- const readonlyKeyword = [ ts.factory.createModifier(ts.SyntaxKind.ReadonlyKeyword) ]
143
+ // "Optional" modifier (the "?" token )
137
144
  const optionalKeyword = ts.factory.createToken(ts.SyntaxKind.QuestionToken)
138
145
 
139
146
 
@@ -147,9 +154,19 @@ registerTypeGenerator(AnyNumberValidator, () => numberType)
147
154
  registerTypeGenerator(AnyObjectValidator, () => recordType)
148
155
  registerTypeGenerator(AnyStringValidator, () => stringType)
149
156
  registerTypeGenerator(BooleanValidator, () => booleanType)
157
+ registerTypeGenerator(NeverValidator, () => neverType)
150
158
  registerTypeGenerator(DateValidator, () => ts.factory.createTypeReferenceNode('Date'))
151
159
  registerTypeGenerator(URLValidator, () => ts.factory.createTypeReferenceNode('URL'))
152
160
 
161
+ registerTypeGenerator(any, () => anyType)
162
+ registerTypeGenerator(array, () => anyArrayType)
163
+ registerTypeGenerator(number, () => numberType)
164
+ registerTypeGenerator(object, () => recordType)
165
+ registerTypeGenerator(string, () => stringType)
166
+ registerTypeGenerator(boolean, () => booleanType)
167
+ registerTypeGenerator(date, () => ts.factory.createTypeReferenceNode('Date'))
168
+ registerTypeGenerator(url, () => ts.factory.createTypeReferenceNode('URL'))
169
+
153
170
  /* ========================================================================== */
154
171
 
155
172
  // Complex generator functions...
@@ -180,6 +197,11 @@ registerTypeGenerator(NumberValidator, (validator: NumberValidator) => {
180
197
  return ts.factory.createIntersectionTypeNode([ numberType, literal ])
181
198
  })
182
199
 
200
+ registerTypeGenerator(OptionalValidator, (validator: OptionalValidator, references) => {
201
+ const type = generateTypeNode(validator.validator, references)
202
+ return ts.factory.createUnionTypeNode([ type, undefinedType ])
203
+ })
204
+
183
205
  registerTypeGenerator(StringValidator, (validator: StringValidator) => {
184
206
  if (! validator.brand) return stringType
185
207
 
@@ -245,15 +267,15 @@ registerTypeGenerator(OneOfValidator, (validator, references) => {
245
267
  registerTypeGenerator(ObjectValidator, (validator, references) => {
246
268
  const properties: ts.PropertySignature[] = []
247
269
 
248
- for (const [ key, property ] of validator.properties.entries()) {
249
- const { validator, readonly, optional } = property || { optional: true }
250
- const type = validator ? generateTypeNode(validator, references) : neverType
270
+ for (const [ key, valueValidator ] of validator.validators.entries()) {
271
+ const type = generateTypeNode(valueValidator, references)
272
+ const optional = valueValidator.optional
251
273
 
252
274
  const signature = ts.factory.createPropertySignature(
253
- readonly ? readonlyKeyword : undefined,
254
- key,
255
- optional ? optionalKeyword : undefined,
256
- type)
275
+ undefined,
276
+ key,
277
+ optional ? optionalKeyword : undefined,
278
+ type)
257
279
 
258
280
  properties.push(signature)
259
281
  }
@@ -264,7 +286,7 @@ registerTypeGenerator(ObjectValidator, (validator, references) => {
264
286
 
265
287
  const extra = ts.factory.createMappedTypeNode(
266
288
  undefined, // readonly
267
- ts.factory.createTypeParameterDeclaration('key', stringType),
289
+ ts.factory.createTypeParameterDeclaration([], 'key', stringType),
268
290
  undefined, // name type
269
291
  undefined, // question token
270
292
  optionalPropertyType, // (type | undefined)
package/src/index.ts CHANGED
@@ -12,11 +12,13 @@ export * from './utilities'
12
12
  export { allOf, oneOf, AllOfValidator, InferAllOfValidationType, InferOneOfValidationType, OneOfValidator, UnionArguments } from './validators/union'
13
13
  export { any, AnyValidator } from './validators/any'
14
14
  export { _array, array, arrayOf, AnyArrayValidator, ArrayConstraints, ArrayValidator } from './validators/array'
15
- export { boolean, BooleanValidator } from './validators/boolean'
15
+ export { _boolean, boolean, BooleanConstraints, BooleanValidator } from './validators/boolean'
16
16
  export { constant, ConstantValidator } from './validators/constant'
17
17
  export { _date, date, DateConstraints, DateValidator } from './validators/date'
18
+ export { never, NeverValidator } from './validators/never'
18
19
  export { _number, number, AnyNumberValidator, BrandedNumberConstraints, NumberConstraints, NumberValidator } from './validators/number'
19
- export { _object, object, AnyObjectValidator, ObjectProperty, ObjectValidator } from './validators/object'
20
+ export { _object, object, AnyObjectValidator, ObjectValidator } from './validators/object'
21
+ export { optional, OptionalValidator } from './validators/optional'
20
22
  export { _string, string, AnyStringValidator, BrandedStringConstraints, StringConstraints, StringValidator } from './validators/string'
21
23
  export { tuple, TupleMember, TupleValidator } from './validators/tuple'
22
24
  export { _url, url, URLConstraints, URLValidator } from './validators/url'
package/src/schema.ts CHANGED
@@ -3,15 +3,10 @@ import { getValidator } from './utilities'
3
3
 
4
4
  import {
5
5
  AdditionalProperties,
6
- CombinedModifier,
7
6
  InferValidation,
8
- Modifier,
9
- OptionalModifier,
10
- ReadonlyModifier,
11
7
  Validation,
12
8
  Validator,
13
9
  additionalValidator,
14
- modifierValidator,
15
10
  } from './types'
16
11
 
17
12
  /* ========================================================================== *
@@ -28,7 +23,7 @@ export function _allowAdditionalProperties(options?: Validation | boolean): Addi
28
23
  if (options === false) return { [additionalValidator]: false }
29
24
  if (options === true) return { [additionalValidator]: any }
30
25
 
31
- return { [additionalValidator]: getValidator(options) }
26
+ return { [additionalValidator]: options ? getValidator(options) : any }
32
27
  }
33
28
 
34
29
  /**
@@ -44,69 +39,3 @@ export const allowAdditionalProperties = _allowAdditionalProperties as
44
39
  typeof _allowAdditionalProperties & AdditionalProperties<Validator<any>>
45
40
 
46
41
  allowAdditionalProperties[additionalValidator] = any
47
-
48
- /* ========================================================================== *
49
- * SCHEMA KEYS MODIFIERS *
50
- * ========================================================================== */
51
-
52
- export type CombineModifiers<M1 extends Modifier, M2 extends Modifier> =
53
- M1 extends ReadonlyModifier ?
54
- M2 extends ReadonlyModifier<infer V> ? ReadonlyModifier<V> :
55
- M2 extends OptionalModifier<infer V> ? CombinedModifier<V> :
56
- never :
57
- M1 extends OptionalModifier ?
58
- M2 extends ReadonlyModifier<infer V> ? CombinedModifier<V> :
59
- M2 extends OptionalModifier<infer V> ? OptionalModifier<V> :
60
- never :
61
- never
62
-
63
- /* -------------------------------------------------------------------------- */
64
-
65
- /** Type guard for `Modifier` instances */
66
- export function isModifier(what: any): what is Modifier<any> {
67
- return (what && (typeof what === 'object') && (modifierValidator in what))
68
- }
69
-
70
- /* -------------------------------------------------------------------------- */
71
-
72
- /**
73
- * Ensure that the property is marked as _read only_ in the `Schema`.
74
- *
75
- * @param validation - A `Validation` to be marked as _read only_.
76
- */
77
- export function readonly(): ReadonlyModifier<any>
78
- export function readonly<V extends Validation>(validation: V): ReadonlyModifier<Validator<InferValidation<V>>>
79
- export function readonly<M extends Modifier>(modifier: M): CombineModifiers<ReadonlyModifier, M>
80
-
81
- export function readonly(options?: Modifier | Validation): Modifier {
82
- const { [modifierValidator]: validation = any, optional = false } =
83
- isModifier(options) ? options : { [modifierValidator]: options }
84
-
85
- const validator = getValidator(validation)
86
-
87
- return optional ?
88
- { [modifierValidator]: validator, optional, readonly: true } :
89
- { [modifierValidator]: validator, readonly: true }
90
- }
91
-
92
- /* -------------------------------------------------------------------------- */
93
-
94
- /**
95
- * Ensure that the property is marked as _optional_ in the `Schema`.
96
- *
97
- * @param validation - A `Validation` to be marked as _optional_.
98
- */
99
- export function optional(): OptionalModifier<any>
100
- export function optional<V extends Validation>(validation: V): OptionalModifier<Validator<InferValidation<V>>>
101
- export function optional<M extends Modifier>(modifier: M): CombineModifiers<OptionalModifier, M>
102
-
103
- export function optional(options?: Modifier<any> | Validation): Modifier<any> {
104
- const { [modifierValidator]: validation = any, readonly = false } =
105
- isModifier(options) ? options : { [modifierValidator]: options }
106
-
107
- const validator = getValidator(validation)
108
-
109
- return readonly ?
110
- { [modifierValidator]: validator, readonly, optional: true } :
111
- { [modifierValidator]: validator, optional: true }
112
- }
package/src/types.ts CHANGED
@@ -2,21 +2,18 @@
2
2
  * SYMBOLS IDENTIFYING SPECIAL FUNCTIONALITIES *
3
3
  * ========================================================================== */
4
4
 
5
+ /** A symbol indicating that an instance is (in fact) a `Validator`. */
6
+ export const isValidator = Symbol.for('justus.isValidator')
7
+
5
8
  /** A symbol indicating the `Validator` for a `Tuple`'s rest parameter. */
6
9
  export const restValidator = Symbol.for('justus.restValidator')
7
10
 
8
11
  /** A symbol indicating the `Validator` for a `Schema`. */
9
12
  export const schemaValidator = Symbol.for('justus.schemaValidator')
10
13
 
11
- /** A symbol indicating the `Validator` for a `Modifier`. */
12
- export const modifierValidator = Symbol.for('justus.modifierValidator')
13
-
14
14
  /** A symbol indicating the `Validator` for a `Schema`'s additional properties. */
15
15
  export const additionalValidator = Symbol.for('justus.additionalValidator')
16
16
 
17
- /** A symbol indicating that a `Schema` property is _forbidden_. */
18
- export const never = Symbol.for('justus.never')
19
-
20
17
  /* ========================================================================== *
21
18
  * BASIC VALIDATION TYPES *
22
19
  * ========================================================================== */
@@ -37,7 +34,45 @@ export interface ValidationOptions {
37
34
  * A `Validator` is an object capable of validating a given _value_ and
38
35
  * (possibly) converting it the required type `T`.
39
36
  */
40
- export abstract class Validator<T = any> implements Iterable<TupleRestParameter<T>> {
37
+ export interface Validator<T = any> extends Iterable<TupleRestParameter<T>> {
38
+ [isValidator]: true
39
+
40
+ optional?: boolean
41
+
42
+ /** Validate a _value_ and optionally convert it to the required `Type` */
43
+ validate(value: unknown, options: ValidationOptions): T
44
+
45
+ /** Allow any `Validator` to be used as a rest parameter in `Tuple`s */
46
+ [Symbol.iterator](): Generator<TupleRestParameter<T>>;
47
+ }
48
+
49
+ /**
50
+ * Create a validator "factory", that is a function that when invoked will
51
+ * create a new `Validator` according to the parameters specified. This function
52
+ * will also implement the `Validator` interface itself, using the `Validator`
53
+ * supplied as the first parameter.
54
+ */
55
+ export function makeValidatorFactory<
56
+ V extends Validator,
57
+ F extends (...args: any[]) => Validator,
58
+ >(validator: V, factory: F): F & V {
59
+ return Object.assign(factory, {
60
+ optional: validator.optional,
61
+ validate: validator.validate.bind(validator),
62
+ [Symbol.iterator]: validator[Symbol.iterator].bind(validator),
63
+ [isValidator]: true,
64
+ }) as F & V
65
+ }
66
+
67
+ /**
68
+ * A `Validator` is an object capable of validating a given _value_ and
69
+ * (possibly) converting it the required type `T`.
70
+ */
71
+ export abstract class AbstractValidator<T = any> implements Iterable<TupleRestParameter<T>> {
72
+ [isValidator]: true = true
73
+
74
+ optional?: boolean = undefined
75
+
41
76
  /** Validate a _value_ and optionally convert it to the required `Type` */
42
77
  abstract validate(value: unknown, options: ValidationOptions): T
43
78
 
@@ -58,7 +93,7 @@ export abstract class Validator<T = any> implements Iterable<TupleRestParameter<
58
93
  * * Either `null`, a `boolean`, a `number` or a `string` for constants
59
94
  */
60
95
  export type Validation =
61
- (() => Validator) | Validator | // Validator instances or their creators
96
+ Validator | // Validator instances
62
97
  Tuple | Schema | // Tuples or schemas (arrays, objects)
63
98
  null | boolean | number | string // Primitives, mapped as constants
64
99
 
@@ -69,33 +104,6 @@ export type InferValidation<V> =
69
104
  // Let `InferValidationType<T>` be liberal in the type it accepts and check
70
105
  // here whether it extends `Validation`
71
106
  V extends Validation ?
72
-
73
- // `Validation` can be a function returning a `Validator`, here we need to
74
- // extract the return value of its _zero-arguments_ overload
75
- V extends {
76
- (...args: infer A0): Validator<infer R0>;
77
- (...args: infer A1): Validator<infer R1>;
78
- (...args: infer A2): Validator<infer R2>;
79
- (...args: infer A3): Validator<infer R3>;
80
- (...args: infer A4): Validator<infer R4>;
81
- (...args: infer A5): Validator<infer R5>;
82
- (...args: infer A6): Validator<infer R6>;
83
- (...args: infer A7): Validator<infer R7>;
84
- (...args: infer A8): Validator<infer R8>;
85
- (...args: infer A9): Validator<infer R9>;
86
- } ?
87
- A0 extends [] ? R0 :
88
- A1 extends [] ? R1 :
89
- A2 extends [] ? R2 :
90
- A3 extends [] ? R3 :
91
- A4 extends [] ? R4 :
92
- A5 extends [] ? R5 :
93
- A6 extends [] ? R6 :
94
- A7 extends [] ? R7 :
95
- A8 extends [] ? R8 :
96
- A9 extends [] ? R9 :
97
- never :
98
-
99
107
  // Validators return their validation type
100
108
  V extends Validator<infer T> ? T :
101
109
 
@@ -175,7 +183,7 @@ export type InferTuple<T> =
175
183
  * how they should be validated.
176
184
  */
177
185
  export interface Schema {
178
- [ key: string ] : Validation | Modifier | typeof never
186
+ [ key: string ] : Validation
179
187
  [ additionalValidator ]?: Validator | false
180
188
  }
181
189
 
@@ -187,118 +195,23 @@ export interface AdditionalProperties<V extends Validator | false> {
187
195
  [ additionalValidator ]: V
188
196
  }
189
197
 
190
- /* -------------------------------------------------------------------------- */
191
-
192
- /**
193
- * A `Modifier` marks a `Schema` property either _read only_ and/or _optional_.
194
- */
195
- export interface Modifier<V extends Validator = Validator> {
196
- [ modifierValidator ]: V
197
- readonly?: true,
198
- optional?: true,
199
- }
200
-
201
- /**
202
- * Mark a `Schema` property as _read only_.
203
- */
204
- export interface ReadonlyModifier<V extends Validator = Validator> extends Modifier<V> {
205
- readonly: true,
206
- }
207
-
208
- /**
209
- * Mark a `Schema` property as _optional_.
210
- */
211
- export interface OptionalModifier<V extends Validator = Validator> extends Modifier<V> {
212
- optional: true,
213
- }
214
-
215
- /**
216
- * Mark a `Schema` property as both _optional_ and _read only_.
217
- */
218
- export interface CombinedModifier<V extends Validator = Validator>
219
- extends ReadonlyModifier<V>, OptionalModifier<V>, Modifier<V> {
220
- optional: true,
221
- readonly: true,
222
- }
223
-
224
198
  /* ========================================================================== *
225
199
  * INFER OBJECT TYPE FROM SCHEMA *
226
200
  * ========================================================================== */
227
201
 
228
202
  /** Infer the type validated by a `Schema` */
229
203
  export type InferSchema<S extends Schema> =
230
- InferReadonlyModifiers<S> &
231
- InferOptionalModifiers<S> &
232
- InferCombinedModifiers<S> &
233
- ( S extends AdditionalProperties<Validator<infer V>> ?
234
- Record<string, V | undefined> &
235
- InferNever<S> &
236
- InferRequired<S> :
237
- InferRequired<S> )
238
-
239
- /* -------------------------------------------------------------------------- */
240
-
241
- /** Infer the type of keys associated with `Validation`s */
242
- export type InferRequired<S extends Schema> = {
243
- [ key in keyof S as
244
- key extends string ?
245
- S[key] extends Validation ? key :
246
- never :
247
- never
248
- ] :
249
- S[key] extends Validation ? InferValidation<S[key]> :
250
- never
251
- }
252
-
253
- /* -------------------------------------------------------------------------- */
254
-
255
- /** Infer the type of _read only_ `Schema` properties */
256
- export type InferReadonlyModifiers<S extends Schema> = {
257
- readonly [ key in keyof S as
258
- key extends string ?
259
- S[key] extends OptionalModifier<Validator> ? never :
260
- S[key] extends ReadonlyModifier<Validator> ? key :
261
- never :
262
- never
263
- ] :
264
- S[key] extends ReadonlyModifier<infer V> ? InferValidation<V> : never
265
- }
266
-
267
- /** Infer the type of _optional_ `Schema` properties */
268
- export type InferOptionalModifiers<S extends Schema> = {
269
- [ key in keyof S as
270
- key extends string ?
271
- S[key] extends ReadonlyModifier<Validator> ? never :
272
- S[key] extends OptionalModifier<Validator> ? key :
273
- never :
274
- never
275
- ] ? :
276
- S[key] extends OptionalModifier<infer V> ? InferValidation<V> : never
277
- }
278
-
279
- /** Infer the type of _read only_ **and** _optional_ `Schema` properties */
280
- export type InferCombinedModifiers<S extends Schema> = {
281
- readonly [ key in keyof S as
282
- key extends string ?
283
- S[key] extends CombinedModifier ? key :
284
- never :
285
- never
286
- ] ? :
287
- S[key] extends CombinedModifier<infer V> ? InferValidation<V> : never
288
- }
289
-
290
- /* -------------------------------------------------------------------------- */
291
-
292
- /** Ensure that we properly type `never` properties */
293
- export type InferNever<S extends Schema> =
294
- { [ key in keyof S as
295
- key extends string ?
296
- S[key] extends typeof never ? key :
297
- never :
298
- never
299
- ] : never
204
+ S extends AdditionalProperties<Validator<infer V>> ?
205
+ { [ key in string ] : V | undefined } & InferSchema2<S> :
206
+ InferSchema2<S>
207
+
208
+ /** Infer the property types described by a `Schema` */
209
+ export type InferSchema2<S extends Schema> =
210
+ { [ key in keyof S as key extends string ? key : never ]:
211
+ InferValidation<S[key]>
300
212
  }
301
213
 
214
+
302
215
  /* ========================================================================== *
303
216
  * TYPE BRANDING *
304
217
  * ========================================================================== */
package/src/utilities.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { any } from './validators/any'
2
1
  import { ConstantValidator, nullValidator } from './validators/constant'
3
- import { Schema, schemaValidator, Validation, Validator } from './types'
2
+ import { isValidator, Schema, schemaValidator, Validation, Validator } from './types'
4
3
  import { TupleValidator } from './validators/tuple'
5
4
  import { ObjectValidator } from './validators/object'
6
5
 
@@ -13,13 +12,12 @@ import { ObjectValidator } from './validators/object'
13
12
  *
14
13
  * When `validation` is `undefined` it will return a `Validator<any>`,
15
14
  */
16
- export function getValidator(validation?: Validation): Validator {
17
- // Undefined maps to `any`, null is a constant
18
- if (validation === undefined) return any
15
+ export function getValidator(validation: Validation): Validator {
16
+ // Null is a constant
19
17
  if (validation === null) return nullValidator
20
18
 
21
- // Validator instances are simply returned
22
- if (validation instanceof Validator) return validation
19
+ // Validator instance (either object or function)
20
+ if ((<any> validation)[isValidator] === true) return validation as Validator
23
21
 
24
22
  // Other types
25
23
  switch (typeof validation) {
@@ -29,10 +27,6 @@ export function getValidator(validation?: Validation): Validator {
29
27
  case 'number':
30
28
  return new ConstantValidator(validation)
31
29
 
32
- // validator generator
33
- case 'function':
34
- return validation()
35
-
36
30
  // other objects...
37
31
  case 'object':
38
32
  // pre-compiled schema with validator
@@ -1,7 +1,7 @@
1
- import { Validator } from '../types'
1
+ import { AbstractValidator } from '../types'
2
2
 
3
3
  /** A `Validator` validating _anything_. */
4
- export class AnyValidator extends Validator<any> {
4
+ export class AnyValidator extends AbstractValidator<any> {
5
5
  validate(value: unknown): any {
6
6
  return value
7
7
  }
@@ -1,8 +1,7 @@
1
1
  import { assertSchema, assertValidation, ValidationErrorBuilder } from '../errors'
2
- import { InferValidation, Validation, ValidationOptions, Validator } from '../types'
2
+ import { InferValidation, Validation, ValidationOptions, AbstractValidator, Validator, makeValidatorFactory } from '../types'
3
3
  import { getValidator } from '../utilities'
4
4
  import { any } from './any'
5
- import { makeTupleRestIterable } from './tuple'
6
5
 
7
6
  /* ========================================================================== *
8
7
  * ARRAYS VALIDATION *
@@ -21,7 +20,7 @@ export interface ArrayConstraints<V extends Validation> {
21
20
  }
22
21
 
23
22
  /** Basic validator for `Array` instances. */
24
- export class AnyArrayValidator<T = any> extends Validator<T[]> {
23
+ export class AnyArrayValidator<T = any> extends AbstractValidator<T[]> {
25
24
  validate(value: unknown, options: ValidationOptions): T[] {
26
25
  void options
27
26
  assertValidation(Array.isArray(value), 'Value is not an "array"')
@@ -30,7 +29,7 @@ export class AnyArrayValidator<T = any> extends Validator<T[]> {
30
29
  }
31
30
 
32
31
  /** A validator for `Array` instances with constraints. */
33
- export class ArrayValidator<T> extends Validator<T[]> {
32
+ export class ArrayValidator<T> extends AbstractValidator<T[]> {
34
33
  readonly maxItems: number
35
34
  readonly minItems: number
36
35
  readonly uniqueItems: boolean
@@ -87,22 +86,15 @@ export class ArrayValidator<T> extends Validator<T[]> {
87
86
  }
88
87
  }
89
88
 
90
- const anyArrayValidator = new AnyArrayValidator()
91
-
92
89
  /* -------------------------------------------------------------------------- */
93
90
 
94
- export function _array(): Validator<any[]>
95
- export function _array<V extends Validation>(constraints: ArrayConstraints<V>): ArrayValidator<InferValidation<V>>
96
-
97
- export function _array(options?: ArrayConstraints<Validation>): Validator<any[]> {
98
- if (! options) return anyArrayValidator
99
-
100
- const items = getValidator(options.items)
101
- return new ArrayValidator({ ...options, items })
91
+ export function _array<V extends Validation>(constraints: ArrayConstraints<V>): ArrayValidator<InferValidation<V>> {
92
+ const items = constraints.items ? getValidator(constraints.items) : any
93
+ return new ArrayValidator({ ...constraints, items })
102
94
  }
103
95
 
104
96
  /** Validate `Array`s. */
105
- export const array = makeTupleRestIterable(_array)
97
+ export const array = makeValidatorFactory(new AnyArrayValidator(), _array)
106
98
 
107
99
  /** Validate `Array`s containing only the specified elements. */
108
100
  export function arrayOf<V extends Validation>(validation: V): ArrayValidator<InferValidation<V>> {
@@ -1,13 +1,45 @@
1
- import { Validator } from '../types'
1
+ import { AbstractValidator, makeValidatorFactory } from '../types'
2
2
  import { assertValidation } from '../errors'
3
3
 
4
+ /** Constraints to validate a `boolean` with. */
5
+ export interface BooleanConstraints {
6
+ /**
7
+ * Allow booleans to be parsed from strings (default: `false`).
8
+ *
9
+ * The string in question _MUST_ be either `true` or `false`, and will be
10
+ * compared regardless of case.
11
+ */
12
+ fromString?: boolean,
13
+ }
14
+
15
+
4
16
  /** A `Validator` validating `boolean`s. */
5
- export class BooleanValidator extends Validator<boolean> {
17
+ export class BooleanValidator extends AbstractValidator<boolean> {
18
+ readonly fromString: boolean
19
+
20
+ constructor(constraints: BooleanConstraints = {}) {
21
+ super()
22
+ const { fromString = false } = constraints
23
+ this.fromString = fromString
24
+ }
25
+
6
26
  validate(value: unknown): boolean {
27
+ // Allow parsing from strings
28
+ if ((typeof value == 'string') && (this.fromString)) {
29
+ const string = value.toLowerCase()
30
+ const parsed = string === 'true' ? true : string === 'false' ? false : undefined
31
+ assertValidation(parsed !== undefined, 'Boolean can not be parsed from string')
32
+ value = parsed
33
+ }
34
+
7
35
  assertValidation(typeof value === 'boolean', 'Value is not a "boolean"')
8
36
  return value
9
37
  }
10
38
  }
11
39
 
40
+ export function _boolean(constraints: BooleanConstraints): BooleanValidator {
41
+ return new BooleanValidator(constraints)
42
+ }
43
+
12
44
  /** The `Validator` for `boolean`s. */
13
- export const boolean = new BooleanValidator()
45
+ export const boolean = makeValidatorFactory(new BooleanValidator(), _boolean)
@@ -1,8 +1,8 @@
1
- import { Validator } from '../types'
1
+ import { AbstractValidator, Validator } from '../types'
2
2
  import { assertValidation } from '../errors'
3
3
 
4
4
  /** A `Validator` for _constants_. */
5
- export class ConstantValidator<T extends string | number | boolean | null> extends Validator<T> {
5
+ export class ConstantValidator<T extends string | number | boolean | null> extends AbstractValidator<T> {
6
6
  readonly constant: T
7
7
 
8
8
  constructor(constant: T) {