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()});
{"data": "abc=="}
{"data": "!~~"}
ParsedBase64String
Extends Base64String
by transparently converting the string between a utf8 string and a base64 representation.
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.
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()})),
);
// ✅ Valid:
deepEqual(assertMySchema.parse('eyJ2YWx1ZSI6NDJ9'), {value: 42});
// ✅ Valid:
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}),
});
{
"nonEmptyString": "hello world",
"limitedList": ["a", "b", "c"]
}
{
"nonEmptyString": "",
"limitedList": ["a", "b", "c"]
}
{
"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`}),
});
{
"dateOfBirth": "1930-01-15"
}
{
"dateOfBirth": "2034-01-15"
}
{
"dateOfBirth": "2034-01-15T11:30:00Z"
}
{
"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.
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',
},
);
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
timestamp: s.ParsedDateString(),
});
// The next line throws an error because there is a time
// component:
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`)}),
});
const obj = {
timestamp: new Date(`2022-03-16T12:03:00Z`),
};
const obj = {
timestamp: new Date(`2022-03-01T12:03:00Z`),
};
const obj = {
timestamp: new Date(`2022-03-36`),
};
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}),
});
{
"timestamp": "2022-03-16T12:03:00Z"
}
strict: false
, but 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"
}
strict: false
(JavaScript treats this as 2022-03-02T12:03:00.000Z
), but strict: true
(30th February is not a real date):
{
"timestamp": "2022-02-30T12:03:00Z"
}
{
"timestamp": "2022-03-01T12:03:00Z"
}
{
"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.
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',
},
);
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
timestamp: s.ParsedDateTimeString(),
});
// The next line throws an error because the
// timestamp does not represent a valid Date:
MySchema.serialize({
timestamp: new Date('hello world'),
});
Float
Validate that a number is not NaN and optionally is within the desired range:
-
min
defaults toundefined
(i.e. no minimum value). -
max
defaults toundefined
(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}),
});
{"weight": 1}
{"weight": 0.3456}
{"weight": 15}
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 toundefined
(i.e. no minimum value). -
max
defaults toundefined
(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`}),
});
{"weight": "1"}
{"weight": "0.3456"}
{"weight": "hello world"}
{"weight": "15"}
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.
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',
},
);
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
level: s.ParsedIntegerString({min: 1, max: 6}),
});
// The next line throws an error because the value is outside the requested range:
MySchema.serialize({
level: 42,
});
Integer
Validate that a number is an integer (optionally within the desired range)
-
min
defaults toNumber.MIN_SAFE_INTEGER
(i.e.-9007199254740991
). It can be set to a higher value, but cannot be set to a lower value. -
max
defaults toNumber.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}),
});
{"level": 1}
{"level": 1.5}
{"level": 15}
number
):
{"level": "1"}
IntegerString
Validate that a string contains an integer, (optionally within the desired range)
-
min
defaults toNumber.MIN_SAFE_INTEGER
(i.e.'-9007199254740991'
). It can be set to any integer, even if that integer cannot be represented as anumber
in JavaScript. -
max
defaults toNumber.MAX_SAFE_INTEGER
(i.e.'9007199254740991'
). It can be set to any integer, even if that integer cannot be represented as anumber
in JavaScript.
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
id: s.Integer({
min: `-9999999999999999999999999`,
max: `9999999999999999999999999`,
}),
});
{"id": "99"}
Number.MAX_SAFE_INTEGER
):
{"id": "9999999999999999999999978"}
{"level": "1.5"}
{"level": 15}
{"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
.
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',
},
);
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = t.Object({
level: s.ParsedIntegerString({min: 1, max: 6}),
});
// The next line throws an error because the value is not an integer:
MySchema.serialize({
level: 3.14,
});
ParsedJsonString
Transparently parse/serialize to/from JSON. A codec can optionally be provided to handle the parsed value.
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"}`,
);
import * as t from 'funtypes';
import * as s from 'funtypes-schemas';
const MySchema = s.ParsedJsonString(
t.Object({
level: s.ParsedIntegerString(),
}),
);
// The next line throws an error because the string is not valid JSON:
MySchema.parse(`{level: '3'}`);
// The next line throws an error because the value is not an integer:
MySchema.parse(`{"level": "3.14"}`);
// The next line throws an error because the value is not an integer:
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.
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,
}),
),
);
// ✅ Valid:
deepEqual(
MySchema.parse({
version: 2,
width: 10,
height: 15,
}),
{
version: 2,
width: 10,
height: 15,
},
);
// ✅ Valid:
deepEqual(
MySchema.parse({
version: 1,
size: 42,
}),
{
version: 2,
width: 42,
height: 42,
},
);
// ✅ Valid:
deepEqual(
MySchema.serialize({
version: 2,
width: 10,
height: 15,
}),
{
version: 2,
width: 10,
height: 15,
},
);
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, () => []),
),
});
// ✅ Valid:
deepEqual(MySchema.parse({values: [1, 2, 3]}), {values: [1, 2, 3]});
// ✅ Valid:
deepEqual(MySchema.parse({values: []}), {values: []});
// ✅ Valid:
deepEqual(MySchema.parse({values: undefined}), {values: []});
// ✅ Valid:
deepEqual(MySchema.parse({}), {values: []});
// ✅ Valid:
deepEqual(MySchema.serialize({values: [1, 2, 3]}), {values: [1, 2, 3]});
// ✅ Valid:
deepEqual(MySchema.serialize({values: []}), {values: []});
// 🚨 Invalid:
deepEqual(MySchema.serialize({values: undefined}), {values: []});
// 🚨 Invalid:
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:`])}),
});
const obj = {
href: new URL(`http://example.com`),
};
const obj = {
href: new URL(`tel:0000000000`),
};
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:`])}),
});
/
when parsing):
{"href": "http://example.com"}
{"href": "http://example.com/foo/bar"}
{"href": "tel:0000000000"}
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.
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',
},
);