Skip to content

Instantly share code, notes, and snippets.

@andrejewski
Created October 23, 2017 19:18
Show Gist options
  • Select an option

  • Save andrejewski/e9e13728c2674adfa3c7cb9b3dd465ad to your computer and use it in GitHub Desktop.

Select an option

Save andrejewski/e9e13728c2674adfa3c7cb9b3dd465ad to your computer and use it in GitHub Desktop.
Clojure Spec, but for CanJs
export function getNestedChecks (check) {
if (typeof check !== 'object') {
return check;
}
return (x, key) => {
const props = Object.keys(check);
for (let i = 0; i < props.length; i++) {
const prop = props[i];
const subCheck = getNestedChecks(check[prop]);
const error = subCheck(x[prop], `${key}.${prop}`);
if (error) {
return error;
}
}
};
}
export function getTypeCheck (type, key) {
switch (type) {
case Boolean: return (x, key) => {
if (typeof x !== 'boolean') {
return new Error(`Property "${key}" must be a boolean, not ${x}`);
}
};
case Number: return (x, key) => {
if (typeof x !== 'number') {
return new Error(`Property "${key}" must be a number, not ${x}`);
}
};
case String: return (x, key) => {
if (typeof x !== 'string') {
return new Error(`Property "${key}" must be a string, not ${x}`);
}
};
case Function: return (x, key) => {
if (typeof x !== 'function') {
return new Error(`Property "${key}" must be a function, not ${x}`);
}
};
case Error: return (x, key) => {
if (!(x instanceof Error)) {
return new Error(`Property "${key}" must be an error, not ${x}`);
}
};
}
if (type instanceof Maybe) {
const subType = getNestedChecks(getTypeCheck(type.type, key));
return (x, key) => {
if (x !== null) {
return subType(x, key);
}
};
}
if (Array.isArray(type)) {
const subType = getNestedChecks(getTypeCheck(type[0]));
return (x, key) => {
if (!Array.isArray(x)) {
return new Error(`Property "${key}" must be an array, not ${x}`);
}
for (let i = 0; i < x.length; i++) {
const error = subType(x[i], `${key}[${i}]`);
if (error) {
return error;
}
}
};
}
if (typeof type === 'object') {
return Object.keys(type).reduce((checks, key) => {
const subType = type[key];
const check = getTypeCheck(subType, key);
checks[key] = check;
return checks;
}, {});
}
if (typeof type === 'function') {
return type;
}
throw new Error(`Spec key "${key}" value ${type} is not a valid type check`);
}
function isDev () {
let isDev = false;
// !steal-remove-start
isDev = true;
// !steal-remove-end
return isDev;
}
export function Maybe (type) {
if (!(this instanceof Maybe)) {
return new Maybe(type);
}
this.type = type;
}
export function getCheck (spec, keys) {
const [key, ...rest] = keys;
const check = spec[key];
if (typeof check === 'function') {
return check;
} else if (typeof check === 'object') {
if (rest.length < 1) {
return getNestedChecks(check);
}
return getCheck(check, rest);
}
}
export default function specMap (getSpec) {
const spec = getSpec({maybe: Maybe});
const specChecks = getTypeCheck(spec);
const listener = (event, attribute, how, newValue) => {
const check = getCheck(specChecks, attribute.split('.'));
if (check) {
const error = check(newValue, attribute);
if (error) {
throw error;
}
}
};
return baseMap => !isDev() ? baseMap : baseMap.extend({
init () {
if (!this.__specListener) {
this.__specListener = listener;
this.bind('change', listener);
}
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment