Last active
October 18, 2019 05:37
-
-
Save rhythnic/945299d10008cc571d649926f3a37961 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// *********************************************************************** | |
// Validator Lib | |
// *********************************************************************** | |
const { Success, Failure } = require('folktale/validation') | |
const fieldPath = (...args) => args.join('.') | |
const optional = args => ([ | |
(path, prop) => { | |
let assertions = args.map(x => x(path, prop)) | |
return data => data[prop] == null | |
? Success(data) | |
: assertions.reduce((acc, x) => acc.concat(x(data)), Success(data)) | |
} | |
]) | |
const rootAssertions = (model, path) => Object.keys(model).reduce( | |
(acc, prop) => typeof model[prop] === 'function' | |
? acc | |
: [ ...acc, ...model[prop].map(x => x(path, prop))] | |
, | |
[] | |
) | |
const struct = (model, customValidation) => path => { | |
const assertions = rootAssertions(model, path) | |
const nested = Object.keys(model) | |
.filter(prop => typeof model[prop] === 'function') | |
.map(prop => ({ prop, assertion: model[prop](fieldPath(path, prop)) })) | |
if (!customValidation) customValidation = data => Success(data) | |
return data => assertions | |
.reduce( | |
(acc, assertion) => acc.concat(assertion(data)), | |
nested.reduce((acc, x) => acc.concat(x.assertion(data[x.prop])), Success()) | |
) | |
.concat(customValidation(data)) | |
.map(_ => data) | |
} | |
// *********************************************************************** | |
// Validators | |
// *********************************************************************** | |
const isType = type => (path, prop) => data => | |
typeof data[prop] === type | |
? Success(data) | |
: Failure([`${fieldPath(path, prop)} must have type ${type}`]) | |
const minLength = min => (path, prop) => data => | |
data[prop].length >= min | |
? Success(data) | |
: Failure([`${fieldPath(path, prop)} must have a minimum length of ${min}`]) | |
const hasLength = len => (path, prop) => data => | |
data[prop].length === len | |
? Success(data) | |
: Failure([`${fieldPath(path, prop)} must have length ${len}`]) | |
const equals = val => (path, prop) => data => | |
data[prop] === val | |
? Success(data) | |
: Failure([`${fieldPath(path, prop)} must equal ${val}`]) | |
const isArray = (path, prop) => data => | |
Array.isArray(data[prop]) | |
? Success(data) | |
: Failure(`${fieldPath(path, prop)} must be an array`) | |
const items = modelStruct => (path, prop) => data => | |
data[prop].reduce( | |
(acc, item, i) => acc.concat(modelStruct(fieldPath(path, prop, i))(item)), | |
Success(data) | |
) | |
// *********************************************************************** | |
// Curried Validators | |
// *********************************************************************** | |
const isString = isType('string') | |
// *********************************************************************** | |
// API | |
// *********************************************************************** | |
module.exports = { | |
struct, | |
optional, | |
items, | |
isArray, | |
isType, | |
isString, | |
minLength, | |
hasLength, | |
equals | |
} | |
// *********************************************************************** | |
// Example Usage | |
// *********************************************************************** | |
const idAssertions = [isString, minLength(2)] | |
const userSchema = { | |
username: optional(isString, minLength(2)), | |
pwdHash: [isString, hasLength(16)], | |
email: optional(isString, minLength(6)), | |
company: struct({ | |
id: idAssertions, | |
type: equals('Company') | |
}), | |
friends: [ | |
isArray, | |
items(struct({ | |
id: idAssertions, | |
type: equals('User') | |
})) | |
] | |
} | |
const User = struct(userSchema, data => { | |
// any custom validation here | |
if (!data.email && !data.username) { | |
return Failure(['username required if email not present']) | |
} | |
return Success(data) | |
}) | |
const validateUser = User() | |
// validateUser(data) | |
// Success(data) or | |
// Failure(['username must be a string', 'friends.0.type must equal User']) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment