2019-07-15 08:12:52 +02:00
|
|
|
import {
|
2020-02-03 10:24:25 +01:00
|
|
|
filter,
|
|
|
|
union,
|
|
|
|
constant,
|
|
|
|
map,
|
|
|
|
flatten,
|
|
|
|
every,
|
|
|
|
uniqBy,
|
|
|
|
some,
|
|
|
|
includes,
|
|
|
|
isEmpty,
|
|
|
|
has,
|
|
|
|
} from "lodash/fp"
|
2020-03-24 17:12:46 +01:00
|
|
|
import { compileCode } from "../common/compileCode"
|
2019-07-15 08:12:52 +02:00
|
|
|
import {
|
2020-02-03 10:24:25 +01:00
|
|
|
$,
|
|
|
|
isSomething,
|
|
|
|
switchCase,
|
|
|
|
anyTrue,
|
|
|
|
isNonEmptyArray,
|
|
|
|
executesWithoutException,
|
|
|
|
isNonEmptyString,
|
|
|
|
defaultCase,
|
|
|
|
} from "../common"
|
2019-07-15 08:12:52 +02:00
|
|
|
import {
|
2020-02-03 10:24:25 +01:00
|
|
|
isRecord,
|
|
|
|
isRoot,
|
|
|
|
isaggregateGroup,
|
|
|
|
isIndex,
|
|
|
|
getFlattenedHierarchy,
|
|
|
|
} from "./hierarchy"
|
|
|
|
import { eventsList } from "../common/events"
|
|
|
|
import { validateAllFields } from "./fields"
|
2019-07-15 08:12:52 +02:00
|
|
|
import {
|
2020-02-03 10:24:25 +01:00
|
|
|
applyRuleSet,
|
|
|
|
makerule,
|
|
|
|
stringNotEmpty,
|
2019-07-15 08:12:52 +02:00
|
|
|
validationError,
|
2020-02-03 10:24:25 +01:00
|
|
|
} from "../common/validationCommon"
|
|
|
|
import { indexRuleSet } from "./indexes"
|
|
|
|
import { validateAllAggregates } from "./validateAggregate"
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
export const ruleSet = (...sets) => constant(flatten([...sets]))
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
const commonRules = [
|
2020-02-03 10:24:25 +01:00
|
|
|
makerule("name", "node name is not set", node => stringNotEmpty(node.name)),
|
|
|
|
makerule(
|
|
|
|
"type",
|
|
|
|
"node type not recognised",
|
|
|
|
anyTrue(isRecord, isRoot, isIndex, isaggregateGroup)
|
|
|
|
),
|
|
|
|
]
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
const recordRules = [
|
2020-02-03 10:24:25 +01:00
|
|
|
makerule("fields", "no fields have been added to the record", node =>
|
|
|
|
isNonEmptyArray(node.fields)
|
|
|
|
),
|
|
|
|
makerule(
|
|
|
|
"validationRules",
|
|
|
|
"validation rule is missing a 'messageWhenValid' member",
|
|
|
|
node => every(r => has("messageWhenInvalid")(r))(node.validationRules)
|
|
|
|
),
|
|
|
|
makerule(
|
|
|
|
"validationRules",
|
|
|
|
"validation rule is missing a 'expressionWhenValid' member",
|
|
|
|
node => every(r => has("expressionWhenValid")(r))(node.validationRules)
|
|
|
|
),
|
|
|
|
]
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
const aggregateGroupRules = [
|
2020-02-03 10:24:25 +01:00
|
|
|
makerule(
|
|
|
|
"condition",
|
|
|
|
"condition does not compile",
|
|
|
|
a =>
|
|
|
|
isEmpty(a.condition) ||
|
2020-03-24 17:12:46 +01:00
|
|
|
executesWithoutException(() => compileCode(a.condition))
|
2020-02-03 10:24:25 +01:00
|
|
|
),
|
|
|
|
]
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
const getRuleSet = node =>
|
|
|
|
switchCase(
|
|
|
|
[isRecord, ruleSet(commonRules, recordRules)],
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
[isIndex, ruleSet(commonRules, indexRuleSet)],
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
[isaggregateGroup, ruleSet(commonRules, aggregateGroupRules)],
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
[defaultCase, ruleSet(commonRules, [])]
|
|
|
|
)(node)
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
export const validateNode = node => applyRuleSet(getRuleSet(node))(node)
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
export const validateAll = appHierarchy => {
|
|
|
|
const flattened = getFlattenedHierarchy(appHierarchy)
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
const duplicateNameRule = makerule(
|
2020-02-03 10:24:25 +01:00
|
|
|
"name",
|
|
|
|
"node names must be unique under shared parent",
|
|
|
|
n =>
|
|
|
|
filter(f => f.parent() === n.parent() && f.name === n.name)(flattened)
|
|
|
|
.length === 1
|
|
|
|
)
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
const duplicateNodeKeyErrors = $(flattened, [
|
|
|
|
map(n => applyRuleSet([duplicateNameRule])(n)),
|
|
|
|
filter(isSomething),
|
|
|
|
flatten,
|
2020-02-03 10:24:25 +01:00
|
|
|
])
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
const fieldErrors = $(flattened, [
|
|
|
|
filter(isRecord),
|
|
|
|
map(validateAllFields),
|
|
|
|
flatten,
|
2020-02-03 10:24:25 +01:00
|
|
|
])
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
const aggregateErrors = $(flattened, [
|
|
|
|
filter(isaggregateGroup),
|
2020-02-03 10:24:25 +01:00
|
|
|
map(s => validateAllAggregates(s.aggregates)),
|
2019-07-15 08:12:52 +02:00
|
|
|
flatten,
|
2020-02-03 10:24:25 +01:00
|
|
|
])
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
return $(flattened, [
|
|
|
|
map(validateNode),
|
|
|
|
flatten,
|
|
|
|
union(duplicateNodeKeyErrors),
|
|
|
|
union(fieldErrors),
|
|
|
|
union(aggregateErrors),
|
2020-02-03 10:24:25 +01:00
|
|
|
])
|
|
|
|
}
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
const actionRules = [
|
2020-02-03 10:24:25 +01:00
|
|
|
makerule("name", "action must have a name", a => isNonEmptyString(a.name)),
|
|
|
|
makerule("behaviourName", "must supply a behaviour name to the action", a =>
|
|
|
|
isNonEmptyString(a.behaviourName)
|
|
|
|
),
|
|
|
|
makerule(
|
|
|
|
"behaviourSource",
|
|
|
|
"must supply a behaviour source for the action",
|
|
|
|
a => isNonEmptyString(a.behaviourSource)
|
|
|
|
),
|
|
|
|
]
|
|
|
|
|
|
|
|
const duplicateActionRule = makerule("", "action name must be unique", () => {})
|
|
|
|
|
|
|
|
const validateAction = action => applyRuleSet(actionRules)(action)
|
|
|
|
|
|
|
|
export const validateActions = allActions => {
|
2019-07-15 08:12:52 +02:00
|
|
|
const duplicateActions = $(allActions, [
|
|
|
|
filter(a => filter(a2 => a2.name === a.name)(allActions).length > 1),
|
|
|
|
map(a => validationError(duplicateActionRule, a)),
|
2020-02-03 10:24:25 +01:00
|
|
|
])
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
const errors = $(allActions, [
|
|
|
|
map(validateAction),
|
|
|
|
flatten,
|
|
|
|
union(duplicateActions),
|
2020-02-03 10:24:25 +01:00
|
|
|
uniqBy("name"),
|
|
|
|
])
|
|
|
|
|
|
|
|
return errors
|
|
|
|
}
|
|
|
|
|
|
|
|
const triggerRules = actions => [
|
|
|
|
makerule("actionName", "must specify an action", t =>
|
|
|
|
isNonEmptyString(t.actionName)
|
|
|
|
),
|
|
|
|
makerule("eventName", "must specify and event", t =>
|
|
|
|
isNonEmptyString(t.eventName)
|
|
|
|
),
|
|
|
|
makerule(
|
|
|
|
"actionName",
|
|
|
|
"specified action not supplied",
|
|
|
|
t => !t.actionName || some(a => a.name === t.actionName)(actions)
|
|
|
|
),
|
|
|
|
makerule(
|
|
|
|
"eventName",
|
|
|
|
"invalid Event Name",
|
|
|
|
t => !t.eventName || includes(t.eventName)(eventsList)
|
|
|
|
),
|
|
|
|
makerule(
|
|
|
|
"optionsCreator",
|
|
|
|
"Options Creator does not compile - check your expression",
|
|
|
|
t => {
|
|
|
|
if (!t.optionsCreator) return true
|
2019-07-15 08:12:52 +02:00
|
|
|
try {
|
2020-02-03 10:24:25 +01:00
|
|
|
compileCode(t.optionsCreator)
|
|
|
|
return true
|
|
|
|
} catch (_) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
),
|
|
|
|
makerule(
|
|
|
|
"condition",
|
|
|
|
"Trigger condition does not compile - check your expression",
|
|
|
|
t => {
|
|
|
|
if (!t.condition) return true
|
2019-07-15 08:12:52 +02:00
|
|
|
try {
|
2020-03-24 17:12:46 +01:00
|
|
|
compileCode(t.condition)
|
2020-02-03 10:24:25 +01:00
|
|
|
return true
|
|
|
|
} catch (_) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
),
|
|
|
|
]
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
export const validateTrigger = (trigger, allActions) => {
|
2020-02-03 10:24:25 +01:00
|
|
|
const errors = applyRuleSet(triggerRules(allActions))(trigger)
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
return errors
|
|
|
|
}
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
export const validateTriggers = (triggers, allActions) =>
|
|
|
|
$(triggers, [map(t => validateTrigger(t, allActions)), flatten])
|