Add support for validating relationships

This commit is contained in:
Andrew Kingston 2021-08-11 14:34:22 +01:00
parent ce29f27c89
commit b79b75ad78
2 changed files with 87 additions and 70 deletions

View File

@ -25,6 +25,10 @@
label: "Required",
value: "required",
},
MinLength: {
label: "Min length",
value: "minLength",
},
MaxLength: {
label: "Max length",
value: "maxLength",
@ -93,10 +97,10 @@
["attachment"]: [Constraints.Required],
["link"]: [
Constraints.Required,
Constraints.Equal,
Constraints.NotEqual,
Constraints.Contains,
Constraints.NotContains,
Constraints.MinLength,
Constraints.MaxLength,
],
}
@ -255,7 +259,9 @@
bind:value={rule.valueType}
options={["Binding", "Value"]}
/>
{#if rule.valueType === "Binding"}
<!-- Bindings always get a bindable input -->
<DrawerBindableInput
placeholder="Constraint value"
value={rule.value}
@ -263,29 +269,38 @@
disabled={rule.constraint === "required"}
on:change={e => (rule.value = e.detail)}
/>
{:else if ["string", "number", "options", "longform"].includes(rule.type)}
{:else if ["maxLength", "minLength", "regex", "notRegex", "contains", "notContains"].includes(rule.constraint)}
<!-- Certain constraints always need string values-->
<Input
disabled={rule.constraint === "required"}
bind:value={rule.value}
placeholder="Constraint value"
/>
{:else if fieldType === "boolean"}
<Select
disabled={rule.constraint === "required"}
options={[
{ label: "True", value: "true" },
{ label: "False", value: "false" },
]}
bind:value={rule.value}
/>
{:else if fieldType === "datetime"}
<DatePicker
enableTime={false}
disabled={rule.constraint === "required"}
bind:value={rule.value}
/>
{:else}
<DrawerBindableInput disabled />
<!-- Otherwise we render a component based on the type -->
{#if ["string", "number", "options", "longform"].includes(rule.type) || ["contains"]}
<Input
disabled={rule.constraint === "required"}
bind:value={rule.value}
placeholder="Constraint value"
/>
{:else if fieldType === "boolean"}
<Select
disabled={rule.constraint === "required"}
options={[
{ label: "True", value: "true" },
{ label: "False", value: "false" },
]}
bind:value={rule.value}
/>
{:else if fieldType === "datetime"}
<DatePicker
enableTime={false}
disabled={rule.constraint === "required"}
bind:value={rule.value}
/>
{:else}
<DrawerBindableInput disabled />
{/if}
{/if}
<DrawerBindableInput
placeholder="Error message"

View File

@ -38,7 +38,7 @@ export const createValidatorFromConstraints = (
type: "string",
constraint: "length",
value: length,
error: val => `Maximum length is ${val}`,
error: `Maximum length is ${length}`,
})
}
@ -49,7 +49,7 @@ export const createValidatorFromConstraints = (
type: "number",
constraint: "minValue",
value: min,
error: val => `Minimum value is ${val}`,
error: `Minimum value is ${min}`,
})
}
if (exists(schemaConstraints.numericality?.lessThanOrEqualTo)) {
@ -58,13 +58,13 @@ export const createValidatorFromConstraints = (
type: "number",
constraint: "maxValue",
value: max,
error: val => `Maximum value is ${val}`,
error: `Maximum value is ${max}`,
})
}
// Inclusion constraint
if (exists(schemaConstraints.inclusion)) {
const options = schemaConstraints.inclusion
const options = schemaConstraints.inclusion || []
rules.push({
type: "string",
constraint: "inclusion",
@ -76,26 +76,22 @@ export const createValidatorFromConstraints = (
// Date constraint
if (exists(schemaConstraints.datetime?.earliest)) {
const limit = schemaConstraints.datetime.earliest
const limitString = flatpickr.formatDate(new Date(limit), "F j Y, H:i")
rules.push({
type: "datetime",
constraint: "minValue",
value: limit,
error: val => {
const date = flatpickr.formatDate(new Date(val), "F j Y, H:i")
return `Earliest date is ${date}`
},
error: `Earliest date is ${limitString}`,
})
}
if (exists(schemaConstraints.datetime?.latest)) {
const limit = schemaConstraints.datetime.latest
const limitString = flatpickr.formatDate(new Date(limit), "F j Y, H:i")
rules.push({
type: "datetime",
constraint: "maxValue",
value: limit,
error: val => {
const date = flatpickr.formatDate(new Date(val), "F j Y, H:i")
return `Latest date is ${date}`
},
error: `Latest date is ${limitString}`,
})
}
}
@ -133,22 +129,12 @@ const evaluateRule = (rule, value) => {
return null
}
// Coerce values into correct types
const parsedValue = parseType(value, rule.type)
const parsedRuleValue = parseType(rule.value, rule.type)
// Coerce input value into correct type
value = parseType(value, rule.type)
// Evaluate the rule
const pass = handler(parsedValue, parsedRuleValue)
if (pass) {
return null
}
// Return an error if the validation failed
let error = rule.error
if (typeof error === "function") {
error = rule.error(parsedRuleValue)
}
return error || "Error"
const pass = handler(value, rule)
return pass ? null : rule.error || "Error"
}
/**
@ -225,53 +211,66 @@ const requiredHandler = value => {
return value != null
}
// Evaluates a min length constraint
const minLengthHandler = (value, rule) => {
const limit = parseType(rule.value, "number")
return value && value.length >= limit
}
// Evaluates a max length constraint
const maxLengthHandler = (value, maxLength) => {
if (value == null) {
return true
}
return value.length <= maxLength
const maxLengthHandler = (value, rule) => {
const limit = parseType(rule.value, "number")
return value == null || value.length <= limit
}
// Evaluates a min value constraint
const minValueHandler = (value, minValue) => {
if (value == null) {
return true
}
return value >= minValue
const minValueHandler = (value, rule) => {
const limit = parseType(rule.value, "number")
return value && value >= limit
}
// Evaluates a max value constraint
const maxValueHandler = (value, maxValue) => {
if (value == null) {
return true
}
return value <= maxValue
const maxValueHandler = (value, rule) => {
const limit = parseType(rule.value, "number")
return value == null || value <= limit
}
// Evaluates an inclusion constraint
const inclusionHandler = (value, options) => {
return options.includes(value)
const inclusionHandler = (value, rule) => {
return value == null || rule.value.includes(value)
}
// Evaluates an equal constraint
const equalHandler = (value, referenceValue) => {
return value === referenceValue
const equalHandler = (value, rule) => {
const ruleValue = parseType(rule.value, rule.type)
return value === ruleValue
}
// Evaluates a not equal constraint
const notEqualHandler = (value, referenceValue) => {
return !equalHandler(value, referenceValue)
const notEqualHandler = (value, rule) => {
return !equalHandler(value, rule)
}
// Evaluates a regex constraint
const regexHandler = (value, regex) => {
const regexHandler = (value, rule) => {
const regex = parseType(rule.value, "string")
return new RegExp(regex).test(value)
}
// Evaluates a not regex constraint
const notRegexHandler = (value, regex) => {
return !regexHandler(value, regex)
const notRegexHandler = (value, rule) => {
return !regexHandler(value, rule)
}
// Evaluates a contains constraint
const containsHandler = (value, rule) => {
const expectedValue = parseType(rule.value, "string")
return value && value.includes(expectedValue)
}
// Evaluates a not contains constraint
const notContainsHandler = (value, rule) => {
return !containsHandler(value, rule)
}
/**
@ -279,6 +278,7 @@ const notRegexHandler = (value, regex) => {
*/
const handlerMap = {
required: requiredHandler,
minLength: minLengthHandler,
maxLength: maxLengthHandler,
minValue: minValueHandler,
maxValue: maxValueHandler,
@ -287,6 +287,8 @@ const handlerMap = {
notEqual: notEqualHandler,
regex: regexHandler,
notRegex: notRegexHandler,
contains: containsHandler,
notContains: notContainsHandler,
}
/**