Add support for validating relationships
This commit is contained in:
parent
ce29f27c89
commit
b79b75ad78
|
@ -25,6 +25,10 @@
|
||||||
label: "Required",
|
label: "Required",
|
||||||
value: "required",
|
value: "required",
|
||||||
},
|
},
|
||||||
|
MinLength: {
|
||||||
|
label: "Min length",
|
||||||
|
value: "minLength",
|
||||||
|
},
|
||||||
MaxLength: {
|
MaxLength: {
|
||||||
label: "Max length",
|
label: "Max length",
|
||||||
value: "maxLength",
|
value: "maxLength",
|
||||||
|
@ -93,10 +97,10 @@
|
||||||
["attachment"]: [Constraints.Required],
|
["attachment"]: [Constraints.Required],
|
||||||
["link"]: [
|
["link"]: [
|
||||||
Constraints.Required,
|
Constraints.Required,
|
||||||
Constraints.Equal,
|
|
||||||
Constraints.NotEqual,
|
|
||||||
Constraints.Contains,
|
Constraints.Contains,
|
||||||
Constraints.NotContains,
|
Constraints.NotContains,
|
||||||
|
Constraints.MinLength,
|
||||||
|
Constraints.MaxLength,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,7 +259,9 @@
|
||||||
bind:value={rule.valueType}
|
bind:value={rule.valueType}
|
||||||
options={["Binding", "Value"]}
|
options={["Binding", "Value"]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if rule.valueType === "Binding"}
|
{#if rule.valueType === "Binding"}
|
||||||
|
<!-- Bindings always get a bindable input -->
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
placeholder="Constraint value"
|
placeholder="Constraint value"
|
||||||
value={rule.value}
|
value={rule.value}
|
||||||
|
@ -263,7 +269,15 @@
|
||||||
disabled={rule.constraint === "required"}
|
disabled={rule.constraint === "required"}
|
||||||
on:change={e => (rule.value = e.detail)}
|
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
|
||||||
|
bind:value={rule.value}
|
||||||
|
placeholder="Constraint value"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<!-- Otherwise we render a component based on the type -->
|
||||||
|
{#if ["string", "number", "options", "longform"].includes(rule.type) || ["contains"]}
|
||||||
<Input
|
<Input
|
||||||
disabled={rule.constraint === "required"}
|
disabled={rule.constraint === "required"}
|
||||||
bind:value={rule.value}
|
bind:value={rule.value}
|
||||||
|
@ -287,6 +301,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<DrawerBindableInput disabled />
|
<DrawerBindableInput disabled />
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
placeholder="Error message"
|
placeholder="Error message"
|
||||||
value={rule.error}
|
value={rule.error}
|
||||||
|
|
|
@ -38,7 +38,7 @@ export const createValidatorFromConstraints = (
|
||||||
type: "string",
|
type: "string",
|
||||||
constraint: "length",
|
constraint: "length",
|
||||||
value: length,
|
value: length,
|
||||||
error: val => `Maximum length is ${val}`,
|
error: `Maximum length is ${length}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export const createValidatorFromConstraints = (
|
||||||
type: "number",
|
type: "number",
|
||||||
constraint: "minValue",
|
constraint: "minValue",
|
||||||
value: min,
|
value: min,
|
||||||
error: val => `Minimum value is ${val}`,
|
error: `Minimum value is ${min}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (exists(schemaConstraints.numericality?.lessThanOrEqualTo)) {
|
if (exists(schemaConstraints.numericality?.lessThanOrEqualTo)) {
|
||||||
|
@ -58,13 +58,13 @@ export const createValidatorFromConstraints = (
|
||||||
type: "number",
|
type: "number",
|
||||||
constraint: "maxValue",
|
constraint: "maxValue",
|
||||||
value: max,
|
value: max,
|
||||||
error: val => `Maximum value is ${val}`,
|
error: `Maximum value is ${max}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inclusion constraint
|
// Inclusion constraint
|
||||||
if (exists(schemaConstraints.inclusion)) {
|
if (exists(schemaConstraints.inclusion)) {
|
||||||
const options = schemaConstraints.inclusion
|
const options = schemaConstraints.inclusion || []
|
||||||
rules.push({
|
rules.push({
|
||||||
type: "string",
|
type: "string",
|
||||||
constraint: "inclusion",
|
constraint: "inclusion",
|
||||||
|
@ -76,26 +76,22 @@ export const createValidatorFromConstraints = (
|
||||||
// Date constraint
|
// Date constraint
|
||||||
if (exists(schemaConstraints.datetime?.earliest)) {
|
if (exists(schemaConstraints.datetime?.earliest)) {
|
||||||
const limit = schemaConstraints.datetime.earliest
|
const limit = schemaConstraints.datetime.earliest
|
||||||
|
const limitString = flatpickr.formatDate(new Date(limit), "F j Y, H:i")
|
||||||
rules.push({
|
rules.push({
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
constraint: "minValue",
|
constraint: "minValue",
|
||||||
value: limit,
|
value: limit,
|
||||||
error: val => {
|
error: `Earliest date is ${limitString}`,
|
||||||
const date = flatpickr.formatDate(new Date(val), "F j Y, H:i")
|
|
||||||
return `Earliest date is ${date}`
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (exists(schemaConstraints.datetime?.latest)) {
|
if (exists(schemaConstraints.datetime?.latest)) {
|
||||||
const limit = schemaConstraints.datetime.latest
|
const limit = schemaConstraints.datetime.latest
|
||||||
|
const limitString = flatpickr.formatDate(new Date(limit), "F j Y, H:i")
|
||||||
rules.push({
|
rules.push({
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
constraint: "maxValue",
|
constraint: "maxValue",
|
||||||
value: limit,
|
value: limit,
|
||||||
error: val => {
|
error: `Latest date is ${limitString}`,
|
||||||
const date = flatpickr.formatDate(new Date(val), "F j Y, H:i")
|
|
||||||
return `Latest date is ${date}`
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,22 +129,12 @@ const evaluateRule = (rule, value) => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coerce values into correct types
|
// Coerce input value into correct type
|
||||||
const parsedValue = parseType(value, rule.type)
|
value = parseType(value, rule.type)
|
||||||
const parsedRuleValue = parseType(rule.value, rule.type)
|
|
||||||
|
|
||||||
// Evaluate the rule
|
// Evaluate the rule
|
||||||
const pass = handler(parsedValue, parsedRuleValue)
|
const pass = handler(value, rule)
|
||||||
if (pass) {
|
return pass ? null : rule.error || "Error"
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return an error if the validation failed
|
|
||||||
let error = rule.error
|
|
||||||
if (typeof error === "function") {
|
|
||||||
error = rule.error(parsedRuleValue)
|
|
||||||
}
|
|
||||||
return error || "Error"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -225,53 +211,66 @@ const requiredHandler = value => {
|
||||||
return value != null
|
return value != null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluates a max length constraint
|
// Evaluates a min length constraint
|
||||||
const maxLengthHandler = (value, maxLength) => {
|
const minLengthHandler = (value, rule) => {
|
||||||
if (value == null) {
|
const limit = parseType(rule.value, "number")
|
||||||
return true
|
return value && value.length >= limit
|
||||||
}
|
}
|
||||||
return value.length <= maxLength
|
|
||||||
|
// Evaluates a max length constraint
|
||||||
|
const maxLengthHandler = (value, rule) => {
|
||||||
|
const limit = parseType(rule.value, "number")
|
||||||
|
return value == null || value.length <= limit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluates a min value constraint
|
// Evaluates a min value constraint
|
||||||
const minValueHandler = (value, minValue) => {
|
const minValueHandler = (value, rule) => {
|
||||||
if (value == null) {
|
const limit = parseType(rule.value, "number")
|
||||||
return true
|
return value && value >= limit
|
||||||
}
|
|
||||||
return value >= minValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluates a max value constraint
|
// Evaluates a max value constraint
|
||||||
const maxValueHandler = (value, maxValue) => {
|
const maxValueHandler = (value, rule) => {
|
||||||
if (value == null) {
|
const limit = parseType(rule.value, "number")
|
||||||
return true
|
return value == null || value <= limit
|
||||||
}
|
|
||||||
return value <= maxValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluates an inclusion constraint
|
// Evaluates an inclusion constraint
|
||||||
const inclusionHandler = (value, options) => {
|
const inclusionHandler = (value, rule) => {
|
||||||
return options.includes(value)
|
return value == null || rule.value.includes(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluates an equal constraint
|
// Evaluates an equal constraint
|
||||||
const equalHandler = (value, referenceValue) => {
|
const equalHandler = (value, rule) => {
|
||||||
return value === referenceValue
|
const ruleValue = parseType(rule.value, rule.type)
|
||||||
|
return value === ruleValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluates a not equal constraint
|
// Evaluates a not equal constraint
|
||||||
const notEqualHandler = (value, referenceValue) => {
|
const notEqualHandler = (value, rule) => {
|
||||||
return !equalHandler(value, referenceValue)
|
return !equalHandler(value, rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluates a regex constraint
|
// Evaluates a regex constraint
|
||||||
const regexHandler = (value, regex) => {
|
const regexHandler = (value, rule) => {
|
||||||
|
const regex = parseType(rule.value, "string")
|
||||||
return new RegExp(regex).test(value)
|
return new RegExp(regex).test(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluates a not regex constraint
|
// Evaluates a not regex constraint
|
||||||
const notRegexHandler = (value, regex) => {
|
const notRegexHandler = (value, rule) => {
|
||||||
return !regexHandler(value, regex)
|
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 = {
|
const handlerMap = {
|
||||||
required: requiredHandler,
|
required: requiredHandler,
|
||||||
|
minLength: minLengthHandler,
|
||||||
maxLength: maxLengthHandler,
|
maxLength: maxLengthHandler,
|
||||||
minValue: minValueHandler,
|
minValue: minValueHandler,
|
||||||
maxValue: maxValueHandler,
|
maxValue: maxValueHandler,
|
||||||
|
@ -287,6 +287,8 @@ const handlerMap = {
|
||||||
notEqual: notEqualHandler,
|
notEqual: notEqualHandler,
|
||||||
regex: regexHandler,
|
regex: regexHandler,
|
||||||
notRegex: notRegexHandler,
|
notRegex: notRegexHandler,
|
||||||
|
contains: containsHandler,
|
||||||
|
notContains: notContainsHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue