0.13.0 • Published 2 years ago

@spec-validator/validator v0.13.0

Weekly downloads
171
License
Apache-2.0
Repository
github
Last release
2 years ago

@spec-validator/validator

Core API

Fields can be defined by implementing a Field interface via a declareField function.

The first parameteter of declareField is a string value representing field type and the second parameter is a function that aims to construct a field object.

declareField returns a function with the same interface as the constructor one passed as the second parameter.

import assert from 'assert'

import { Field, declareField } from '@spec-validator/validator/core'

const DAYS = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'] as const

const choices = new Set(DAYS)

type Day = typeof DAYS[number]

const dayField = declareField('DateField', (): Field<Day> => ({
  validate: (value: any): Day => {
    if (!choices.has(value)) {
      throw 'Invalid day'
    }
    return value
  },
  serialize: (deserialized) => deserialized,
}))

Define a schema:

const schema = {
  day: dayField(),
}

Validate and serialize payload:

import { validate, serialize } from '@spec-validator/validator'

const deserialized = validate(schema, { day: 'MO' })

assert.deepStrictEqual(
  deserialized,
  {
    day: 'MO'
  }
)

const serialized = serialize(schema, deserialized)

assert.deepStrictEqual(
  serialized,
  {
    day: 'MO'
  }
)

Errors are reported in a structured form with a path outlining location of the error.

let error: any = undefined

try {
  validate(schema, {
    day: 'Abracadabra',
  })
} catch (err) {
  error = err
}

assert.deepStrictEqual(error, {
  inner: "Invalid day",
  path: [
    'day'
  ]
})

Infer TypeScript type from schema:

import { TypeHint } from '@spec-validator/validator'
import { expectType } from '@spec-validator/test-utils/expectType'

type Schema = TypeHint<typeof schema>

expectType<Schema, {
  day: Day
}>(true)

Fields

Primitive fields

import {
  booleanField, numberField, stringField
} from '@spec-validator/validator/fields'

const primitiveSpec = {
  flag: booleanField(),
  title: stringField(),
  // regex for email
  email: stringField(/\S+@\S+\.\S+/),
  count: numberField(),
  weight: numberField({ canBeFloat: true }),
  rating: numberField({ signed: true }),
}

type PrimitiveType = TypeHint<typeof primitiveSpec>

const valid: PrimitiveType = validate(primitiveSpec, {
  flag: true,
  title: 'Title',
  email: 'test@example.com',
  count: 12,
  weight: 16.33,
  rating: -10,
})

assert.deepStrictEqual(valid, {
  flag: true,
  title: 'Title',
  email: 'test@example.com',
  count: 12,
  weight: 16.33,
  rating: -10,
})

Collection fields

Nested objects and arrays can be outlined as follows:

const complexSpec = {
  firstLevel: stringField(),
  innerObj: {
    secondLevel: stringField(),
    array: [{
      itemsThirdLevel: stringField()
    }]
  }
}

const validNestedObj = validate(complexSpec, {
  firstLevel: 'First level',
  innerObj: {
    secondLevel: 'Second level',
    array: [
      {
        itemsThirdLevel: 'Third level #1'
      }, {
        itemsThirdLevel: 'Third level #2'
      }
    ]
  }
})

assert.deepStrictEqual(!!validNestedObj, true)

Note, when it comes to array specification only the first element in the array is taken into consideration as a spec fo array items.

Auxilary fields

In addition to primitive and collection fields there are also ones that aim to match more extensive type checking.

optional

Once annotated, a payload becomes T | undefined:

import { optional } from '@spec-validator/validator/fields'

const optionalField = optional(numberField())

expectType<TypeHint<typeof optionalField>, number | undefined>(true)

assert.deepStrictEqual(validate(optionalField, 42), 42)
assert.deepStrictEqual(validate(optionalField, undefined), undefined)

choiceField

A union type that may have only primitive types' based constants:

import { choiceField } from '@spec-validator/validator/fields'

const withChoices = choiceField(1, 2, 3)

