packages/refine/Refine_PrimitiveCheckers.js (86 lines of code) (raw):
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* refine: type-refinement combinator library for checking mixed values
* see wiki for more info: https://fburl.com/wiki/14q16qqy
*
* @emails oncall+monitoring_interfaces
* @flow strict
* @format
*/
'use strict';
import type {Checker} from './Refine_Checkers';
const {Path, compose, failure, success} = require('./Refine_Checkers');
/**
* a mixed (i.e. untyped) value
*/
function mixed(): Checker<mixed> {
return MIXED_CHECKER;
}
const MIXED_CHECKER: Checker<mixed> = value => success(value, []);
/**
* checker to assert if a mixed value matches a literal value
*/
function literal<T: string | boolean | number | null | void>(
literalValue: T,
): Checker<T> {
const str = value => JSON.stringify(value);
return (value, path = new Path()) => {
return value === literalValue
? success(literalValue, [])
: failure(`value is not literal ${str(literalValue) ?? 'void'}`, path);
};
}
/**
* boolean value checker
*/
function boolean(): Checker<boolean> {
return (value, path = new Path()) =>
typeof value === 'boolean'
? success(value, [])
: failure('value is not a boolean', path);
}
/**
* checker to assert if a mixed value is a number
*/
function number(): Checker<number> {
return (value, path = new Path()) =>
typeof value === 'number'
? success(value, [])
: failure('value is not a number', path);
}
/**
* Checker to assert if a mixed value is a string.
*
* Provide an optional RegExp template to match string against.
*/
function string(regex?: RegExp): Checker<string> {
return (value, path = new Path()) => {
if (typeof value !== 'string') {
return failure('value is not a string', path);
}
if (regex != null && !regex.test(value)) {
return failure(`value does not match regex: ${regex.toString()}`, path);
}
return success(value, []);
};
}
/**
* Checker to assert if a mixed value matches a union of string literals.
* Legal values are provided as key/values in an object and may be translated by
* providing different values in the object.
*
* For example:
* ```jsx
* const suitChecker = stringLiterals({
* heart: 'heart',
* spade: 'spade',
* club: 'club',
* diamond: 'diamond',
* });
*
* const suit: 'heart' | 'spade' | 'club' | 'diamond' = assertion(suitChecker())(x);
* ```
*
* Strings can also be mapped to new values:
* ```jsx
* const placeholderChecker = stringLiterals({
* foo: 'spam',
* bar: 'eggs',
* });
* ```
*
* It can be useful to have a single source of truth for your literals. To
* only specify them once and use it for both the Flow union type and the
* runtime checker you can use the following pattern:
* ```jsx
* const suits = {
* heart: 'heart',
* spade: 'spade',
* club: 'club',
* diamond: 'diamond',
* };
* type Suit = $Values<typeof suits>;
* const suitChecker = stringLiterls(suits);
* ```
*/
function stringLiterals<T: {+[string]: string}>(
enumValues: T,
): Checker<$Values<T>> {
return (value, path = new Path()) => {
if (!(typeof value === 'string')) {
return failure('value must be a string', path);
}
const out = enumValues[value];
if (out == null) {
return failure(
`value is not one of ${Object.values(enumValues).join(', ')}`,
path,
);
}
return success(out, []);
};
}
/**
* checker to assert if a mixed value is a Date object
*
* For example:
* ```jsx
* const dateChecker = date();
*
* assertion(dateChecker())(new Date());
* ```
*/
function date(): Checker<Date> {
return (value, path = new Path()) => {
if (!(value instanceof Date)) {
return failure('value is not a date', path);
}
if (isNaN(value)) {
return failure('invalid date', path);
}
return success(value, []);
};
}
/**
* checker to coerce a date string to a Date object. This is useful for input
* that was from a JSON encoded `Date` object.
*
* For example:
* ```jsx
* const jsonDateChecker = coerce(jsonDate({encoding: 'string'}));
*
* jsonDateChecker('October 26, 1985');
* jsonDateChecker('1955-11-05T07:00:00.000Z');
* jsonDateChecker(JSON.stringify(new Date()));
* ```
*/
function jsonDate(): Checker<Date> {
return compose(string(), ({value, warnings}, path) => {
const parsedDate = new Date(value);
return Number.isNaN(parsedDate)
? failure('value is not valid date string', path)
: success(parsedDate, warnings);
});
}
module.exports = {
mixed,
literal,
boolean,
number,
string,
stringLiterals,
date,
jsonDate,
};