3.2.1 • Published 26 days ago

ts-type-inspector v3.2.1

Weekly downloads
-
License
MIT
Repository
github
Last release
26 days ago

ts-type-inspector

The TypeInspector is a data validation tool that is heavily inspired by Joi. Due to the type-safety, it can prevent a misconfigured data validation (in contrast to Joi).

npm version License: MIT npm downloads

Features

  • type safe (no automatic type conversions/casting)
  • determine the value's data type based on the validators used (generic type arguments are mostly optional)
  • custom error messages
  • flexibel & additional custom validation
  • predefined default-validators for most common data types
  • extendable valdators to take external dependencies into account

Installation

npm i ts-type-inspector

Basics

  • the validation is terminated immediately when an invalidity occurs.
  • validation order:
    1. Basic data type
    2. Conditions
  • conditions can be chained to make the validation more precise
  • validators can be mixed to achieve more complex validation
import ti from 'ts-type-inspector';

// condition chaining
ti.<VALIDATOR>.<CONDITION1>.<CONDITION2>()...;
ti.<VALIDATOR>(<VALIDATION_PARAMS>).<CONDITION1>.<CONDITION2>()...;

// mix validators, e.g.:
ti.object({
  prop1: ti.<VALIDATOR>.<CONDITION1>.<CONDITION2>()...,
  prop2: ti.<VALIDATOR>(<VALIDATION_PARAMS>).<CONDITION1>.<CONDITION2>()...
})
ParameterDescription
<VALIDATOR>There are various validators that can be used for validation of diverse value-types (string, number, date, object, ...)
<VALIDATION_PARAMS>Some validators need configuration parameters to work correctly (array -> item validator, object -> property validators, ...)
<CONDITION>The TypeInspector uses method-chaining to define special validation conditions. These are additional checks that evaluate the incoming value more precisely

Validation modes

All validators provide two validation modes:

  • <VALIDATOR>.isValid(<UNKNOWN_VALUE>)
  • <VALIDATOR>.validate(<UNKNOWN_VALUE>)

Both modes perform the same validation, but their result outputs are different.

isValid

This mode uses the type predicate feature of Typescript and therefore returns a boolean value as validation result. This assigns an exact type to the (successfully) validated value based on the validator used.

import ti from 'ts-type-inspector';

function processIncomingValueAsString(value_: unknown): number {
  if (ti.string.isValid(value_)) {
    return value_.length; // typescript knows that the value_ is of type string at this point
  }

  return NaN;
}

validate

This mode throws a ValidationError when validation fails. On success it returns the same value (same object reference - in contrast to Joi) that was validated but with the correct type information.

import ti from 'ts-type-inspector';

function processIncomingValueAsString(value_: unknown): number {
  try {
    const message = ti.string.validate(value_);
    return message.length; // typescript knows that the value_ is of type string at this point
  } catch {
    return NaN;
  }
}

Error evaluation

The validator saves the last validation error that occurred, making it easy to evaluate. Since the validation is terminated immediately when an invalidity occurs, the error only contains information about this specific invalidity.

import ti from 'ts-type-inspector';

function processIncomingValueAsString(value_: unknown): number {
  const validator = ti.string;
  if (validator.isValid(value_)) {
    return value_.length; // typescript knows that the value_ is of type string at this point
  } else {
    const <VALIDATION_ERROR> = validator.validationError;
    console.log(<VALIDATION_ERROR>)
  }

  return NaN;
}

Or you can use isValidationError with try-catch.

import ti, { isValidationError } from 'ts-type-inspector';