expectType<TypeHint<typeof withChoices>, 1 | 2 | 3>(true)

assert.deepStrictEqual(validate(withChoices, 1), 1)

dateField

A mapping between JavaScript-friendly string date value and a Date object.

import { dateField } from '@spec-validator/validator/fields'

const date = dateField()

expectType<TypeHint<typeof date>, Date>(true)

assert.deepStrictEqual(validate(date, '1995-12-17T03:24:00Z'), new Date('1995-12-17T03:24:00Z'))

assert.deepStrictEqual(serialize(dateField(), new Date('1995-12-17T03:24:00Z')), '1995-12-17T03:24:00.000Z')
assert.deepStrictEqual(serialize(dateField('date'), new Date('1995-12-17T03:24:00Z')), '1995-12-17')
assert.deepStrictEqual(serialize(dateField('time'), new Date('1995-12-17T03:24:00Z')), '03:24:00.000Z')

constantField

Similar to choiceField but only with a single valid choice

import { constantField } from '@spec-validator/validator/fields'

const constant = constantField(11)

expectType<TypeHint<typeof constant>, 11>(true)

assert.deepStrictEqual(validate(constant, 11), 11)

withDefault

Similar to optional but instead of returning undefined in case of a missing field.

import { withDefault } from '@spec-validator/validator/fields'

const defaultValue = withDefault(numberField(), 42 as number)

expectType<TypeHint<typeof defaultValue>, number>(true)

assert.deepStrictEqual(validate(defaultValue, 11), 11)
assert.deepStrictEqual(validate(defaultValue, undefined), 42)

unionField

A field for the cases when an object can have multiple structures (i.e. union of objects).

import { unionField } from '@spec-validator/validator/fields'

const union = unionField(
  {
    innerFieldV1: stringField(),
  },
  {
    innerFieldV2: numberField(),
  }
)

expectType<TypeHint<typeof union>, {
  innerFieldV1: string
} | {
  innerFieldV2: number
}>(true)

assert.deepStrictEqual(validate(union, {
  innerFieldV1: 'value'
}), {
  innerFieldV1: 'value'
})

assert.deepStrictEqual(validate(union, {
  innerFieldV2: 42
}), {
  innerFieldV2: 42
})

wildcardObjectField

In case if payload can be virtually anything JSON friendly (i.e. no validation is required)

import { wildcardObjectField } from '@spec-validator/validator/fields'

const wildCard = wildcardObjectField()

assert.deepStrictEqual(validate(wildCard, '"Abracadabra"'), 'Abracadabra')

Segment field ($)

This special kind of a field with a fluent API, is for outlining a structure of a string (e.g. a url pattern).

import { segmentField as $ } from '@spec-validator/validator/fields'

const url = $
  ._('/')
  ._('username', stringField())
  ._('/items/')
  ._('id', numberField())

assert.deepStrictEqual(url.toString(), '^\\/(?<username>.*)\\/items\\/(?<id>\\d+)$')

expectType<TypeHint<typeof url>, {
  username: string,
  id: number
}>(true)

assert.deepStrictEqual(validate(url, '/john-smith/items/42'), {
  id: 42,
  username: 'john-smith'
})
0.13.0

2 years ago

0.10.0

2 years ago

0.9.0

2 years ago

0.7.2

2 years ago

0.7.1

2 years ago

0.6.1

3 years ago

0.5.0

3 years ago

0.4.8

3 years ago

0.4.7

3 years ago

0.4.6

3 years ago

0.4.5

3 years ago

0.4.4

3 years ago

0.4.3

3 years ago

0.4.2

3 years ago

0.4.1

3 years ago

0.4.0

3 years ago

0.3.0

3 years ago

0.3.2

3 years ago

0.3.1

3 years ago

0.2.19

3 years ago

0.2.18

3 years ago

0.2.17

3 years ago

0.2.16

3 years ago

0.2.15

3 years ago

0.2.14

3 years ago

0.2.13

3 years ago

0.2.12

3 years ago

0.2.9

3 years ago

0.2.6

3 years ago