justus 0.0.2 → 0.0.6

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/src/index.ts CHANGED
@@ -9,16 +9,17 @@ export * from './types'
9
9
  export * from './utilities'
10
10
 
11
11
  // Validators
12
- export { allOf, oneOf, AllOfValidator, OneOfValidator } from './validators/union'
12
+ export { allOf, oneOf, AllOfValidator, InferAllOfValidationType, InferOneOfValidationType, OneOfValidator, UnionArguments } from './validators/union'
13
13
  export { any, AnyValidator } from './validators/any'
14
- export { array, arrayOf, AnyArrayValidator, ArrayValidator } from './validators/array'
14
+ export { _array, array, arrayOf, AnyArrayValidator, ArrayConstraints, ArrayValidator } from './validators/array'
15
15
  export { boolean, BooleanValidator } from './validators/boolean'
16
16
  export { constant, ConstantValidator } from './validators/constant'
17
- export { date, DateValidator } from './validators/date'
18
- export { number, AnyNumberValidator, NumberValidator } from './validators/number'
19
- export { object, AnyObjectValidator, ObjectValidator } from './validators/object'
20
- export { string, AnyStringValidator, StringValidator } from './validators/string'
21
- export { tuple, TupleValidator } from './validators/tuple'
17
+ export { _date, date, DateConstraints, DateValidator } from './validators/date'
18
+ export { _number, number, AnyNumberValidator, BrandedNumberConstraints, NumberConstraints, NumberValidator } from './validators/number'
19
+ export { _object, object, AnyObjectValidator, ObjectProperty, ObjectValidator } from './validators/object'
20
+ export { _string, string, AnyStringValidator, BrandedStringConstraints, StringConstraints, StringValidator } from './validators/string'
21
+ export { tuple, TupleMember, TupleValidator } from './validators/tuple'
22
+ export { _url, url, URLConstraints, URLValidator } from './validators/url'
22
23
 