function processIncomingValueAsString(value_: unknown): number {
  try {
    const stringValue = ti.string.validate(value_);
    return stringValue.length;
  } catch (reason_) {
    if (isValidationError(reason_)) {
      console.log(reason_); // <VALIDATION_ERROR>
    }

    return NaN;
  }
}
ParameterDescription
<VALIDATION_ERROR>Undefined if validation succeeds; Defined else; Contains reason for failed validation
propertyPathTrace/Path of property keys (array index, property name) to invalid value; only set if validation value is a complex data type (object, array) >> example: propertyX.5.propertyY
propertyTraceequivalent to propertyPath but stored as array >> [propertyX, 5, propertyY]
subErrorsEach chained validator has its own validation error instance. Errors are caught, processed/expanded and then thrown again by parent validators. Each validator captures thrown child validation errors.
messageSpecific message describing the invalidity

How to define custom validators

Create specialized (data type related) validators

All predefined default validators can be inherited to create custom validators for specific data types. Validation of complex data types can therefore be easily centralized for reuse.

import { DefaultObjectValidator, ti } from 'ts-type-inspector';

export type CommonData = {
  data: string | undefined;
};

export class CommonDataValidator extends DefaultObjectValidator<CommonData> {
  constructor() {
    super({
      data: ti.optional(ti.string)
    });
  }
}

const cdv = new CommonDataValidator();
cdv.isValide({ data: undefined }) // true
cdv.isValide({ data: false }) // false

Validation based on external influences

Sometimes data validation depends on external influences that limit the actual data type or its range of values. These influencing factors can be defined for custom validators and passed during validation. This means that a new validator does not necessarily have to be developed for every use case.

This simple example demonstrates 3 options to implement extended validation:

import { DefaultObjectValidator, ti } from 'ts-type-inspector';

export type CommonData = {
  data: string | undefined;
};

export type CommonDataValidationParameter = {
  valueRequired?: boolean;
};

export class CommonDataValidator extends DefaultObjectValidator<
  CommonData,
  CommonDataValidationParameter
> {
  constructor() {
    super({
      data: ti.optional(ti.string)
    });

    // 1. option - use the custom condition
    this.custom((value_, params_) => {
      if (params_?.valueRequired && value_.data === undefined) {
        return 'data is required';
      }
    });
  }

  // 2. option - create a new condition
  public get failWhenRequired() {
    this.setupCondition((value_, params_) => {
      if (params_?.valueRequired && value_.data === undefined) {
        this.throwValidationError('data is required');
      }
    });
    return this;
  }

  // 3. option - extend base type validation
  protected validateBaseType(
    value_: unknown,
    params_?: CommonDataValidationParameter
  ): CommonData {
    const base = super.validateBaseType(value_, params_);

    if (params_?.valueRequired && base.data === undefined) {
      this.throwValidationError('data is required');
    }

    return base;
  }
}

const cdv = new CommonDataValidator();
const value: CommonData = { data: undefined };

// when using 1. or 3. option
cdv.isValid(value); // true
cdv.isValid(value, { valueRequired: true }); // false

// when using 2. option
cdv.isValid(value, { valueRequired: true }); // true
cdv.failWhenRequired.isValid(value, { valueRequired: true }); // false

External influences and nested validators

Relevant to: Object, Partial, Dictionary, Array, Tuple

It is possible to pass the external influence parameters to nested validators. For this it is necessary to use a wrapper, which is provided by the main validator.

import { DefaultObjectValidator, DefaultStringValidator } from 'ts-type-inspector';

export type CommonData = {
  data: string | undefined;
};
export type SpecialStringValidationParams = {
  notEmpty?: boolean;
};

type CommonDataValidationParams = {
  dataParams?: SpecialStringValidationParams;
};

export class SpecialStringValidator extends DefaultStringValidator<SpecialStringValidationParams> {
  constructor() {
    super();
    this.custom((value_, params_) => {
      if (params_?.notEmpty && value_ === '') {
        return 'empty is not allowed';
      }
    });
  }
}

export class CommonDataValidator extends DefaultObjectValidator<
  CommonData,
  CommonDataValidationParams
> {
  constructor() {
    super({
      data: (validateWith, validationParams) =>
        validateWith(new SpecialStringValidator(), validationParams?.dataParams)
    });
  }
}

