Dilswer
Small and lightweight data validation library with TypeScript integration.
Keep your type definitions in one place, and have but one source of truth for
both the runtime validation types and the TypeScript type definitions.
Table Of Contents
- Quick Start
- Create type definitions
- Create a TypeScript type from a Dilswer definition
- Create a validation function
- Create a function with a validated input
- Main exported functions
- assertDataType()
- createValidator()
- createTypeGuardedFunction()
- createValidatedFunction()
- toJsonSchema()
- DataType
- Data Types
- String
- Number
- Int
- StringNumeral
- StringInt
- Boolean
- Symbol
- Null
- Undefined
- Function
- Unknown
- OneOf
- AllOf
- ArrayOf
- RecordOf
- Dict
- SetOf
- Literal
- Enum
- EnumMember
- Custom
- Utility Functions
- And
- Omit
- Pick
- Partial
- Required
- Exclude
- Metadata
- Assign Metadata
- Read Metadata
- Metadata in JSON Schema
Quick Start
Create type definitions
import { DataType, OptionalField } from "dilswer";
const PersonDataType = DataType.RecordOf({
id: DataType.String,
name: DataType.String,
age: { type: DataType.Number },
email: OptionalField(DataType.String),
friends: { type: DataType.ArrayOf(DataType.String), required: false },
});
NOTE: the required
attribute in a RecordOf fields is set to true
by
default.
Create a TypeScript type from a Dilswer definition
It is possible to infer a TypeScript type from a Dilswer definition:
import { GetDataType } from "dilswer";
import { PersonDataType } from "./person-type.ts";
type Person = GetDataType<typeof PersonDataType>;
Create a validation function
import { createValidator } from "dilswer";
import { PersonDataType } from "./person-type.ts";
const isPerson = createValidator(PersonDataType);
const person = await axios
.get("https://my-api.io/get-person/1")
.then((r) => r.data);
if (isPerson(person)) {
console.log("Name: ", person.name);
} else {
console.error("`person` variable is not of expected type.");
}
Create a function with a validated input
import { createValidator } from "dilswer";
import { PersonDataType } from "./person-type.ts";
const processPerson = createValidatedFunction(
PersonDataType,
(person) => {
console.log("Processing person: ", person.name);
return "Success!";
},
(error) => {
console.error("Function input is not of expected type.");
console.error("Type expected:", error.expectedValueType);
console.error("Received:", error.receivedValue);
console.error("Invalid property location: ", error.fieldPath);
return "Failure";
}
);
const person = await axios
.get("https://my-api.io/get-person/1")
.then((r) => r.data);
const result = processPerson(person);
Main exported functions
dilswer.createValidator()
const createValidator: <DT extends AllDataTypes>(
dataType: DT
) => (data: unknown) => data is ParseDataType<DT>;
Higher order function that generates a validator which will check the provided
data
against the dataType
type structure definition and returns a boolean
indicating if the check was successful.
dilswer.createTypeGuardedFunction()
const createTypeGuardedFunction: <DT extends AllDataTypes, R, ER = void>(
dataType: DT,
onValidationSuccess: (data: ReWrap<ParseDataType<DT>>) => R,
onValidationError?: (error: ValidationError, data: unknown) => ER
) => (data: unknown) => R | ER;
Higher order function that generates a new function which will check the
provided data
against the dataType
type structure, and if the check is
successful then the first callback onValidationSuccess
is invoked with data
as it's argument, otherwise the second callback onValidationError
is invoked
with the type validation error as it's argument (unless the callback is not
specified).
dilswer.createValidatedFunction()
Alias for the createTypeGuardedFunction()
.
dilswer.assertDataType()
Also available under an alias dilswer.ensureDataType()
const assertDataType: <DT extends AllDataTypes>(
dataType: DT,
data: unknown
) => asserts data is ParseDataType<DT>;
Checks the provided data
against the dataType
type definition and throws an
ValidationError if the data
does not conform to the dataType
.
dilswer.toJsonSchema()
Translates given DataType into a JSON Schema.
const toJsonSchema = (
type: AnyDataType,
options: ParseToJsonSchemaOptions = {},
include$schemaProperty = true
): JSONSchema6 | undefined
ParseToJsonSchemaOptions
type ParseToJsonSchemaOptions = {
incompatibleTypes?: "throw" | "omit" | "set-as-any";
additionalProperties?: boolean;
customParser?: {
Set?: (
setItemsSchemas: JSONSchema6[],
original: Set<AnyDataType[]>,
options: ParseToJsonSchemaOptions
) => JSONSchema6 | undefined;
Custom?: (
validateFunction: Custom["custom"],
original: Custom,
options: ParseToJsonSchemaOptions
) => JSONSchema6 | undefined;
Undefined?: (
dataType: BasicDataType,
options: ParseToJsonSchemaOptions
) => JSONSchema6 | undefined;
Symbol?: (
dataType: BasicDataType,
options: ParseToJsonSchemaOptions
) => JSONSchema6 | undefined;
Function?: (
dataType: BasicDataType,
options: ParseToJsonSchemaOptions
) => JSONSchema6 | undefined;
};
};
dilswer.DataType
Object containing all the dilswer runtime type definitions (like Number
,
String
, ArrayOf(...)
, etc.)
Data Types
DataType.String
will match any string values and translate to the standard string
type in
TypeScript.
DataType.Number
will match any number values and translate to the standard number
type in
TypeScript.
DataType.Int
will match any integer values and translate to the standard number
type in
TypeScript. TypeScript does not have any way of distinguishing float and
integers therefore both are assigned the same TypeScript type.
DataType.StringNumeral
will match any string containing only numeric values and translate to a
`${number}`
type in TypeScript. A value successfully validated with
StringNumeral
is safe to convert into a number and will never produce a NaN
value.
DataType.StringInt
will match any string containing only numbers and translate to a
`${number}`
type in TypeScript. Strings with floating point numbers are
not matched by this type. A value successfully validated with StringInt
is
safe to convert into a number and will never produce a NaN
value.
DataType.Boolean
will match any true
and false
values and translate to the standard boolean
type in TypeScript.
DataType.Symbol
will match any symbolic values and translate to the symbol
type in TypeScript.
DataType.Null
will match only null
value and translate to the standard null
type in
TypeScript.
DataType.Undefined
will match only undefined
value and translate to the standard undefined
type
in TypeScript.
DataType.Function
will match any function and translate to the Function
type in TypeScript.
DataType.Unknown
will match any value and translate to the unknown
type in TypeScript.
DataType.OneOf(...DataType's)
will match any value matching one of the DataType's provided in the arguments
and translate to an TypeScript union type.
Example
const foo = DataType.OneOf(DataType.String, DataType.Number);
type T = GetDataType<typeof foo>;
DataType.AllOf(...DataType's)
will match values matching every DataType provided and translate to a TypeScript
intersection of all those DataType's.
Mostly useful to intersect multiple RecordOf's.
Example
const foo = DataType.RecordOf({ foo: string });
const bar = DataType.RecordOf({ bar: string });
const combined = DataType.AllOf(foo, bar);
type T = GetDataType<typeof combined>;
DataType.ArrayOf(...DataType's)
will match any array which contains only values matching any of the DataType's
provided in the arguments and translate to the Array<...>
type in TypeScript.
Example
const foo = DataType.ArrayOf(DataType.String, DataType.Number);
type T = GetDataType<typeof foo>;
DataType.RecordOf(Record<string, FieldDescriptor>)
will match any object which structure matches the key-value pairs of object
properties and FieldDescriptor's passed to the argument.
Example
const foo = DataType.RecordOf({
foo: DataType.Boolean,
bar: { type: DataType.String },
baz: { type: DataType.Number, required: false },
});
type T = GetDataType<typeof foo>;
DataType.Dict(...DataType's)
will match any object which properties match against the provided DataTypes's,
and translates to a Record type in TypeScript.
Example
const dictOfFunctions = DataType.Dict(DataType.Function);
type T = GetDataType<typeof dictOfFunctions>;
DataType.SetOf(...DataType's)
will match any Set object which contains only values matching any of the
DataType's provided in the arguments and translate to the Set<...>
type in
TypeScript.
Example
const foo = DataType.SetOf(DataType.String, DataType.Number);
type T = GetDataType<typeof foo>;
DataType.Literal(string | number | boolean)
will match any value that exactly matches the passed argument and translate to
the literal type of that value in TypeScript.
Example's
const foo = DataType.Literal("some-string-literal");
type T0 = GetDataType<typeof foo>;
const bar = DataType.Literal(123);
type T1 = GetDataType<typeof bar>;
const baz = DataType.Literal(true);
type T2 = GetDataType<typeof baz>;
will match any value that belongs to an TypeScript enum and translate to that
enum type.
enum MyEnum {
A = "A",
B = "B",
}
const foo = DataType.Enum(MyEnum);
type T = GetDataType<typeof foo>;
const validate = createValidator(foo);
validate(MyEnum.A);
validate(MyEnum.B);
DataType.EnumMember(enum member)
will match any value that equals to the specified TypeScript enum member and
translate to that enum member type.
enum MyEnum {
A = "VALUE_A",
B = "VALUE_B",
}
const foo = DataType.EnumMember(MyEnum.A);
type T = GetDataType<typeof foo>;
const validate = createValidator(foo);
validate("VALUE_A");
validate(MyEnum.A);
validate(MyEnum.B);
DataType.Custom(Function)
will test the data with the provided function, provided function should return a
boolean indicating if the tested value passed the validation, passed function
should also have a type definition that looks like this: (v: any) => v is T
,
where T is any valid TS type.
Example
const isNonEmptyString = (v: any): v is string =>
typeof v === "string" && v.length > 0;
const nonEmptyTypeDef = DataType.Custom(isNonEmptyString);
type T = GetDataType<typeof nonEmptyTypeDef>;
Utility Functions
And()
And()
utility function can combine two Record Type Definitions into one. If
any of the properties between the two combined Type Defs have the same key-name,
the definition of the second one takes priority.
const typeDefOne = DataType.RecordOf({
foo: DataType.Number,
bar: DataType.Number,
});
const typeDefTwo = DataType.RecordOf({
bar: DataType.ArrayOf(DataType.String),
baz: DataType.Boolean,
});
const typeDefSum = And(typeDefOne, typeDefTwo);
Omit()
Omit()
utility function removes specified keys from a Record Type Definition.
const typeDefOne = DataType.RecordOf({
foo: DataType.Number,
bar: DataType.Number,
baz: DataType.Number,
qux: DataType.Number,
});
const typeDefOmitted = Omit(typeDefOne, "bar", "qux");
Pick()
Pick()
utility function removes all not specified keys from a Record Type
Definition.
const typeDefOne = DataType.RecordOf({
foo: DataType.Number,
bar: DataType.Number,
baz: DataType.Number,
qux: DataType.Number,
});
const typeDefPick = Pick(typeDefOne, "bar", "qux");
Partial()
Partial()
utility type makes all the Record's Type Definition keys optional.
const typeDefOne = DataType.RecordOf({
foo: DataType.Number,
bar: DataType.String,
baz: DataType.ArrayOf(DataType.Number),
});
const typeDefPartial = Partial(typeDefOne);
Required()
Required()
utility type makes all the Record's Type Definition keys to be
required (vs optional).
const typeDefOne = DataType.RecordOf({
foo: { type: DataType.Number, required: false },
bar: { type: DataType.String, required: false },
baz: { type: DataType.ArrayOf(DataType.Number), required: false },
});
const typeDefRequired = Required(typeDefOne);
Exclude()
Exclude()
utility function removes Type Definitions from an Type Def union.
const typeDefOne = DataType.OneOf(
DataType.String,
DataType.Number,
DataType.Boolean
);
const typeDefExcluded = Exclude(typeDefOne, DataType.Number);
Metadata
Each DataType can have metadata attached to it, this metadata can be used to
provide additional information about the data type, for example, you can attach
a description to a data type, or a title, or format.
Metadata is completely ignored by the validation process
Assign Metadata
import { DataType } from "dilswer";
const UserNameDT =
DataType.String.setTitle("User Name").setDescription("The user's name.");
const User = DataType.RecordOf({
name: UserNameDT,
id: DataType.String.setTitle("User ID").setFormat("uuid"),
friends: DataType.ArrayOf(DataType.String).setDescription(
"A list of the user's friends names."
),
})
.setTitle("User")
.setDescription(
"A user object. Contains the user's name, id and friends list."
);
Read Metadata
import { DataType, getMetadata } from "dilswer";
const userNameMetadata = getMetadata(UserNameDT);
const userMetadata = getMetadata(User);
Metadata in JSON Schema
Metadata is also used when generating JSON Schema, if a DataType has a title,
description or format, it will be included in the generated JSON Schema.
import { DataType, toJsonSchema } from "dilswer";
const UserDT = DataType.RecordOf({
name: DataType.String.setTitle("User Name").setDescription(
"The user's name."
),
id: DataType.String.setTitle("User ID").setFormat("uuid"),
friends: DataType.ArrayOf(DataType.String).setDescription(
"A list of the user's friends names."
),
})
.setTitle("User")
.setDescription(
"A user object. Contains the user's name, id and friends list."
);
const jsonSchema = toJsonSchema(UserDT);