funtypes-schemas
Validators and parsers for common types not covered by the base funtypes package.
This package includes all these schemas:
- 🚀 Base64String
- 🚀 ChainCodecs
- 🚀 ConstrainLength
- 🚀 DateString
- 🚀 DateTime
- 🚀 DateTimeString
- 🚀 Float
- 🚀 FloatString
- 🚀 Integer
- 🚀 IntegerString
- 🚀 Migrate
- 🚀 ParsedBase64Array
- 🚀 ParsedBase64String
- 🚀 ParsedDateString
- 🚀 ParsedDateTimeString
- 🚀 ParsedFloatString
- 🚀 ParsedIntegerString
- 🚀 ParsedJsonString
- 🚀 ParsedUrlString
- 🚀 Url
- 🚀 UrlString
Installation
yarn add funtypes-schemas
Usage
Base64String
Validate that a string could represent base64 encoded binary data.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({data: s.Base64String()});
✅ Valid:
{"data": "abc=="}
🚨 Invalid:
{"data": "!~~"}
ParsedBase64String
Extends Base64String
by transparently converting the string between a utf8 string and a base64 representation.
✅ Valid:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
data: s.ParsedBase64String(),
});
deepEqual(
MySchema.parse({
data: 'aGVsbG8gd29ybGQ=',
}),
{
data: 'hello world',
},
);
deepEqual(
MySchema.serialize({
data: 'hello world',
}),
{
data: 'aGVsbG8gd29ybGQ=',
},
);
ParsedBase64Array
Extends Base64String
by transparently converting the string between a Uint8Array and a base64 representation.
✅ Valid:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
data: s.ParsedBase64Array(),
});
deepEqual(
MySchema.parse({
data: 'aGVsbG8gd29ybGQ=',
}),
{
data: new Uint8Array([
104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100,
]),
},
);
deepEqual(
MySchema.serialize({
data: new Uint8Array([
104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100,
]),
}),
{
data: 'aGVsbG8gd29ybGQ=',
},
);
ChainCodecs
Chain multiple codecs together to combine parsers, for example you can parse a Base64 encoded JSON object:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = s.ChainCodecs(
s.ParsedBase64String(),
s.ParsedJsonString(t.Object({value: s.Integer()})),
);
deepEqual(assertMySchema.parse('eyJ2YWx1ZSI6NDJ9'), {value: 42});
deepEqual(assertMySchema.serialize({value: 42}), 'eyJ2YWx1ZSI6NDJ9');
You can pass as many codecs as you like as parameters to ChainCodecs
. They will be applied in order when parsing and in reverse order when serializing.
ConstrainLength
Constrain the length of a base type that has a length
property, such as a String
or an Array
.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
nonEmptyString: s.ConstrainLength(t.String, {min: 1})
limitedList: s.ConstrainLength(t.Array(t.String), {max: 3}),
});
✅ Valid:
{
"nonEmptyString": "hello world",
"limitedList": ["a", "b", "c"]
}
🚨 Invalid (string must have length >= 1):
{
"nonEmptyString": "",
"limitedList": ["a", "b", "c"]
}
🚨 Invalid (array must have length <= 3):
{
"nonEmptyString": "hello world",
"limitedList": ["a", "b", "c", "d"]
}
DateString
Represent a date (not including a time) in yyyy-mm-dd
format.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
dateOfBirth: s.DateString({max: `2022-03-15`}),
});
✅ Valid:
{
"dateOfBirth": "1930-01-15"
}
🚨 Invalid (date is greater than max date):
{
"dateOfBirth": "2034-01-15"
}
🚨 Invalid (date includes a time):
{
"dateOfBirth": "2034-01-15T11:30:00Z"
}
🚨 Invalid (date is not valid):
{
"dateOfBirth": "hello world"
}
ParsedDateString
Equivalent to the DateString
schema, except that once you have parsed the value, it is represented as a JavaScript Date
object.
✅ Valid:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
timestamp: s.ParsedDateString(),
});
deepEqual(
MySchema.parse({
timestamp: '2022-03-16',
}),
{
timestamp: new Date('2022-03-16T00:00:00.000Z'),
},
);
deepEqual(
MySchema.serialize({
timestamp: new Date('2022-03-16T00:00:00.000Z'),
}),
{
timestamp: '2022-03-16',
},
);
🚨 Invalid:
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
timestamp: s.ParsedDateString(),
});
MySchema.serialize({
timestamp: new Date('2022-03-16T13:45:00.000Z'),
});
DateTime
Represent a date time as a JavaScript Date
object.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
timestamp: s.DateTime({min: new Date(`2022-03-15`)}),
});
✅ Valid:
const obj = {
timestamp: new Date(`2022-03-16T12:03:00Z`),
};
🚨 Invalid (date is less than min date):
const obj = {
timestamp: new Date(`2022-03-01T12:03:00Z`),
};
🚨 Invalid (date must be valid):
const obj = {
timestamp: new Date(`2022-03-36`),
};
🚨 Invalid (date cannot be represented as a string):
const obj = {
timestamp: `2022-03-16T12:03:00Z`,
};
DateTimeString
Represent a date time as a string
.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
timestamp: s.DateTimeString({min: new Date(`2022-03-15`), strict: true}),
});
✅ Valid:
{
"timestamp": "2022-03-16T12:03:00Z"
}
✅ Valid with strict: false
, but 🚨 invalid with strict: true
(time zone, i.e. Z
, is required):
{
"timestamp": "2022-03-16T12:03:00"
}
```
✅ Valid with `strict: false` (equivalent to setting time to `00:00:00.000Z`), but 🚨 invalid with `strict: true` (time is required):
```json
{
"timestamp": "2022-03-16"
}
✅ Valid with strict: false
(JavaScript treats this as 2022-03-02T12:03:00.000Z
), but 🚨 invalid with strict: true
(30th February is not a real date):
{
"timestamp": "2022-02-30T12:03:00Z"
}
🚨 Invalid (date is less than min date):
{
"timestamp": "2022-03-01T12:03:00Z"
}
🚨 Invalid (date must be valid):
{
"timestamp": "2022-03-36"
}
ParsedDateTimeString
Equivalent to the DateTimeString
schema, except that once you have parsed the value, it is represented as a JavaScript Date
object.
✅ Valid:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
timestamp: s.ParsedDateTimeString({
min: new Date(`2022-03-15`),
strict: true,
}),
});
deepEqual(
MySchema.parse({
timestamp: '2022-03-16T12:03:00Z',
}),
{
timestamp: new Date('2022-03-16T12:03:00Z'),
},
);
deepEqual(
MySchema.serialize({
timestamp: new Date('2022-03-16T12:03:00Z'),
}),
{
timestamp: '2022-03-16T12:03:00.000Z',
},
);
🚨 Invalid:
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
timestamp: s.ParsedDateTimeString(),
});
MySchema.serialize({
timestamp: new Date('hello world'),
});
Float
Validate that a number is not NaN and optionally is within the desired range:
min
defaults to undefined
(i.e. no minimum value).max
defaults to undefined
(i.e. no maximum value).
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
weight: s.Float({min: 0, max: 1}),
});
✅ Valid:
{"weight": 1}
✅ Valid:
{"weight": 0.3456}
🚨 Invalid (value is greater than "max"):
{"weight": 15}
🚨 Invalid (value is not a number
):
{"weight": "1"}
FloatString
Validate that a string represents a floating point number (optionally in the desired range). Using a string can be helpful as it allows you to get precision that may be lost in JavaScript number representations.
min
defaults to undefined
(i.e. no minimum value).max
defaults to undefined
(i.e. no maximum value).
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
weight: s.FloatString({min: `0`, max: `1`}),
});
✅ Valid:
{"weight": "1"}
✅ Valid:
{"weight": "0.3456"}
🚨 Invalid (value does not represent a floating point number):
{"weight": "hello world"}
🚨 Invalid (value is greater than "max"):
{"weight": "15"}
🚨 Invalid (value is not a string
):
{"weight": 1}
ParsedFloatString
Equivalent to the FloatString
schema, except that once you have parsed the value, it is represented as a JavaScript number
. This may result in a loss of precision.
✅ Valid:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
weight: s.ParsedFloatString({min: 0, max: 1}),
});
deepEqual(
MySchema.parse({
level: '0.5',
}),
{
level: 0.5,
},
);
deepEqual(
MySchema.serialize({
level: 0.5,
}),
{
level: '0.5',
},
);
🚨 Invalid:
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
level: s.ParsedIntegerString({min: 1, max: 6}),
});
MySchema.serialize({
level: 42,
});
Integer
Validate that a number is an integer (optionally within the desired range)
min
defaults to Number.MIN_SAFE_INTEGER
(i.e. -9007199254740991
). It can be set to a higher value, but cannot be set to a lower value.max
defaults to Number.MAX_SAFE_INTEGER
(i.e. 9007199254740991
). It can be set to a lower value, but cannot be set to a higher value.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
level: s.Integer({min: 1, max: 6}),
});
✅ Valid:
{"level": 1}
🚨 Invalid (value is not an integer):
{"level": 1.5}
🚨 Invalid (value is greater than "max"):
{"level": 15}
🚨 Invalid (value is not a number
):
{"level": "1"}
IntegerString
Validate that a string contains an integer, (optionally within the desired range)
min
defaults to Number.MIN_SAFE_INTEGER
(i.e. '-9007199254740991'
). It can be set to any integer, even if that integer cannot be represented as a number
in JavaScript.max
defaults to Number.MAX_SAFE_INTEGER
(i.e. '9007199254740991'
). It can be set to any integer, even if that integer cannot be represented as a number
in JavaScript.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
id: s.Integer({
min: `-9999999999999999999999999`,
max: `9999999999999999999999999`,
}),
});
✅ Valid:
{"id": "99"}
✅ Valid (integer strings can be used to represent numbers that are larger than Number.MAX_SAFE_INTEGER
):
{"id": "9999999999999999999999978"}
🚨 Invalid (value is not an integer):
{"level": "1.5"}
🚨 Invalid (value is not a string):
{"level": 15}
🚨 Invalid (value is greater than "max"):
{"level": "9999999999999999999999999999999999999"}
ParsedIntegerString
Equivalent to the IntegerString
schema, except that once you have parsed the value, it is represented as a JavaScript number
. This restricts your integers to between Number.MIN_SAFE_INTEGER
and Number.MAX_SAFE_INTEGER
.
✅ Valid:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
level: s.ParsedIntegerString({min: 1, max: 6}),
});
deepEqual(
MySchema.parse({
level: '3',
}),
{
level: 3,
},
);
deepEqual(
MySchema.serialize({
level: 3,
}),
{
level: '3',
},
);
🚨 Invalid:
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
level: s.ParsedIntegerString({min: 1, max: 6}),
});
MySchema.serialize({
level: 3.14,
});
ParsedJsonString
Transparently parse/serialize to/from JSON. A codec can optionally be provided to handle the parsed value.
✅ Valid:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = s.ParsedJsonString(
t.Object({
level: s.ParsedIntegerString(),
}),
);
deepEqual(MySchema.parse(`{"level": "3"}`), {
level: 3,
});
deepEqual(
MySchema.serialize({
level: 3,
}),
`{"level":"3"}`,
);
🚨 Invalid:
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = s.ParsedJsonString(
t.Object({
level: s.ParsedIntegerString(),
}),
);
MySchema.parse(`{level: '3'}`);
MySchema.parse(`{"level": "3.14"}`);
MySchema.serialize({
level: 3.14,
});
Migrate
A simplified alternative to ParsedValue
/.withParser
for migrating legacy data. The Migrate
cannot be serialized, so it's best used in a Union
where one of the other types in the union handles serialization.
🚀 Migrating an object to a new schema:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Union(
t.Object({
version: t.Literal(2),
width: t.Number,
height: t.Number,
}),
s.Migrate(
t.Object({
version: t.Literal(1),
size: t.Number,
}),
({size}) => ({
version: 2,
width: size,
height: size,
}),
),
);
deepEqual(
MySchema.parse({
version: 2,
width: 10,
height: 15,
}),
{
version: 2,
width: 10,
height: 15,
},
);
deepEqual(
MySchema.parse({
version: 1,
size: 42,
}),
{
version: 2,
width: 42,
height: 42,
},
);
deepEqual(
MySchema.serialize({
version: 2,
width: 10,
height: 15,
}),
{
version: 2,
width: 10,
height: 15,
},
);
🚀 Setting a default for an optional property:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
values: t.Union(
t.Array(t.Number),
s.Migrate(t.Undefined, () => []),
),
});
deepEqual(MySchema.parse({values: [1, 2, 3]}), {values: [1, 2, 3]});
deepEqual(MySchema.parse({values: []}), {values: []});
deepEqual(MySchema.parse({values: undefined}), {values: []});
deepEqual(MySchema.parse({}), {values: []});
deepEqual(MySchema.serialize({values: [1, 2, 3]}), {values: [1, 2, 3]});
deepEqual(MySchema.serialize({values: []}), {values: []});
deepEqual(MySchema.serialize({values: undefined}), {values: []});
deepEqual(MySchema.serialize({}), {values: []});
Url
Represent an absolute URL as a JavaScript URL
object. You can pass options to constrain the valid URLs:
allowedProtocols
- Set of valid protocols, e.g. new Set(['http:', 'https:'])
. Defaults to allowing any protocol.allowedHosts
- Set of valid hosts, e.g. new Set(['example.com'])
. Defaults to allowing any host.allowedOrigins
- Set of allowed origins, e.g. new Set('https://example.com')
. Defaults to allowing any origin.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
href: s.Url({allowedProtocols: new Set([`http:`, `https:`])}),
});
✅ Valid:
const obj = {
href: new URL(`http://example.com`),
};
🚨 Invalid (protocol does not match teh allowed protocols):
const obj = {
href: new URL(`tel:0000000000`),
};
🚨 Invalid (value is not a URL):
const obj = {
href: `http://example.com`,
};
UrlString
Represent an absolute URL as a string
. You can pass options to constrain the valid URLs:
allowedProtocols
- Set of valid protocols, e.g. new Set(['http:', 'https:'])
. Defaults to allowing any protocol.allowedHosts
- Set of valid hosts, e.g. new Set(['example.com'])
. Defaults to allowing any host.allowedOrigins
- Set of allowed origins, e.g. new Set('https://example.com')
. Defaults to allowing any origin.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
href: s.UrlString({allowedProtocols: new Set([`http:`, `https:`])}),
});
✅ Valid (will be normalized to add a trailing /
when parsing):
{"href": "http://example.com"}
✅ Valid:
{"href": "http://example.com/foo/bar"}
🚨 Invalid (protocol does not match teh allowed protocols):
{"href": "tel:0000000000"}
🚨 Invalid (value is not a string):
const obj = {
href: new URL(`http://example.com`),
};
ParsedUrlString
Equivalent to the UrlString
schema, except that once you have parsed the value, it is represented as a JavaScript URL
object.
✅ Valid:
import {deepEqual} from 'assert';
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
href: s.ParsedUrlString(),
});
deepEqual(
MySchema.parse({
href: 'https://example.com/foo/bar',
}),
{
href: new URL('https://example.com/foo/bar'),
},
);
deepEqual(
MySchema.serialize({
href: new URL('https://example.com/foo/bar'),
}),
{
href: 'https://example.com/foo/bar',
},
);