const cdv = new CommonDataValidator();
cdv.isValid({ data: '' }); // true
cdv.isValid({ data: '' }, { dataParams: { notEmpty: true } }); // false

Predefined validators

Most of the examples given here indicate generic type information of validators. This is optional, in most cases you can validate values without additional type information. The TypeInspector automatically calculates the resulting value type.

import ti from 'ts-type-inspector';

const <VALUE> = ti.object({
  greeting: ti.string.accept('hello', 'hi')
  greeting2: ti.strict('hello', 'hi')
})

/*
  <VALUE> will assert the following type:
  {
    greeting: string;
    greeting2: 'hello' | 'hi'
  }
*/

String

since 1.0.0

Validator for string values.

ConditionDescription
shortestreject strings with lenght less than minimal value
longestreject strings with lenght greater than maximal value
acceptaccept specific values only; regexp can be used to apply patterns
rejectreject specific values; regexp can be used to apply patterns
lengthreject strings with divergent length
rejectEmptyreject empty strings
base64accept just base64 encoded strings
jsonstrings have to be json parsable
datereject strings that are not in ISO8601 date format
numericstrings have to contain a numeric value
uuidreject strings that are no UUIDs
emailstring has to match email pattern (uses email-validator)
uristring has to match uri pattern (uses url-validator)
urlstring has to match url pattern
hexaccept just hexadecimal strings

Number

since 1.0.0

Validator for number values.

ConditionDescription
positiveaccept positive values only (zero is not positive)
negativeaccept negative values only (zero is not negative)
finitereject NaN or Infinity
rejectNaNreject NaN
rejectInfinityreject Infinity
rejectZeroreject 0
minreject numbers less than minimal value
maxreject numbers greater than maximal value
acceptaccept specific numbers only
rejectreject specific numbers

Object

since 1.0.0

Validator for object based values.

  • null is rejected by default
ConditionDescription
noOverloadreject objects that contain more keys than have been validated. USE FOR POJOs ONLY!. Getters/setters or private properties can produce false negatives.
import ti from 'ts-type-inspector';

interface DataInterface {
  prop1: string;
  prop2: number;
}

ti.object<DataInterface>({
  prop1: ti.string,
  prop2: ti.number
});

Partial

since 2.0.0

Validator for object based values. This is an UNSAFE validator that only validates some properties and ignores others

  • null is rejected
import ti from 'ts-type-inspector';

interface DataInterface {
  prop1: string;
  prop2: number;
}

ti.partial<DataInterface>({
  prop1: ti.string
});

Dictionary

since 1.0.0

Validator for dictionary objects

ConditionDescription
keysa string validator will check the dictionary keys
import ti from 'ts-type-inspector';

interface DataInterface {
  prop1: string;
  prop2: number;
}

interface DictionaryDataInterface {
  [key: string]: DataInterface;
}

ti.dictionary<DictionaryDataInterface>(
  ti.object({
    prop1: ti.string,
    prop2: ti.number
  })
);

Array

since 1.0.0

Validator for array values.

ConditionDescription
lengthreject arrays with divergent length
minreject arrays with length less than minimal value
maxreject arrays with length greater than maximal value
acceptaccept arrays with specific length only
rejectreject arrays with specific length values
import ti from 'ts-type-inspector';

interface DataInterface {
  prop1: string;
  prop2: number;
}

type DataArrayType = DataInterface[];

ti.array<DataArrayType>(
  ti.object({
    prop1: ti.string,
    prop2: ti.number
  })
);

Tuple

since 3.0.0

Validator for tuple based values (e.g. [string, number]).

ConditionDescription
noOverloadreject tuples that contain more entries than have been validated
import ti from 'ts-type-inspector';

type DataTuple = [string, number, 'mode1' | 'mode2']

ti.tuple<DataTuple>(
  ti.string,
  ti.number,
  ti.strict('mode1', 'mode2')
);

Date

since 1.0.0

Validator for date objects.

  • invalid date objects (isNaN(date.getTime())) are rejected
