justus 0.0.6 → 0.1.0

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.
@@ -0,0 +1,18 @@
1
+ import { ValidationError } from '../errors'
2
+ import { ValidationOptions, AbstractValidator } from '../types'
3
+
4
+ /** A `Validator` validating _nothing_. */
5
+ export class NeverValidator extends AbstractValidator<never> {
6
+ optional: true = true
7
+
8
+ validate(value: unknown, options: ValidationOptions): never {
9
+ const { stripForbiddenProperties } = options
10
+
11
+ // @ts-expect-error
12
+ if (stripForbiddenProperties || (value === undefined)) return
13
+ throw new ValidationError('Forbidden property')
14
+ }
15
+ }
16
+
17
+ /** The `Validator` validating _nothing_. */
18
+ export const never = new NeverValidator()
@@ -1,6 +1,5 @@
1
- import { Branding, Validator } from '../types'
1
+ import { Branding, Validator, AbstractValidator, makeValidatorFactory } from '../types'
2
2
  import { assertSchema, assertValidation } from '../errors'
3
- import { makeTupleRestIterable } from './tuple'
4
3
  import { ValidationError } from '..'
5
4
 
6
5
  /* ========================================================================== */
@@ -42,7 +41,7 @@ export interface BrandedNumberConstraints<B extends string> extends NumberConstr
42
41
  }
43
42
 
44
43
  /** A `Validator` validating any `number`. */
