A library to coerce values at run-time.
Table of Contents
Introduction
Contented is a TypeScript library for performing type coercion at run-time. To this end, Contented introduces run-time representations of primitive types, such as string
, which can be then mixed and matched to describe compound types.
import { string, number, at, combine, coerceTo } from '@gucciogucci/contented';
const Image = combine(
(url, size) => ({ url, size }),
at('url', string),
at(['metadata', 'size'], number)
);
const image = coerceTo(Image, data );
Contented may be useful every time there are expectations — but no real guarantees, on the shape of data acquired at run-time. Common use cases include processing data coming over the wire, from files, or any other external source.
Reference
Coercing
coerceTo(T, input)
Attempts to coerce the input
data to the type represented by T
. Note that the specific return value, whether successful or not, depends on the particular T
.
import { string, coerceTo } from '@gucciogucci/contented';
coerceTo(string, 'hello');
coerceTo(string, 42);
Primitive types
string
A run-time representation of the string
type. An attempt to coerce to string
may result in either the string itself (if the input data is indeed a string) or an InvalidCoercion
error.
import { string, coerceTo } from '@gucciogucci/contented';
coerceTo(string, 'hello');
coerceTo(string, 42);
number
A run-time representation of the number
type. An attempt to coerce to number
may result in either the number itself (if the input data is indeed a number) or an InvalidCoercion
error.
import { number, coerceTo } from '@gucciogucci/contented';
coerceTo(number, 42);
coerceTo(number, 'hello');
boolean
A run-time representation of the boolean
type. An attempt to coerce to boolean
may result in either the boolean itself (if the input data is indeed a boolean) or an InvalidCoercion
error.
import { boolean, coerceTo } from '@gucciogucci/contented';
coerceTo(boolean, true);
coerceTo(boolean, 'hello');
Object types
at(path, T)
Constructs a run-time type that expects the input data to be an object such that there exists a value of type T
under the keys specified in path
.
import { string, at, coerceTo } from '@gucciogucci/contented';
const stringAtAB = at(['a', 'b'], string);
coerceTo(stringAtAB, { a: { b: 'hello' } });
coerceTo(stringAtAB, { a: { c: 'hello' } });
coerceTo(stringAtAB, 'hello');
When the path consists of a single key, such a key can be supplied without the need of enclosing it in an array.
coerceTo(at('a', string), { a: 'hello' });
fallback(T, substitute)
fallback
works in tandem with at
to provide a fallback value in case the input data does not contain the specified keys. Apart from removing the possibility of a MissingKey
error, fallback
retains the same behavior as the at
it wraps.
import { number, at, fallback, coerceTo } from '@gucciogucci/contented';
const numberAtAB = fallback(at(['a', 'b'], number), 42);
coerceTo(numberAtAB, { a: { c: 3 } });
coerceTo(numberAtAB, { a: { b: 3 } });
Array types
arrayOf(T)
A run-time representation of an array of T
s, where T
denotes the run-time representation of its element type.
import { number, arrayOf, coerceTo } from '@gucciogucci/contented';
coerceTo(arrayOf(number), [3, 4, 5]);
coerceTo(arrayOf(number), 'hello');
coerceTo(arrayOf(number), [3, 'a', 5]);
permissiveArrayOf(T)
A run-time representation of an array of T
s, where T
denotes the run-time representation of its element type.
The distinctive feature of a permissiveArrayOf(T)
is that it skips elements that are not recognized as T
. This is different from arrayOf(T)
, which instead stops as soon as one element is not recognized.
import { number, permissiveArrayOf, coerceTo } from '@gucciogucci/contented';
coerceTo(permissiveArrayOf(number), [3, 4, 5]);
coerceTo(permissiveArrayOf(number), [3, 'a', 5]);
Narrowing
match(value)
A run-time representation of the narrowest type that can be constructed from value
. Hence, coercions to match(value)
succeed only when value
is provided as an input.
import { match, coerceTo } from '@gucciogucci/contented';
coerceTo(match('hello'), 'hello');
coerceTo(match('hello'), 'foo');
always(value)
A run-time type that always succeeds with value
regardless of the input data.
import { always, coerceTo } from '@gucciogucci/contented';
coerceTo(always(20), 'hello');
coerceTo(always(20), false);
Combinations & Alternatives
combine(fn, ...Ts)
combine
constructs a run-time type from some known run-time types Ts
and a function fn
. Coercing to combine(fn, ...Ts)
results in an attempt to coerce the input data to each type specified in Ts
; if every coercion ends up successful, the resulting values are passed to the function fn
.
import { combine, string, coerceTo } from '@gucciogucci/contented';
const User = combine(
(name, surname, phone) => ({ fullname: `${name} ${surname}`, phone }),
at('name', string),
at('surname', string),
at(['contacts', 'phone'], string)
);
coerceTo(User, {
name: 'John',
surname: 'Smith',
contacts: {
phone: '055-123404',
email: 'john@smith.com',
}
});
coerceTo(User, { name: 42 });
combineIntoObject({ [...Keys]: [...Ts] })
combineIntObject
is a convenience function that may be used every time there is the need of combining some known run-time types Ts
into an object of known keys. In other words, instead of writing the following:
import { string, number, at, combine, coerceTo } from '@gucciogucci/contented';
const Image = combine(
(url, size) => ({ url, size }),
at('url', string),
at(['metadata', 'size'], number)
);
const image = coerceTo(Image, data);
One may simply write:
import { string, number, at, combineIntoObject, coerceTo } from '@gucciogucci/contented';
const Image = combineIntoObject({
url: at('url', string),
size: at(['metadata', 'size'], number)
});
const image = coerceTo(Image, data);
T1.or(T2)
A run-time representation of the union type T1 | T2
. In case of a failed coercion, the result encloses the errors coming from both T1
and T2
.
import { string, number, at, coerceTo } from '@gucciogucci/contented';
coerceTo(string.or(number), 'hello');
coerceTo(string.or(number), true);
coerceTo(string.or(at('a', number)), { a: true });
optional(T)
Marks the type T
as optional. That is, not only optional(T)
succeeds for every valid value of type T
, but also when undefined
is passed as an input. It is equivalent to T.or(always(undefined))
.
import { optional, string, coerceTo } from '@gucciogucci/contented';
coerceTo(optional(string), undefined);
Errors
InvalidCoercion
When the input data does not conform to the expected primitive type, coerceTo
returns a InvalidCoercion
, which contains both the expectation and the actual value.
import { string, coerceTo } from '@gucciogucci/contented';
coerceTo(string, 42);
AtKey<InvalidCoercion>
An InvalidCoercion
error, together with the path at which to find the non-conforming data.
import { number, arrayOf, at, coerceTo } from '@gucciogucci/contented';
coerceTo(at('x', number), { x: 'hello' });
coerceTo(arrayOf(number), [3, 'a', 5]);
MissingKey
The path at which a non-existing key in the input data was instead expected.
import { number, at, coerceTo } from '@gucciogucci/contented';
coerceTo(at('x', number), { y: 12 });
Joint<[...Errs]>
When multiple alternatives are provided but none of them is applicable to the input data, coerceTo
returns a Joint
error, reporting the errors resulting from the different failed attempts.
import { string, number, coerceTo } from '@gucciogucci/contented';
coerceTo(string.or(number), true);
Utility types
Infer<typeof T>
Infer
comes in handy every time it is necessary to infer the compile-time type corresponding to some run-time representation T
.
import { Infer, coerceTo } from '@gucciogucci/contented';
const User = combine(
(name, surname, phone) => ({ fullname: `${name} ${surname}`, phone }),
at('name', string),
at('surname', string),
at(['contacts', 'phone'], string)
);
function fn(user: Infer<typeof User>) {
}
License
Copyright 2022 Gucci.
Licensed under the GNU Lesser General Public License, Version 3.0