budibase/packages/builder/src/userInterface/pagesParsing/validateProps.js

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]
}