ConditionDescription
earliest*reject dates earlier than minimal value
latest*reject dates later than maximal value
accept*accept specific values only
reject*reject specific values

* string (ISO8601), number (timestamp) and date can be used.

Method

since 1.0.0

Validator for method-like values.

Unfortunately (for technical reasons), this validator can only validate the number of parameters.

ConditionDescription
countreject methods with divergent params count
minreject methods with params count less than minimal value
maxreject methods with params count greater than maximal value
acceptaccept methods with specific params count only
rejectreject methods with specific params count

Union

since 1.0.0

Validator for union type values (like "string | number")

This is just a wrapper, other validators will do the job.

import ti from 'ts-type-inspector';

type UnionDataType = string | number;

ti.union<UnionDataType>(
  ti.string,
  ti.number
);

Strict

since 1.0.0

Validator for precisely defined values (not just of specific type).

import ti from 'ts-type-inspector';

type StrictType = 'hello' | 'world';

const <VALUE> = ti.strict<StrictType>('hello', 'world');

In contrast to union the strict validator validates the exact value and not just the value type. The resulting <VALUE> will be of type 'hello' | 'world' (and not just string)

Optional

since 1.0.0

Validator for optional values.

  • undefined is valid by default

This is just a wrapper, other validators will do the job.

import ti from 'ts-type-inspector';

interface DataInterface {
  prop1: string;
  prop2: number;
}

interface MoreDataInterface {
  data1?: DataInterface;
  data2: DataInterface | undefined;
}

ti.object<MoreDataInterface>(
  data1: ti.optional(
    ti.object({
      prop1: ti.string;
      prop2: ti.number;
    })
  ),
  data2: ti.optional(
    ti.object({
      prop1: ti.string;
      prop2: ti.number;
    })
  )
);

Any

since 1.0.0

This validator should only be used when a value is indeterminate or when you want to bypass deep validation of an object.

ConditionDescription
notNullishreject null or undefined
notFalsyreject null, undefined, 0, '', false, NaN, ...

Custom

since 1.0.0

Provide a validation callback to this validator to process a custom validation.

import ti from 'ts-type-inspector';

ti.custom(value_ => {
  if (value_ === 42) {
    return 'The value cannot be 42'
  }
})

Return an error message if validation fails. Don't throw your own error!

Enum

since 1.0.2

Validator for enum values.

import ti from 'ts-type-inspector';

enum NumberEnum {
  foo,
  bar
}

enum StringEnum {
  foo = 'foo',
  bar = 'bar'
}

ti.enum(NumberEnum);
ti.enum(StringEnum).values(ti.string.reject(StringEnum.bar));
ConditionDescription
valuesadd validator for additional base type validation

Exclude

since 1.1.0

This validator is able to validate if a type doesn't exist in a KNOWN union type.

The generics "Out" and "In" have to be set. "In" describes the incoming union type and "Out" the desired output type. The passed validator checks whether the undesired types (= In - Out) exist in the value.

import ti from 'ts-type-inspector';

type Input = string | number | boolean;

function filter(input_: Input): string | boolean {
  return ti.exclude<string | boolean, Input>(
    ti.number
  ).validate(input_);
}

function filter2(input_: Input): string {
  return ti.exclude<string, Input>(
    ti.union(
      ti.number,
      ti.boolean
    )
  ).validate(input_);
}

Boolean

since 1.0.0

Validator for boolean values.

ConditionDescription
trueonly true is valid
falseonly false is valid

Undefined

since 1.0.0

This validator rejects all values that are defined (!== undefined).

Null

since 1.0.0

This validator rejects all values that are not null.

Nullish

since 1.0.0

This validator rejects all values that are not null or undefined.

3.2.1

26 days ago

3.2.0

27 days ago

3.1.0

1 month ago

3.0.1

1 month ago

3.0.0

1 month ago

2.1.1

5 months ago

2.1.0

10 months ago

2.0.0

11 months ago

1.1.1

2 years ago

1.1.0

2 years ago

1.0.2

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago