Navigator Prefabricating Marinates

    @connectedcars/object-validator
    TypeScript icon, indicating that this package has built-in type declarations

    2.1.1 • Public • Published

    Object-validator

    Validate input data based on a defined schema with automatic compile time type interference and excellent performance, removing any reason to not always do validation on input:

    Features:

    • Detailed validation errors
    • Precise validation with sub types for number like floats and integers with min/max value
    • Optimized code generation (Similar performance to hand optimized code)
    • Compile time type interference (Without using compiler plugins or parsers, just pure typescript)
    • Easy to implement own validators
    • Zero dependencies

    Purpose

    There exists a lot of validation libraries but most of them are slow, limit what you can validate, are tied to a specific build system, does not allow extension or has horrible syntax. ObjectValidator lets you construct simple schemas where you can validate input in detail, fx. limiting the size of a integer and give full feedback on what failed validation to the end user. While doing this you also construct your typescript types without having to define separate interfaces or types for what you are validating.

    When running in optimized mode it generates a custom function that should give the fastest and least overhead validation that is possible often speeding up validating by more than 10 times.

    Installation

    npm install @connectedcars/object-validator

    Features

    The following validators are supported:

    • Array: Validate that a value is an array of a specific type using other validators and min/max length of array
    • Boolean: Validate that a value is a boolean
    • Date: Validate that a value is a JavaScript Date object
    • DateTime: Validate that a value is an ISO 8601 date/time string
    • ExactString: Validate that a value is a string that matches an exact string
    • FloatString: Validate that a value is float string within a min and max limit
    • Float: Validate that a value is a float within a min and max limit
    • IntegerString: Validate that a value is a integer string within a min and max limit
    • Integer: Validate that a value is a integer within a min and max limit
    • Null: Validate that a value is null
    • Object: Validate that a value is an object using other validators
    • RegexMatch: Validate that a value is a string that matches a regular expression
    • Sample: Validate that a value matches a sample
    • String: Validate that a value is a string within a min and max limit
    • Undefined: Validate that a value is undefined
    • Union: Validate that a value validates at least one validator out of a number of validators
    • Enum: Validate that a value validates is a string and matches at least one exact string
    • Unknown: Validate that a value is not undefined or or unset on a object

    Validation functions:

    • .validate(value) : Returns list of errors found in the validation
    • .isType(value, listOfErrors) : Type guard for the type to avoid doing validation twice
    • .isValid(value) : Type guard for the type
    • .cast(value) : Returns type or throws ValidationError
    • .toString() : Return validator code or TypeScript interface for the validator
    • .AssertType<OwnType, true | false> : No op function to validate custom types match a validator

    Common options:

    • optimize: Create optimized function and use this for validation (default: true)
    • earlyFail: Return on first error (default: false)

    How to use

    Simple

    The sample validator enables building very quick validators where that you slowly can add more validation as you go along.

    import { RequiredSample } from '@connectedcars/object-validator'
    
    const tripValidator = new RequiredSample({
      type: new RequiredExactString('trip'),
      unitId: '1234567',
      messageId: 'MSG-12345',
      recordedAt: '2018-08-06T13:37:00Z',
      tripId: 1337,
      value: 500,
      position: {
        latitude: 55.332131,
        longitude: 12.54454,
        accuracy: 21
      },
      positions: [{
        latitude: 55.332131,
        longitude: 12.54454,
        accuracy: 21
      }]
    }, { optimize: true, earlyFail: true })
    
    // Returns true if unknownValue validates
    if(objectValidator.isValid(unknownValue)) {
      // unknownValue has been cast to the following type
      // {
      //  type: 'gps_odometer_km'
      //  unitId: string
      //  recordedAt: string
      //  tripId: number
      //  value: number
      //  position: {
      //    latitude: number
      //    longitude: number
      //    accuracy: number
      //  }
      //  positions: Array<{
      //    latitude: number
      //    longitude: number
      //    accuracy: number
      //  }>
      // }
      console.log(unknownValue.tripId)
    }
    
    console.log(tripValidator.toString()) // Dump validator code
    console.log(tripValidator.toString({ type: true })) // Dump typescript interface for the validator

    Advanced

    import {
      RequiredObject,
      OptionalObject,
      RequiredArray,
      RequiredDateTime,
      RequiredFloat,
      RequiredInteger,
      RequiredObject,
      RequiredString,
      RequiredExactString
    } from '@connectedcars/object-validator'
    
    const tripValidator = new RequiredObject(
      {
        type: new RequiredExactString('trip'),
        unitId: new RequiredString(1, 32),
        recordedAt: new RequiredDateTime(),
        tripId: new RequiredInteger(0, 4294967295),
        value: new RequiredInteger(0, 999999),
        position: new OptionalObject({
          latitude: new RequiredFloat(-90, 90),
          longitude: new RequiredFloat(-180, 180),
          accuracy: new RequiredInteger(0, 20)
        }),
        positions: new RequiredArray(
          new RequiredObject({
            latitude: new RequiredFloat(-90, 90),
            longitude: new RequiredFloat(-180, 180),
            accuracy: new RequiredInteger(0, 20)
          }),
          0,
          10
        )
      },
      { optimize: true }
    )
    
    let unknownValue = {
      type: 'trip',
      unitId: '1234567',
      messageId: 'MSG-12345',
      recordedAt: '2018-08-06T13:37:00Z',
      tripId: 1337,
      value: 500,
      position: {
        latitude: 55.332131,
        longitude: 12.54454,
        accuracy: 21
      },
      positions: []
    }
    
    // Returns errors found in object
    let errors = gpsOdometerKm.validate(unknownValue)
    if (errors.length > 0) {
      console.error(`${errors.length} errors found`)
    }
    
    // Returns true if errors is empty
    if(gpsOdometerKm.isType(unknownValue, errors)) {
      console.log(unknownValue.unitId) // unknownValue.unitId has been type cast to string
    }
    
    // Returns true if unknownValue validates
    if(objectValidator.isValid(unknownValue)) {
      console.log(unknownValue.tripId) // unknownValue.unitId has been type cast to number
    }
    
    // Throws ValidationsError if unknownValue does not validate
    let knownValue = objectValidator.cast(unknownValue)
      console.log(knownValue.positions.length()) // unknownValue.unitId has been type cast to array
    }
    
    interface Trip {
      type: 'trip',
      unitId: string,
      recordedAt: string,
      tripId: number,
      value: number,
      position?: {
        latitude: number,
        longitude: number,
        accuracy: number
      },
      positions: [
        {
          latitude: number,
          longitude: number,
          accuracy: number
        }
      ]
    }
    // Throws compile error if type does not match validator
    objectValidator.AssertType<Trip, true>()
    
    // Throws compile error if type matches validator
    objectValidator.AssertType<Trip, false>()

    Validators

    Array (validator, min, max, options)

    Validators: RequiredArray, OptionalArray, NullableArray, OptionalNullableArray

    Function: isArray

    let arrayValidator = new RequiredArray(new RequiredInteger(0, 10), 0, 2)
    let errors = arrayValidator.validate([1, 10])
    if (isArray(new RequiredInteger(0, 10), [1, 10] 0, 2))  {
      console.log('Is integer array')
    }

    Boolean

    Validators: RequiredBoolean, OptionalBoolean, NullableBoolean, OptionalNullableBoolean

    Function: isBoolean

    let booleanValidator = new RequiredBoolean()
    let errors = booleanValidator.validate(true)
    if (isBoolean(true)) {
      console.log('Is boolean')
    }

    Date

    Validators: RequiredDate, OptionalDate, NullableDate, OptionalNullableDate

    Function: isDate

    let dateValidator = new RequiredDate()
    let errors = dateValidator.validate(new Date())
    if (isDate(new Date())) {
      console.log('Is Date')
    }

    DateTime

    Validators: RequiredDateTime, OptionalDateTime, NullableDateTime, OptionalNullableDateTime

    Function: isDateTime

    let dateTimeValidator = new RequiredDateTime()
    let errors = dateTimeValidator.validate('2018-08-06T13:37:00Z')
    if (isDateTime('2018-08-06T13:37:00Z')) {
      console.log('Is ISO date')
    }

    ExactString (expected)

    Validators: RequiredExactString, OptionalExactString, NullableExactString, OptionalNullableExactString

    Function: isExactString

    let exactStringValidator = new RequiredExactString('mystring')
    let errors = exactStringValidator.validate('mystring')
    if (isExactString('mystring', 'mystring')) {
      console.log('Is mystring')
    }

    FloatString (min, max)

    Validators: RequiredFloatString, OptionalFloatString, NullableFloatString, OptionalNullableFloatString

    Function: isFloatString

    let floatStringValidator = new RequiredFloatString()
    let errors = floatStringValidator.validate("1.0")
    if (isFloatString('1.1')) {
      console.log('Is float string')
    }

    Float (min, max)

    Validators: RequiredFloat, OptionalFloat, NullableFloat, OptionalNullableFloat

    Function:

    let floatValidator = new RequiredFloat()
    let errors = floatValidator.validate(1.0)
    if (isFloat(1.1)) {
      console.log('Is float')
    }

    IntegerString (min, max)

    Validators: RequiredIntegerString, OptionalIntegerString, NullableIntegerString, OptionalNullableIntegerString

    Function: isIntegerString

    let integerStringValidator = new RequiredIntegerString()
    let errors = integerStringValidator.validate('10')
    if (isIntegerString('10')) {
      console.log('Is integer string')
    }

    Integer (min, max)

    Validators: RequiredInteger, OptionalInteger, NullableInteger, OptionalNullableInteger

    Function: isInteger

    let integerValidator = new RequiredInteger()
    let errors = integerValidator.validate(10)
    if (isInteger(10)) {
      console.log('Is integer')
    }

    Null

    Validators: RequiredNull, OptionalNull, NullableNull, OptionalNullableNull

    Function: isNull

    let nullValidator = new RequiredNull()
    let errors = nullValidator.validate(null)
    if (isNull(10)) {
      console.log('Is null')
    }

    Object (schema)

    Validators: RequiredObject, OptionalObject, NullableObject, OptionalNullableObject

    Function: isObject

    let objectValidator = new RequiredObject({
      int: new RequiredInteger(0, 10)
    })
    let errors = objectValidator.validate({
      int: 10
    })
    if (isObject({ int: new RequiredInteger(0, 10) }, { int: 10 })) {
      console.log('Is object')
    }

    RegexMatch (regex)

    Validators: RequiredRegexMatch, OptionalRegexMatch, NullableRegexMatch, OptionalNullableRegexMatch

    Function: isRegexMatch

    let regexMatchValidator = new RequiredRegexMatch(/^hello$/)
    let errors = regexMatchValidator.validate('hello')
    if (isRegexMatch(/hello/, 'hello')) {
      console.log('Is regex match')
    }

    Sample (sample)

    Validators: RequiredSample, OptionalSample, NullableSample, OptionalNullableSample

    Function: isSample

    The sample validator will convert a sample to a validator based on types with a few exceptions:

    • Integer numbers in the sample with be converted to a IntegerValidator, fx. 1, 10, etc.
    • Float numbers in the sample with be converted to a FloatValidator, fx. 1.1, 2.2, etc.
    • Strings that validates with the DateTime validator will be converted DateTimeValidator, fx. '2018-08-06T13:37:00Z'
    let sampleValidator = new RequiredSample({
      date: '2018-08-06T13:37:00Z'
      key: new RequiredExactString('value')
    })
    let errors = sampleValidator.validate({
      date: '2018-08-06T13:37:00Z',
      key: 'value'
    })
    let sample = {
      date: '2018-08-06T13:37:00Z'
      key: new RequiredExactString('value')
    }
    let value = {
      date: '2018-08-06T13:37:00Z',
      key: 'value'
    }
    if (isSample(sample, value)) {
      console.log('Is sample match')
    }

    More complex example:

    const sample = {
      type: new RequiredExactString('gps_odometer_km'),
      unitId: '1234567',
      recordedAt: '2018-08-06T13:37:00Z',
      tripId: 1337,
      value: 500,
      position: {
        latitude: 55.332131,
        longitude: 12.54454,
        accuracy: 18,
        extra: {
          tag: 'test',
          tagversion: 32,
          tagDepth: 3.1416
        }
      },
      positions: [
        {
          latitude: 55.332131,
          longitude: 12.54454,
          accuracy: 18
        }
      ]
    }
    const sampleValidator = new RequiredSample(sample, { optimize })
    const errors = sampleValidator.validate(sample)

    Converting to a normal validator structure:

    console.log(`interface MyType ${sampleValidator.toString({ types: true })}`)
    console.log(`let myTypeValidator = ${sampleValidator.toString()}`)

    String (min, max)

    Validators: RequiredString, OptionalString, NullableString, OptionalNullableString

    Function: isString

    let stringValidator = new RequiredString()
    let errors = stringValidator.validate('hello1234')
    if (isString('hello1234')) {
      console.log('Is string')
    }

    Undefined

    Validators: RequiredUndefined, OptionalUndefined, NullableUndefined

    Function: isUndefined

    let undefinedValidator = new RequiredUndefined()
    let errors = undefinedValidator.validate(undefined)
    if (isUndefined(undefined)) {
      console.log('Is undefined')
    }

    Union ([validator, ...])

    Validators: RequiredUnion, OptionalUnion, NullableUnion, OptionalNullableUnion

    Function: isUnion

    The union validator takes a union of validates and validates the value with each of them, it will stop when the first validates and return a positive result. It has a special optimization for a union of ObjectValidators with a shared required ExactString key, it will use the key to figure out what validator to use.

    let unionValidator = new RequiredUnion([
      new RequiredInteger(),
      new RequiredString()
    ])
    let errors1 = unionValidator.validate('hello')
    let errors2 = unionValidator.validate(10)
    let union = [
      new RequiredInteger(),
      new RequiredString()
    ]
    if (isSample(union, 10)) {
      console.log('Is union match')
    }

    Example of shared required ExactString key:

    const numberMessageValidator = new RequiredObject({
      type: new RequiredExactString('number'),
      value: new RequiredFloat()
    })
    
    const stringMessageValidator = new RequiredObject({
      type: new RequiredExactString('string'),
      value: new RequiredString()
    })
    
    const errorMessageValidator = new RequiredObject({
      type: new RequiredExactString('error'),
      error: new RequiredString()
    })
    
    const messageValidator = new UnionValidator([
      numberMessageValidator,
      stringMessageValidator,
      errorMessageValidator
    ])
    
    let errors = messageValidator.validate({ type: 'string', value: 'hello' })

    EnumValidator([string, ...])

    Alias: RequiredEnum, OptionalEnum, NullableEnum, OptionalNullableEnum

    The enum validator is a short hand instance of the union validator:

    let unionValidator = new RequiredEnum(['hello', 'more'])
    let errors1 = unionValidator.validate('hello')
    let errors2 = unionValidator.validate(10)

    Unknown

    Validators: RequiredUnknown, OptionalUnknown

    The unknown validator only makes sense to use with another validate like the object validator to require a key even when the type is not known.

    let unknownValidator = new RequiredUnknown()
    let errors1 = unknownValidator.validate('hello')
    let errors2 = unknownValidator.validate(10)
    let errors3 = unknownValidator.validate(null)

    Benchmark

    success in 1.937 s (516,262.261 ops/s)
    success optimized in 0.195 s (5,128,205.128 ops/s)
    failureEarly in 1.349 s (741,289.844 ops/s)
    failureEarly optimized in 0.715 s (1,398,601.399 ops/s)
    failureLate in 1.309 s (763,941.94 ops/s)
    failureLate optimized in 0.351 s (2,849,002.849 ops/s)
    

    Keywords

    none

    Install

    npm i @connectedcars/object-validator

    DownloadsWeekly Downloads

    225

    Version

    2.1.1

    License

    MIT

    Unpacked Size

    149 kB

    Total Files

    44

    Last publish

    Collaborators

    • mlsalexius
    • gbullock
    • thormortensen
    • jkrichardson
    • betlemaguado
    • lmfros
    • jt_connectedcars
    • amandalarssen
    • lbrcc
    • tjansson
    • tori
    • jagdos
    • tlbdk
    • vallentin
    • mex
    • fg-cc
    • mikfoo
    • torbenkikkert
    • connectedcars-cloudbuild
    • mtjcc
    • urcra
    • alparty
    • viter
    • chribsen
    • madspelt
    • sta-cc
    • hakimeg
    • mwa-cc
    • yannlyng
    • aabnpm