Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass the top-level inputs object to the predicate functions as a third argument. #102

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"chai": "^4.2.0",
"eslint": "^4.0.0",
"eslint-config-airbnb-base": "^11.2.0",
"eslint-plugin-import": "^2.3.0",
Expand All @@ -51,7 +52,8 @@
"rollup-plugin-flow": "^1.1.1",
"rollup-plugin-node-resolve": "^3.0.0",
"rollup-plugin-replace": "^1.1.1",
"rollup-plugin-uglify": "^2.0.1"
"rollup-plugin-uglify": "^2.0.1",
"sinon": "^7.2.2"
},
"dependencies": {
"ramda": "^0.24.1"
Expand Down
11 changes: 6 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const transform = (successFn: Function, failFn: Function, input: Array<any>): an
*/
const runPredicate = ([predicate, errorMsg]:[Function, string],
value:any,
inputs:Object, field:string) => predicate(value, inputs) // eslint-disable-line no-nested-ternary
inputs:Object, globalInput:Object, field:string) => predicate(value, inputs, globalInput) // eslint-disable-line no-nested-ternary
? true
: typeof errorMsg === 'function'
? errorMsg(value, field)
Expand All @@ -45,20 +45,21 @@ const runPredicate = ([predicate, errorMsg]:[Function, string],
* @param {Object|Function} input the validation input data
* @returns {{}}
*/
export const validate = curry((successFn: Function, failFn: Function, spec: Object, input: Object): Object => {
export const validate = curry((successFn: Function, failFn: Function, spec: Object, input: Object, initialInput = null): Object => {
const inputFn = typeof input === 'function' ? input : (key?: string) => key ? input : input
const globalInput = initialInput || input
const keys = Object.keys(inputFn())
return reduce((result, key) => {
const inputObj = inputFn(key)
const value = inputObj[key]
const predicates = spec[key]
if (Array.isArray(predicates)) {
return { ...result, [key]: transform(() => successFn(value), failFn, map(f => runPredicate(f, value, inputObj, key), predicates)) }
return { ...result, [key]: transform(() => successFn(value), failFn, map(f => runPredicate(f, value, inputObj, globalInput, key), predicates)) }
} else if (typeof predicates === 'object') {
return { ...result, [key]: validate(successFn, failFn, predicates, value) }
return { ...result, [key]: validate(successFn, failFn, predicates, value, globalInput) }
} else if (typeof predicates === 'function') {
const rules = predicates(value)
return { ...result, [key]: validate(successFn, failFn, rules, value) }
return { ...result, [key]: validate(successFn, failFn, rules, value, globalInput) }
} else {
return { ...result, [key]: successFn([]) }
}
Expand Down
116 changes: 116 additions & 0 deletions src/spectedSchema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/* @flow */
import {
curry,
compose,
_,
} from 'ramda';

const simpleTypes = [
'string',
'number',
'integer',
];

export default class Jss {
constructor(opts = {}) {
const {
rules = {},
} = opts;
this.rules = { ...rules };
this.clean = this.clean.bind(this);
this.compileRules = this.compileRules.bind(this);
this.applyRules = this.applyRules.bind(this);
}

addRule = (name, handler) => {
if (typeof this.rules[name] !== 'undefined') {
throw new Error('A rule with the same name already exists');
}
this.rules[name] = handler;
};

compileRules = rulesConfig => {
if (!Array.isArray(rulesConfig)) {
throw new Error('Improperly configured. Rules should be an array of values');
}
const handlers = rulesConfig.map(config => {
if (Array.isArray(config)) {
if (config.length !== 2) {
throw new Error('Improperly configured. If a rule is specified as an array it should have length 2.');
}
const ruleHandler = this.rules[config[0]];
if (typeof ruleHandler === 'undefined') {
throw new Error(`Cannot find rule ${config[0]}`);
}
const curriedHanlder = curry(ruleHandler);
return curriedHanlder(_, config[1]);
} else {
const ruleHandler = this.rules[config];
return ruleHandler;
}
});

const combinedHandler = compose(...handlers.reverse());
return combinedHandler;
};

applyRules = (rulesConfig, data) => {
const handler = this.compileRules(rulesConfig);
return handler(data);
};

clean = (schema: Object, data, required) => {
if (simpleTypes.indexOf(schema.type) > -1) {
const hasData = (typeof data !== 'undefined');
if (!required && !hasData && schema.default) {
return schema.default;
}
if (required && !hasData) {
throw new Error('Invalid data');
}
if (schema.rules && hasData) {
return this.applyRules(schema.rules, data);
}
if (!required && !hasData) {
return;
}
return data;
}
if (schema.properties) {
// Clean properties
const cleaned = {};
const requiredProps = schema.required || [];

Object.keys(schema.properties).forEach(propKey => {
const propsSchema = schema.properties[propKey];
const nextData = data[propKey];
const isRequired = requiredProps.indexOf(propKey) > -1;
const cleanedPart = this.clean(propsSchema, nextData, isRequired);
if (typeof cleanedPart !== 'undefined') {
cleaned[propKey] = cleanedPart;
}
});
return cleaned;
} else if (schema.allOf) {
// Collect all properties
let cleanedParts = {};
schema.allOf.forEach(subSchema => {
const cleanedPart = this.clean(subSchema, data);
cleanedParts = { ...cleanedParts, ...cleanedPart };
});
return cleanedParts;
} else if (schema.anyOf) {
console.log('Skipping validation because of anyOf keyword');
return data;
} else if (schema.oneOf) {
console.log('Skipping validation because of oneOf keyword');
return data;
}

if (schema.type === 'array') {
return data.map(value => this.clean(schema.items, value));
}

throw new Error('Unsupported schema');
};
}
23 changes: 23 additions & 0 deletions test/allOfSchema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
{
"properties": {
"email": {
"type": "string"
}
}
}
]
}
6 changes: 6 additions & 0 deletions test/arraySchema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "array",
"items": {
"type": "string"
}
}
25 changes: 25 additions & 0 deletions test/complexArraySchema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"type": "object",
"properties": {
"names": {
"type": "array",
"items": {
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
}
}
}
},
"ids": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
Loading