23
24
  /* ========================================================================== *
24
25
  * VALIDATE FUNCTION (our main entry point) *
@@ -32,7 +33,12 @@ export type ValidateOptions = {
32
33
  -readonly [ key in keyof ValidationOptions ]?: ValidationOptions[key] | undefined
33
34
  }
34
35
 
35
- /** Validate a _value_ using the specified `Validation` */
36
+ /**
37
+ * Validate a _value_ using the specified `Validation`.
38
+ *
39
+ * By default additional and forbidden properties will _not_ be stripped and
40
+ * reported as an error.
41
+ */
36
42
  export function validate<V extends Validation>(
37
43
  validation: V,
38
44
  value: any,
@@ -41,6 +47,36 @@ export function validate<V extends Validation>(
41
47
  const opts: ValidationOptions = {
42
48
  stripAdditionalProperties: false,
43
49
  stripForbiddenProperties: false,
50
+ stripOptionalNulls: false,
51
+ ...options,
52
+ }
53
+
54
+ return getValidator(validation).validate(value, opts)
55
+ }
56
+
57
+ /**
58
+ * Validate a _value_ using the specified `Validation`, automatically stripping
59
+ * additional properties and optional `null`s (but not forbidden ones).
60
+ *
61
+ * This is equivalent to:
62
+ *
63
+ * ```
64
+ * validate(validation, value, {
65
+ * stripAdditionalProperties: true,
66
+ * stripForbiddenProperties: false,
67
+ * stripOptionalNulls: true,
68
+ * })
69
+ * ```
70
+ */
71
+ export function strip<V extends Validation>(
72
+ validation: V,
73
+ value: any,
74
+ options: ValidateOptions = {},
75
+ ): InferValidation<V> {
76
+ const opts: ValidationOptions = {
77
+ stripAdditionalProperties: true,
78
+ stripForbiddenProperties: false,
79
+ stripOptionalNulls: true,
44
80
  ...options,
45
81
  }
46
82
 
package/src/schema.ts CHANGED
@@ -19,12 +19,12 @@ import {
19
19
  * ========================================================================== */
20
20
 
21
21
  /** Internal definition of `allowAdditionalProperties(...)` */
22
- function _allowAdditionalProperties(): AdditionalProperties<Validator<any>>
23
- function _allowAdditionalProperties(allow: true): AdditionalProperties<Validator<any>>
24
- function _allowAdditionalProperties(allow: false): AdditionalProperties<false>
25
- function _allowAdditionalProperties<V extends Validation>(validation: V): AdditionalProperties<Validator<InferValidation<V>>>
22
+ export function _allowAdditionalProperties(): AdditionalProperties<Validator<any>>
23
+ export function _allowAdditionalProperties(allow: true): AdditionalProperties<Validator<any>>
24
+ export function _allowAdditionalProperties(allow: false): AdditionalProperties<false>
25
+ export function _allowAdditionalProperties<V extends Validation>(validation: V): AdditionalProperties<Validator<InferValidation<V>>>
26
26
 
27
- function _allowAdditionalProperties(options?: Validation | boolean): AdditionalProperties<Validator | false> {
27
+ export function _allowAdditionalProperties(options?: Validation | boolean): AdditionalProperties<Validator | false> {
28
28
  if (options === false) return { [additionalValidator]: false }
29
29
  if (options === true) return { [additionalValidator]: any }
30
30
 
@@ -49,7 +49,7 @@ allowAdditionalProperties[additionalValidator] = any
49
49
  * SCHEMA KEYS MODIFIERS *
50
50
  * ========================================================================== */
51
51
 
52
- type CombineModifiers<M1 extends Modifier, M2 extends Modifier> =
52
+ export type CombineModifiers<M1 extends Modifier, M2 extends Modifier> =
53
53
  M1 extends ReadonlyModifier ?
54
54
  M2 extends ReadonlyModifier<infer V> ? ReadonlyModifier<V> :
55
55
  M2 extends OptionalModifier<infer V> ? CombinedModifier<V> :
package/src/types.ts CHANGED
@@ -27,6 +27,8 @@ export const never = Symbol.for('justus.never')
27
27
  export interface ValidationOptions {
28
28
  /** Strip additional, undeclared properties from objects */
29
29
  readonly stripAdditionalProperties: boolean,
30
+ /** Strip `null`s from an object when associated with an optional key */
31
+ readonly stripOptionalNulls: boolean,
30
32
  /** Ignore and strip forbidden (`never`) properties from objects */
31
33
  readonly stripForbiddenProperties: boolean,
32
34
  }
@@ -115,7 +117,7 @@ export type InferValidation<V> =
115
117
  * ========================================================================== */
116
118
 
117
119
  /** Infer the type validated by a `Validation` or `TupleRestParameter` */
118
- type InferValidationOrTupleRest<T> =
120
+ export type InferValidationOrTupleRest<T> =
119
121
  T extends TupleRestParameter<infer X> ? X :
120
122
  T extends Validation ? InferValidation<T> :
121
123
  never
@@ -237,7 +239,7 @@ export type InferSchema<S extends Schema> =
237
239
  /* -------------------------------------------------------------------------- */
238
240
 
239
241
  /** Infer the type of keys associated with `Validation`s */
240
- type InferRequired<S extends Schema> = {
242
+ export type InferRequired<S extends Schema> = {
241
243
  [ key in keyof S as
242
244
  key extends string ?
243
245
  S[key] extends Validation ? key :
@@ -251,7 +253,7 @@ type InferRequired<S extends Schema> = {
251
253
  /* -------------------------------------------------------------------------- */
252
254
 
253
255
  /** Infer the type of _read only_ `Schema` properties */
254
- type InferReadonlyModifiers<S extends Schema> = {
256
+ export type InferReadonlyModifiers<S extends Schema> = {
255
257
  readonly [ key in keyof S as
256
258
  key extends string ?
257
259
  S[key] extends OptionalModifier<Validator> ? never :
@@ -263,7 +265,7 @@ type InferReadonlyModifiers<S extends Schema> = {
263
265
  }
264
266
 
265
267
  /** Infer the type of _optional_ `Schema` properties */
266
- type InferOptionalModifiers<S extends Schema> = {
268
+ export type InferOptionalModifiers<S extends Schema> = {
267
269
  [ key in keyof S as
268
270
  key extends string ?
269
271
  S[key] extends ReadonlyModifier<Validator> ? never :
@@ -275,7 +277,7 @@ type InferOptionalModifiers<S extends Schema> = {
275
277
  }
276
278
 
277
279
  /** Infer the type of _read only_ **and** _optional_ `Schema` properties */
278
- type InferCombinedModifiers<S extends Schema> = {
280
+ export type InferCombinedModifiers<S extends Schema> = {
279
281
  readonly [ key in keyof S as
280
282
  key extends string ?
281
283
  S[key] extends CombinedModifier ? key :
@@ -288,7 +290,7 @@ type InferCombinedModifiers<S extends Schema> = {
288
290
  /* -------------------------------------------------------------------------- */
289
291
 
290
292
  /** Ensure that we properly type `never` properties */
291
- type InferNever<S extends Schema> =
293
+ export type InferNever<S extends Schema> =
292
294
  { [ key in keyof S as
293
295
  key extends string ?
294
296
  S[key] extends typeof never ? key :
@@ -91,10 +91,10 @@ const anyArrayValidator = new AnyArrayValidator()
91
91
 
92
92
  /* -------------------------------------------------------------------------- */
93
93
 
94
- function _array(): Validator<any[]>
95
- function _array<V extends Validation>(constraints: ArrayConstraints<V>): ArrayValidator<InferValidation<V>>
94
+ export function _array(): Validator<any[]>
95
+ export function _array<V extends Validation>(constraints: ArrayConstraints<V>): ArrayValidator<InferValidation<V>>
96
96
 
97
- function _array(options?: ArrayConstraints<Validation>): Validator<any[]> {
97
+ export function _array(options?: ArrayConstraints<Validation>): Validator<any[]> {
98
98
  if (! options) return anyArrayValidator
99
99
 
100
100
  const items = getValidator(options.items)
@@ -67,10 +67,10 @@ export class DateValidator extends Validator<Date> {
67
67
 
68
68
  const anyDateValidator = new DateValidator()
69
69
 
70
- function _date(): DateValidator
71
- function _date(constraints: DateConstraints): DateValidator
70
+ export function _date(): DateValidator
71
+ export function _date(constraints: DateConstraints): DateValidator
72
72
 
73
- function _date(constraints?: DateConstraints): DateValidator {
73
+ export function _date(constraints?: DateConstraints): DateValidator {
74
74
  return constraints ? new DateValidator(constraints) : anyDateValidator
75
75
  }
76
76
 
@@ -150,12 +150,12 @@ export class NumberValidator<N extends number = number> extends Validator<N> {
150
150
 
151
151
  const anyNumberValidator = new AnyNumberValidator()
152
152
 
153
- function _number(): Validator<number>
154
- function _number(constraints?: NumberConstraints): NumberValidator<number>
155
- function _number<N extends number>(constraints?: NumberConstraints): NumberValidator<N>
156
- function _number<B extends string>(constraints: BrandedNumberConstraints<B>): NumberValidator<number & Branding<B>>
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>
156
+ export function _number<B extends string>(constraints: BrandedNumberConstraints<B>): NumberValidator<number & Branding<B>>
157
157
 
158
- function _number(constraints?: NumberConstraints): Validator<number> {
158
+ export function _number(constraints?: NumberConstraints): Validator<number> {
159
159
  return constraints ? new NumberValidator(constraints) : anyNumberValidator
160
160
  }
161
161
 
@@ -19,7 +19,7 @@ import { makeTupleRestIterable } from './tuple'
19
19
  * OBJECT VALIDATOR *
20
20
  * ========================================================================== */
21
21
 
22
- type ObjectProperty = {
22
+ export type ObjectProperty = {
23
23
  validator: Validator,
24
24
  readonly?: true,
25
25
  optional?: true,
@@ -70,6 +70,8 @@ export class ObjectValidator<S extends Schema> extends Validator<InferSchema<S>>
70
70
  assertValidation(typeof value === 'object', 'Value is not an "object"')
71
71
  assertValidation(value !== null, 'Value is "null"')
72
72
 
73
+ const { stripAdditionalProperties, stripForbiddenProperties, stripOptionalNulls } = options
74
+
73
75
  const record: { [ k in string | number | symbol ]?: unknown } = value
74
76
  const builder = new ValidationErrorBuilder()
75
77
  const clone: Record<string, any> = {}
@@ -80,7 +82,7 @@ export class ObjectValidator<S extends Schema> extends Validator<InferSchema<S>>
80
82
  // no validator? this is "never" (forbidden)
81
83
  if (! validator) {
82
84
  if (record[key] === undefined) continue
83
- if (options.stripForbiddenProperties) continue
85
+ if (stripForbiddenProperties) continue
84
86
  builder.record('Forbidden property', key)
85
87
  continue
86
88
  }
@@ -91,6 +93,11 @@ export class ObjectValidator<S extends Schema> extends Validator<InferSchema<S>>
91
93
  continue
92
94
  }
93
95
 
96
+ // strip any optional "null" value if told to do so
97
+ if (stripOptionalNulls && optional && (record[key] === null)) {
98
+ continue
99
+ }
100
+
94
101
  // all the rest gets validated normally
95
102
  try {
96
103
  clone[key] = validator.validate(record[key], options)
@@ -111,7 +118,7 @@ export class ObjectValidator<S extends Schema> extends Validator<InferSchema<S>>
111
118
  builder.record(error, key)
112
119
  }
113
120
  })
114
- } else if (! options.stripAdditionalProperties) {
121
+ } else if (! stripAdditionalProperties) {
115
122
  additionalKeys.forEach((key) => {
116
123
  if (record[key] !== undefined) builder.record('Unknown property', key)
117
124
  })
@@ -123,11 +130,11 @@ export class ObjectValidator<S extends Schema> extends Validator<InferSchema<S>>
123
130
 
124
131
  const anyObjectValidator = new AnyObjectValidator()
125
132
 
126
- function _object(): Validator<Record<string, any>>
127
- function _object<S extends Schema>(schema: S): S & {
133
+ export function _object(): Validator<Record<string, any>>
134
+ export function _object<S extends Schema>(schema: S): S & {
128
135
  [Symbol.iterator](): Generator<TupleRestParameter<InferSchema<S>>>
129
136
  }
130
- function _object(schema?: Schema): Validator<Record<string, any>> | Schema {
137
+ export function _object(schema?: Schema): Validator<Record<string, any>> | Schema {
131
138
  if (! schema) return anyObjectValidator
132
139
 
133
140
  const validator = new ObjectValidator(schema)
@@ -71,12 +71,12 @@ export class StringValidator<S extends string = string> extends Validator<S> {
71
71
 
72
72
  const anyStringValidator = new AnyStringValidator()
73
73
 
74
- function _string(): Validator<string>
75
- function _string(constraints?: StringConstraints): StringValidator<string>
76
- function _string<S extends string>(constraints?: StringConstraints): StringValidator<S>
77
- function _string<B extends string>(constraints: BrandedStringConstraints<B>): StringValidator<string & Branding<B>>
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>
77
+ export function _string<B extends string>(constraints: BrandedStringConstraints<B>): StringValidator<string & Branding<B>>
78
78
 
79
- function _string(constraints?: StringConstraints): Validator<string> {
79
+ export function _string(constraints?: StringConstraints): Validator<string> {
80
80
  return constraints ? new StringValidator(constraints) : anyStringValidator
81
81
  }
82
82
 
@@ -4,7 +4,7 @@ import { assertValidation, ValidationError } from '../errors'
4
4
  import { getValidator } from '../utilities'
5
5
  import { nullValidator } from './constant'
6
6
 
7
- interface TupleMember { single: boolean, validator: Validator }
7
+ export interface TupleMember { single: boolean, validator: Validator }
8
8
 
9
9
  /** A `Validator` for _tuples_. */
10
10
  export class TupleValidator<T extends Tuple> extends Validator<InferTuple<T>> {
@@ -7,11 +7,11 @@ import {
7
7
  Validator,
8
8
  } from '../types'
9
9
 
10
- type UnionArguments = readonly [ Validation, ...Validation[] ]
10
+ export type UnionArguments = readonly [ Validation, ...Validation[] ]
11
11
 
12
12
  /* -------------------------------------------------------------------------- */
13
13
 
14
- type InferOneOfValidationType<A extends UnionArguments> =
14
+ export type InferOneOfValidationType<A extends UnionArguments> =
15
15
  A extends readonly [ infer First, ...infer Rest ] ?
16
16
  First extends Validation ?
17
17
  Rest extends UnionArguments ?
@@ -49,7 +49,7 @@ export function oneOf<A extends UnionArguments>(...args: A): OneOfValidator<A> {
49
49
 
50
50
  /* -------------------------------------------------------------------------- */
51
51
 
52
- type InferAllOfValidationType<A extends UnionArguments> =
52
+ export type InferAllOfValidationType<A extends UnionArguments> =
53
53
  A extends readonly [ infer First, ...infer Rest ] ?
54
54
  First extends Validation ?
55
55
  Rest extends UnionArguments ?
@@ -0,0 +1,141 @@
1
+ import { ConstantValidator } from './constant'
2
+ import { ValidationError, ValidationErrorBuilder } from '../errors'
3
+ import { Schema, Validator } from '../types'
4
+ import { makeTupleRestIterable } from './tuple'
5
+ import { ObjectValidator, ValidationOptions } from '..'
6
+
7
+ const KEYS: Exclude<keyof URLConstraints, 'searchParams'>[] = [
8
+ 'href',
9
+ 'origin',
10
+ 'protocol',
11
+ 'username',
12
+ 'password',
13
+ 'host',
14
+ 'hostname',
15
+ 'port',
16
+ 'pathname',
17
+ 'search',
18
+ 'hash',
19
+ ]
20
+
21
+ const OPTIONS: ValidationOptions = {
22
+ stripAdditionalProperties: false,
23
+ stripForbiddenProperties: false,
24
+ stripOptionalNulls: false,
25
+ }
26
+
27
+ /** Constraints to validate a `URL` with. */
28
+ export interface URLConstraints {
29
+ /** Constraint to validate the `href` component of the `URL`. */
30
+ href?: string | Validator<string>,
31
+ /** Constraint to validate the `origin` component of the `URL`. */
32
+ origin?: string | Validator<string>,
33
+ /** Constraint to validate the `protocol` component of the `URL`. */
34
+ protocol?: string | Validator<string>,
35
+ /** Constraint to validate the `username` component of the `URL`. */
36
+ username?: string | Validator<string>,
37
+ /** Constraint to validate the `password` component of the `URL`. */
38
+ password?: string | Validator<string>,
39
+ /** Constraint to validate the `host` (`hostname:port`) component of the `URL`. */
40
+ host?: string | Validator<string>,
41
+ /** Constraint to validate the `hostname` component of the `URL`. */
42
+ hostname?: string | Validator<string>,
43
+ /** Constraint to validate the `port` component of the `URL`. */
44
+ port?: string | Validator<string>,
45
+ /** Constraint to validate the `pathname` component of the `URL`. */
46
+ pathname?: string | Validator<string>,
47
+ /** Constraint to validate the `search` component of the `URL` as a string. */
48
+ search?: string | Validator<string>,
49
+ /** Constraint to validate the `hash` component of the `URL`. */
50
+ hash?: string | Validator<string>,
51
+ /**
52
+ * Schema used to validate the `searchParams` component of the `URL`.
53
+ *
54
+ * The `searchParams` will be normalized in a `Record<string, string>`, where
55
+ * only the _first_ value associated with a search parameter will be checked.
56
+ */
57
+ searchParams?: Schema,
58
+ }
59
+
60
+ /** A `Validator` validating URLs and converting them to `URL` instances. */
61
+ export class URLValidator extends Validator<URL> {
62
+ readonly href?: Validator<string>
63
+ readonly origin?: Validator<string>
64
+ readonly protocol?: Validator<string>
65
+ readonly username?: Validator<string>
66
+ readonly password?: Validator<string>
67
+ readonly host?: Validator<string>
68
+ readonly hostname?: Validator<string>
69
+ readonly port?: Validator<string>
70
+ readonly pathname?: Validator<string>
71
+ readonly search?: Validator<string>
72
+ readonly hash?: Validator<string>
73
+
74
+ readonly searchParams?: ObjectValidator<Schema>
75
+
76
+ constructor(constraints: URLConstraints = {}) {
77
+ super()
78
+
79
+ for (const key of KEYS) {
80
+ const constraint = constraints[key]
81
+ if (typeof constraint === 'string') {
82
+ this[key] = new ConstantValidator(constraint)
83
+ } else if (constraint) {
84
+ this[key] = constraint
85
+ }
86
+ }
87
+
88
+ if (constraints.searchParams) {
89
+ this.searchParams = new ObjectValidator(constraints.searchParams)
90
+ }
91
+ }
92
+
93
+ validate(value: unknown): URL {
94
+ let url: URL
95
+ try {
96
+ url = value instanceof URL ? value : new URL(value as any)
97
+ } catch (error) {
98
+ throw new ValidationError('Value could not be converted to a "URL"')
99
+ }
100
+
101
+ const builder = new ValidationErrorBuilder()
102
+
103
+ for (const key of KEYS) {
104
+ const validator = this[key]
105
+ if (validator) {
106
+ try {
107
+ validator.validate(url[key], OPTIONS)
108
+ } catch (error) {
109
+ builder.record(error, key)
110
+ }
111
+ }
112
+ }
113
+
114
+ if (this.searchParams) {
115
+ const parameters: Record<string, string> = {}
116
+ for (const param of url.searchParams.keys()) {
117
+ parameters[param] = url.searchParams.get(param) as string
118
+ }
119
+
120
+ try {
121
+ this.searchParams.validate(parameters, OPTIONS)
122
+ } catch (error) {
123
+ builder.record(error, 'searchParams')
124
+ }
125
+ }
126
+
127
+ return builder.assert(url)
128
+ }
129
+ }
130
+
131
+ const anyURLValidator = new URLValidator()
132
+
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
138
+ }
139
+
140
+ /** Validate URLs and convert them to `URL` instances. */
141
+ export const url = makeTupleRestIterable(_url)