198 lines
6.0 KiB
JavaScript
198 lines
6.0 KiB
JavaScript
import { types } from "./types";
|
|
import { createProps } from "./createProps";
|
|
import { isString } from "util";
|
|
import {
|
|
includes,
|
|
filter,
|
|
map,
|
|
keys,
|
|
flatten,
|
|
flattenDeep,
|
|
each,
|
|
indexOf } from "lodash/fp";
|
|
import { common } from "budibase-core";
|
|
|
|
const pipe = common.$;
|
|
|
|
const makeError = (errors, propName, stack) => (message) =>
|
|
errors.push({
|
|
stack,
|
|
propName,
|
|
error:message});
|
|
|
|
export const recursivelyValidate = (rootProps, getComponent, stack=[]) => {
|
|
|
|
const getComponentPropsDefinition = componentName => {
|
|
if(componentName.includes(":")) {
|
|
const [parentComponent, arrayProp] = componentName.split(":");
|
|
return getComponent(parentComponent)[arrayProp].elementDefinition;
|
|
}
|
|
return getComponent(componentName);
|
|
}
|
|
|
|
if(!rootProps._component) {
|
|
const errs = [];
|
|
makeError(errs, "_component", stack)("Component is not set");
|
|
return errs;
|
|
// this would break everything else anyway
|
|
}
|
|
|
|
const propsDef = getComponentPropsDefinition(
|
|
rootProps._component);
|
|
|
|
const getPropsDefArray = (def) => pipe(def, [
|
|
keys,
|
|
map(k => def[k].name
|
|
? expandPropDef(def[k])
|
|
: ({
|
|
...expandPropDef(def[k]),
|
|
name:k }))
|
|
]);
|
|
|
|
const propsDefArray = getPropsDefArray(propsDef);
|
|
|
|
const errors = validateProps(
|
|
propsDef,
|
|
rootProps,
|
|
stack,
|
|
true);
|
|
|
|
const validateChildren = (_defArray, _props, _stack) => pipe(_defArray, [
|
|
filter(d => d.type === "component"),
|
|
map(d => recursivelyValidate(
|
|
_props[d.name],
|
|
getComponentPropsDefinition,
|
|
[..._stack, d.name])),
|
|
flatten
|
|
]);
|
|
|
|
const childErrors = validateChildren(
|
|
propsDefArray, rootProps, stack);
|
|
|
|
const childArrayErrors = pipe(propsDefArray, [
|
|
filter(d => d.type === "array"),
|
|
map(d => pipe(rootProps[d.name], [
|
|
map(elementProps => pipe(elementProps._component, [
|
|
getComponentPropsDefinition,
|
|
getPropsDefArray,
|
|
arr => validateChildren(
|
|
arr,
|
|
elementProps,
|
|
[...stack,
|
|
`${d.name}[${indexOf(elementProps)(rootProps[d.name])}]`])
|
|
]))
|
|
]))
|
|
]);
|
|
|
|
return flattenDeep([errors, ...childErrors, ...childArrayErrors]);
|
|
}
|
|
|
|
const expandPropDef = propDef => {
|
|
const p = isString(propDef)
|
|
? types[propDef].defaultDefinition()
|
|
: propDef;
|
|
if(p.type === "array" && isString(p.elementDefinition)) {
|
|
p.elementDefinition = types[p.elementDefinition].defaultDefinition()
|
|
}
|
|
return p;
|
|
}
|
|
|
|
|
|
export const validateProps = (propsDefinition, props, stack=[], isFinal=true) => {
|
|
|
|
const errors = [];
|
|
|
|
if(!props._component) {
|
|
makeError(errors, "_component", stack)("Component is not set");
|
|
return errors;
|
|
// this would break everything else anyway
|
|
}
|
|
|
|
for(let propDefName in propsDefinition) {
|
|
|
|
if(propDefName === "_component") continue;
|
|
|
|
const propDef = expandPropDef(propsDefinition[propDefName]);
|
|
|
|
const type = types[propDef.type];
|
|
|
|
const error = makeError(errors, propDefName, stack);
|
|
|
|
const propValue = props[propDefName];
|
|
if(isFinal && propDef.required && propValue) {
|
|
error(`Property ${propDefName} is required`);
|
|
continue;
|
|
}
|
|
|
|
if(!type.isOfType(propValue)) {
|
|
error(`Property ${propDefName} is not of type ${propDef.type}. Actual value ${propValue}`)
|
|
continue;
|
|
}
|
|
|
|
if(propDef.type === "array") {
|
|
let index = 0;
|
|
for(let arrayItem of propValue) {
|
|
arrayItem._component = `${props._component}:${propDefName}`
|
|
const arrayErrs = validateProps(
|
|
propDef.elementDefinition,
|
|
arrayItem,
|
|
[...stack, `${propDefName}[${index}]`],
|
|
isFinal
|
|
)
|
|
for(let arrErr of arrayErrs) {
|
|
errors.push(arrErr);
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
|
|
if(propDef.type === "options"
|
|
&& propValue
|
|
&& !includes(propValue)(propDef.options)) {
|
|
error(`Property ${propDefName} is not one of allowed options. Acutal value is ${propValue}`);
|
|
}
|
|
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
export const validatePropsDefinition = (propsDefinition) => {
|
|
const { errors } = createProps("dummy_component_name", propsDefinition);
|
|
|
|
|
|
// arrar props without elementDefinition
|
|
pipe(propsDefinition, [
|
|
keys,
|
|
map(k => ({
|
|
propDef:propsDefinition[k],
|
|
propName:k
|
|
})),
|
|
filter(d => d.propDef.type === "array" && !d.propDef.elementDefinition),
|
|
each(d => makeError(errors, d.propName)(`${d.propName} does not have a definition for it's item props`))
|
|
]);
|
|
|
|
const arrayPropValidationErrors = pipe(propsDefinition, [
|
|
keys,
|
|
map(k => propsDefinition[k]),
|
|
filter(d => d.type === "array" && d.elementDefinition),
|
|
map(d => validatePropsDefinition(d.elementDefinition)),
|
|
flatten
|
|
]);
|
|
|
|
pipe(propsDefinition, [
|
|
keys,
|
|
map(k => ({
|
|
propDef:propsDefinition[k],
|
|
propName:k
|
|
})),
|
|
filter(d => d.propDef.type === "options"
|
|
&& (!d.propDef.options || d.propDef.options.length === 0)),
|
|
each(d => makeError(errors, d.propName)(`${d.propName} does not have any options`))
|
|
]);
|
|
|
|
return [...errors, ...arrayPropValidationErrors]
|
|
|
|
}
|
|
|