45
- export class AnyNumberValidator extends Validator<number> {
44
+ export class AnyNumberValidator extends AbstractValidator<number> {
46
45
  validate(value: unknown): number {
47
46
  assertValidation(typeof value == 'number', 'Value is not a "number"')
48
47
  assertValidation(! isNaN(value), 'Number is "NaN"')
@@ -51,7 +50,7 @@ export class AnyNumberValidator extends Validator<number> {
51
50
  }
52
51
 
53
52
  /** A `Validator` validating `number`s with constaints. */
54
- export class NumberValidator<N extends number = number> extends Validator<N> {
53
+ export class NumberValidator<N extends number = number> extends AbstractValidator<N> {
55
54
  #isMultipleOf?: ((value: number) => boolean)
56
55
 
57
56
  readonly allowNaN: boolean
@@ -148,16 +147,13 @@ export class NumberValidator<N extends number = number> extends Validator<N> {
148
147
  }
149
148
  }
150
149
 
151
- const anyNumberValidator = new AnyNumberValidator()
152
150
 
153
- export function _number(): Validator<number>
154
- export function _number(constraints?: NumberConstraints): NumberValidator<number>
155
- export function _number<N extends number>(constraints?: NumberConstraints): NumberValidator<N>
151
+ export function _number(constraints: NumberConstraints): NumberValidator<number>
152
+ export function _number<N extends number>(constraints: NumberConstraints): NumberValidator<N>
156
153
  export function _number<B extends string>(constraints: BrandedNumberConstraints<B>): NumberValidator<number & Branding<B>>
157
-
158
- export function _number(constraints?: NumberConstraints): Validator<number> {
159
- return constraints ? new NumberValidator(constraints) : anyNumberValidator
154
+ export function _number(constraints: NumberConstraints): Validator<number> {
155
+ return new NumberValidator(constraints)
160
156
  }
161
157
 
162
158
  /** Validate `number`s. */
163
- export const number = makeTupleRestIterable(_number)
159
+ export const number = makeValidatorFactory(new AnyNumberValidator(), _number)
@@ -4,29 +4,21 @@ import {
4
4
  TupleRestParameter,
5
5
  ValidationOptions,
6
6
  Validator,
7
+ AbstractValidator,
7
8
  additionalValidator,
8
- modifierValidator,
9
- never,
10
9
  restValidator,
11
10
  schemaValidator,
11
+ makeValidatorFactory,
12
12
  } from '../types'
13
13
  import { assertValidation, ValidationErrorBuilder } from '../errors'
14
14
  import { getValidator } from '../utilities'
15
- import { isModifier } from '../schema'
16
- import { makeTupleRestIterable } from './tuple'
17
15
 
18
16
  /* ========================================================================== *
19
17
  * OBJECT VALIDATOR *
20
18
  * ========================================================================== */
21
19
 
22
- export type ObjectProperty = {
23
- validator: Validator,
24
- readonly?: true,
25
- optional?: true,
26
- }
27
-
28
20
  /** A `Validator` validating any `object`. */
29
- export class AnyObjectValidator extends Validator<Record<string, any>> {
21
+ export class AnyObjectValidator extends AbstractValidator<Record<string, any>> {
30
22
  validate(value: unknown): Record<string, any> {
31
23
  assertValidation(typeof value == 'object', 'Value is not an "object"')
32
24
  assertValidation(value !== null, 'Value is "null"')
@@ -35,10 +27,10 @@ export class AnyObjectValidator extends Validator<Record<string, any>> {
35
27
  }
36
28
 
37
29
  /** A `Validator` validating `object`s according to a `Schema`. */
38
- export class ObjectValidator<S extends Schema> extends Validator<InferSchema<S>> {
30
+ export class ObjectValidator<S extends Schema> extends AbstractValidator<InferSchema<S>> {
39
31
  readonly schema: Readonly<S>
40
32
 
41
- properties = new Map<string, ObjectProperty | undefined>()
33
+ validators = new Map<string, Validator>()
42
34
  additionalProperties?: Validator
43
35
 
44
36
  constructor(schema: S) {
@@ -48,19 +40,7 @@ export class ObjectValidator<S extends Schema> extends Validator<InferSchema<S>>
48
40
  if (additional) this.additionalProperties = getValidator(additional)
49
41
 
50
42
  for (const key of Object.keys(properties)) {
51
- const definition = properties[key]
52
-
53
- if (definition === never) {
54
- this.properties.set(key, undefined)
55
- } else if (isModifier(definition)) {
56
- this.properties.set(key, {
57
- validator: definition[modifierValidator],
58
- readonly: definition.readonly,
59
- optional: definition.optional,
60
- })
61
- } else {
62
- this.properties.set(key, { validator: getValidator(definition) })
63
- }
43
+ this.validators.set(key, getValidator(properties[key]))
64
44
  }
65
45
 
66
46
  this.schema = schema
@@ -70,22 +50,14 @@ export class ObjectValidator<S extends Schema> extends Validator<InferSchema<S>>
70
50
  assertValidation(typeof value === 'object', 'Value is not an "object"')
71
51
  assertValidation(value !== null, 'Value is "null"')
72
52
 
73
- const { stripAdditionalProperties, stripForbiddenProperties, stripOptionalNulls } = options
53
+ const { stripAdditionalProperties, stripOptionalNulls } = options
74
54
 
75
55
  const record: { [ k in string | number | symbol ]?: unknown } = value
76
56
  const builder = new ValidationErrorBuilder()
77
57
  const clone: Record<string, any> = {}
78
58
 
79
- for (const [ key, property ] of this.properties.entries()) {
80
- const { validator, optional } = property || {}
81
-
82
- // no validator? this is "never" (forbidden)
83
- if (! validator) {
84
- if (record[key] === undefined) continue
85
- if (stripForbiddenProperties) continue
86
- builder.record('Forbidden property', key)
87
- continue
88
- }
59
+ for (const [ key, validator ] of this.validators.entries()) {
60
+ const optional = !! validator.optional
89
61
 
90
62
  // no value? might be optional, but definitely not validated
91
63
  if (record[key] === undefined) {
@@ -100,13 +72,14 @@ export class ObjectValidator<S extends Schema> extends Validator<InferSchema<S>>
100
72
 
101
73
  // all the rest gets validated normally
102
74
  try {
103
- clone[key] = validator.validate(record[key], options)
75
+ const value = validator.validate(record[key], options)
76
+ if (! (optional && (value == undefined))) clone[key] = value
104
77
  } catch (error) {
105
78
  builder.record(error, key)
106
79
  }
107
80
  }
108
81
 
109
- const additionalKeys = Object.keys(record).filter((k) => !this.properties.has(k))
82
+ const additionalKeys = Object.keys(record).filter((k) => !this.validators.has(k))
110
83
  const additional = this.additionalProperties
111
84
 
112
85
  if (additional) {
@@ -128,15 +101,9 @@ export class ObjectValidator<S extends Schema> extends Validator<InferSchema<S>>
128
101
  }
129
102
  }
130
103
 
131
- const anyObjectValidator = new AnyObjectValidator()
132
-
133
- export function _object(): Validator<Record<string, any>>
134
104
  export function _object<S extends Schema>(schema: S): S & {
135
105
  [Symbol.iterator](): Generator<TupleRestParameter<InferSchema<S>>>
136
- }
137
- export function _object(schema?: Schema): Validator<Record<string, any>> | Schema {
138
- if (! schema) return anyObjectValidator
139
-
106
+ } {
140
107
  const validator = new ObjectValidator(schema)
141
108
  function* iterator(): Generator<TupleRestParameter> {
142
109
  yield { [restValidator]: validator }
@@ -145,8 +112,8 @@ export function _object(schema?: Schema): Validator<Record<string, any>> | Schem
145
112
  return Object.defineProperties(schema, {
146
113
  [schemaValidator]: { value: validator, enumerable: false },
147
114
  [Symbol.iterator]: { value: iterator, enumerable: false },
148
- })
115
+ }) as any
149
116
  }
150
117
 
151
118
  /** Validate `object`s. */
152
- export const object = makeTupleRestIterable(_object)
119
+ export const object = makeValidatorFactory(new AnyObjectValidator(), _object)
@@ -0,0 +1,30 @@
1
+ import { AbstractValidator, Validator, ValidationOptions, Validation, InferValidation } from '../types'
2
+ import { getValidator } from '../utilities'
3
+
4
+ /**
5
+ * A `Validator` for _optional_ properties (that is `type | undefined`).
6
+ */
7
+ export class OptionalValidator<T = any> extends AbstractValidator<T | undefined> {
8
+ validator: Validator<T>
9
+ optional: true = true
10
+
11
+ constructor(validator: Validator<T>) {
12
+ super()
13
+ this.validator = validator
14
+ }
15
+
16
+ validate(value: unknown, options: ValidationOptions): T | undefined {
17
+ if (value === undefined) return value
18
+ return this.validator.validate(value, options)
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Ensure that the property is marked as _optional_ in the `Schema`.
24
+ *
25
+ * @param validation - A `Validation` to be marked as _optional_.
26
+ */
27
+ export function optional<V extends Validation>(validation: V): OptionalValidator<InferValidation<V>> {
28
+ const validator = getValidator(validation)
29
+ return new OptionalValidator(validator)
30
+ }
@@ -1,6 +1,5 @@
1
- import { Branding, Validator } from '../types'
1
+ import { Branding, Validator, AbstractValidator, makeValidatorFactory } from '../types'
2
2
  import { assertValidation, assertSchema } from '../errors'
3
- import { makeTupleRestIterable } from './tuple'
4
3
 
5
4
  /** Constraints to validate a `string` with. */
6
5
  export interface StringConstraints {
@@ -19,7 +18,7 @@ export interface BrandedStringConstraints<B extends string> extends StringConstr
19
18
  }
20
19
 
21
20
  /** A `Validator` validating any `string`. */
22
- export class AnyStringValidator extends Validator<string> {
21
+ export class AnyStringValidator extends AbstractValidator<string> {
23
22
  validate(value: unknown): string {
24
23
  assertValidation(typeof value == 'string', 'Value is not a "string"')
25
24
  return value
@@ -27,7 +26,7 @@ export class AnyStringValidator extends Validator<string> {
27
26
  }
28
27
 
29
28
  /** A `Validator` validating `string`s with constraints. */
30
- export class StringValidator<S extends string = string> extends Validator<S> {
29
+ export class StringValidator<S extends string = string> extends AbstractValidator<S> {
31
30
  readonly maxLength: number
32
31
  readonly minLength: number
33
32
  readonly pattern?: RegExp
@@ -69,16 +68,12 @@ export class StringValidator<S extends string = string> extends Validator<S> {
69
68
  }
70
69
  }
71
70
 
72
- const anyStringValidator = new AnyStringValidator()
73
-
74
- export function _string(): Validator<string>
75
- export function _string(constraints?: StringConstraints): StringValidator<string>
76
- export function _string<S extends string>(constraints?: StringConstraints): StringValidator<S>
71
+ export function _string(constraints: StringConstraints): StringValidator<string>
72
+ export function _string<S extends string>(constraints: StringConstraints): StringValidator<S>
77
73
  export function _string<B extends string>(constraints: BrandedStringConstraints<B>): StringValidator<string & Branding<B>>
78
-
79
- export function _string(constraints?: StringConstraints): Validator<string> {
80
- return constraints ? new StringValidator(constraints) : anyStringValidator
74
+ export function _string(constraints: StringConstraints): Validator<string> {
75
+ return new StringValidator(constraints)
81
76
  }
82
77
 
83
78
  /** Validate `string`s. */
84
- export const string = makeTupleRestIterable(_string)
79
+ export const string = makeValidatorFactory(new AnyStringValidator(), _string)
@@ -1,4 +1,4 @@
1
- import { Tuple, InferTuple, Validator, TupleRestParameter, InferValidation, restValidator } from '../types'
1
+ import { Tuple, InferTuple, Validator, AbstractValidator, restValidator } from '../types'
2
2
  import { ValidationOptions } from '../types'
3
3
  import { assertValidation, ValidationError } from '../errors'
4
4
  import { getValidator } from '../utilities'
@@ -7,7 +7,7 @@ import { nullValidator } from './constant'
7
7
  export interface TupleMember { single: boolean, validator: Validator }
8
8
 
9
9
  /** A `Validator` for _tuples_. */
10
- export class TupleValidator<T extends Tuple> extends Validator<InferTuple<T>> {
10
+ export class TupleValidator<T extends Tuple> extends AbstractValidator<InferTuple<T>> {
11
11
  readonly members: readonly TupleMember[]
12
12
  readonly tuple: T
13
13
 
@@ -78,13 +78,3 @@ export class TupleValidator<T extends Tuple> extends Validator<InferTuple<T>> {
78
78
  export function tuple<T extends Tuple>(tuple: T): Validator<InferTuple<T>> {
79
79
  return new TupleValidator(tuple)
80
80
  }
81
-
82
- export function makeTupleRestIterable<
83
- F extends () => Validator,
84
- >(create: F): F & Iterable<TupleRestParameter<InferValidation<F>>> {
85
- const validator = create()
86
- ;(<any>create)[Symbol.iterator] = function* (): Generator<TupleRestParameter<InferValidation<F>>> {
87
- yield { [restValidator]: validator }
88
- }
89
- return create as any
90
- }
@@ -5,6 +5,7 @@ import {
5
5
  Validation,
6
6
  ValidationOptions,
7
7
  Validator,
8
+ AbstractValidator,
8
9
  } from '../types'
9
10
 
10
11
  export type UnionArguments = readonly [ Validation, ...Validation[] ]
@@ -21,7 +22,7 @@ export type InferOneOfValidationType<A extends UnionArguments> =
21
22
  never
22
23
 
23
24
  /** A `Validator` validating a value as _one of_ the specified arguments. */
24
- export class OneOfValidator<A extends UnionArguments> extends Validator<InferOneOfValidationType<A>> {
25
+ export class OneOfValidator<A extends UnionArguments> extends AbstractValidator<InferOneOfValidationType<A>> {
25
26
  readonly validators: readonly Validator[]
26
27
 
27
28
  constructor(args: A) {
@@ -59,7 +60,7 @@ export type InferAllOfValidationType<A extends UnionArguments> =
59
60
  never
60
61
 
61
62
  /** A `Validator` validating a value as _all of_ the specified arguments. */
62
- export class AllOfValidator<A extends UnionArguments> extends Validator<InferAllOfValidationType<A>> {
63
+ export class AllOfValidator<A extends UnionArguments> extends AbstractValidator<InferAllOfValidationType<A>> {
63
64
  readonly validators: readonly Validator[]
64
65
 
65
66
  constructor(args: A) {
@@ -1,7 +1,6 @@
1
1
  import { ConstantValidator } from './constant'
2
2
  import { ValidationError, ValidationErrorBuilder } from '../errors'
3
- import { Schema, Validator } from '../types'
4
- import { makeTupleRestIterable } from './tuple'
3
+ import { Schema, Validator, AbstractValidator, makeValidatorFactory } from '../types'
5
4
  import { ObjectValidator, ValidationOptions } from '..'
6
5
 
7
6
  const KEYS: Exclude<keyof URLConstraints, 'searchParams'>[] = [
@@ -58,7 +57,7 @@ export interface URLConstraints {
58
57
  }
59
58
 
60
59
  /** A `Validator` validating URLs and converting them to `URL` instances. */
61
- export class URLValidator extends Validator<URL> {
60
+ export class URLValidator extends AbstractValidator<URL> {
62
61
  readonly href?: Validator<string>
63
62
  readonly origin?: Validator<string>
64
63
  readonly protocol?: Validator<string>
@@ -130,12 +129,9 @@ export class URLValidator extends Validator<URL> {
130
129
 
131
130
  const anyURLValidator = new URLValidator()
132
131
 
133
- export function _url(): URLValidator
134
- export function _url(constraints: URLConstraints): URLValidator
135
-
136
- export function _url(constraints?: URLConstraints): URLValidator {
137
- return constraints ? new URLValidator(constraints) : anyURLValidator
132
+ export function _url(constraints: URLConstraints): URLValidator {
133
+ return new URLValidator(constraints)
138
134
  }
139
135
 
140
136
  /** Validate URLs and convert them to `URL` instances. */
141
- export const url = makeTupleRestIterable(_url)
137
+ export const url = makeValidatorFactory(anyURLValidator, _url)