Skip to content

Commit 938a6be

Browse files
committed
wip: custom validation implementation
1 parent 022ee05 commit 938a6be

File tree

2 files changed

+39
-30
lines changed

2 files changed

+39
-30
lines changed

packages/form-js-viewer/src/core/Validator.js

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { isNil, get, set } from 'min-dash';
22
import { countDecimals } from '../render/components/util/numberFieldUtil';
33
import Big from 'big.js';
4+
import { clone } from '../util';
5+
import { wrapObjectKeysWithUnderscores } from '../util/simple';
46

57
const EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
68
const PHONE_PATTERN = /(\+|00)(297|93|244|1264|358|355|376|971|54|374|1684|1268|61|43|994|257|32|229|226|880|359|973|1242|387|590|375|501|1441|591|55|1246|673|975|267|236|1|61|41|56|86|225|237|243|242|682|57|269|238|506|53|5999|61|1345|357|420|49|253|1767|45|1809|1829|1849|213|593|20|291|212|34|372|251|358|679|500|33|298|691|241|44|995|44|233|350|224|590|220|245|240|30|1473|299|502|594|1671|592|852|504|385|509|36|62|44|91|246|353|98|964|354|972|39|1876|44|962|81|76|77|254|996|855|686|1869|82|383|965|856|961|231|218|1758|423|94|266|370|352|371|853|590|212|377|373|261|960|52|692|389|223|356|95|382|976|1670|258|222|1664|596|230|265|60|262|264|687|227|672|234|505|683|31|47|977|674|64|968|92|507|64|51|63|680|675|48|1787|1939|850|351|595|970|689|974|262|40|7|250|966|249|221|65|500|4779|677|232|503|378|252|508|381|211|239|597|421|386|46|268|1721|248|963|1649|235|228|66|992|690|993|670|676|1868|216|90|688|886|255|256|380|598|1|998|3906698|379|1784|58|1284|1340|84|678|681|685|967|27|260|263)(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\d{4,20}$/;
79

8-
const VALIDATE_FEEL_PROPERTIES = [
10+
const PRECOMPUTE_FEEL_PROPERTIES = [
11+
'custom',
912
'min',
1013
'max',
1114
'minLength',
@@ -75,7 +78,7 @@ export class Validator {
7578
return errors;
7679
}
7780

78-
const evaluatedValidation = evaluateFEELValues(
81+
const evaluatedValidation = precomputeFEELValues(
7982
validate,
8083
this._expressionLanguage,
8184
this._conditionChecker,
@@ -144,6 +147,20 @@ export class Validator {
144147
];
145148
}
146149

150+
if ('custom' in evaluatedValidation && value && evaluatedValidation.custom.length) {
151+
const { custom } = evaluatedValidation;
152+
custom.forEach(customValidation => {
153+
const { condition, message } = customValidation;
154+
155+
if (condition && !evaluateExpression(condition, this._expressionLanguage, this._conditionChecker, this._form, { value })) {
156+
errors = [
157+
...errors,
158+
message
159+
];
160+
}
161+
});
162+
}
163+
147164
return errors;
148165
}
149166
}
@@ -153,35 +170,29 @@ Validator.$inject = [ 'expressionLanguage', 'conditionChecker', 'form' ];
153170

154171
// helpers //////////
155172

156-
/**
157-
* Helper function to evaluate optional FEEL validation values.
158-
*/
159-
function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
160-
161-
const evaluatedValidate = { ...validate };
162-
163-
VALIDATE_FEEL_PROPERTIES.forEach(property => {
164-
const path = property.split('.');
173+
function precomputeFEELValues(_validate, expressionLanguage, conditionChecker, form) {
165174

166-
const value = get(evaluatedValidate, path);
175+
const validate = clone(_validate);
167176

168-
// mirroring FEEL evaluation of our hooks
169-
if (!expressionLanguage || !expressionLanguage.isExpression(value)) {
170-
return value;
177+
PRECOMPUTE_FEEL_PROPERTIES.forEach(propertyPath => {
178+
const value = get(validate, propertyPath.split('.'));
179+
const evaluatedValue = evaluateExpression(value, expressionLanguage, conditionChecker, form);
180+
if (evaluatedValue) {
181+
set(validate, propertyPath.split('.'), evaluatedValue);
171182
}
183+
});
172184

173-
const { initialData, data } = form._getState();
174-
175-
const newData = conditionChecker ? conditionChecker.applyConditions(data, data) : data;
176-
const filteredData = { ...initialData, ...newData };
185+
return validate;
186+
}
177187

178-
const evaluatedValue = expressionLanguage.evaluate(value, filteredData);
188+
function evaluateExpression(expression, expressionLanguage, conditionChecker, form, additionalContext = {}) {
189+
if (!expressionLanguage || !expressionLanguage.isExpression(expression)) {
190+
return expression;
191+
}
179192

180-
// replace validate property with evaluated value
181-
if (evaluatedValue) {
182-
set(evaluatedValidate, path, evaluatedValue);
183-
}
184-
});
193+
const { initialData, data } = form._getState();
194+
const filteredData = conditionChecker ? conditionChecker.applyConditions(data, data) : data;
195+
const expressionContext = { ...additionalContext, ...initialData, ...filteredData, ...wrapObjectKeysWithUnderscores(additionalContext) };
185196

186-
return evaluatedValidate;
197+
return expressionLanguage.evaluate(expression, expressionContext);
187198
}

packages/form-js-viewer/src/util/simple.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function buildExpressionContext(context) {
6363
return {
6464
...specialContextKeys,
6565
...data,
66-
..._wrapObjectKeysWithUnderscores(specialContextKeys)
66+
...wrapObjectKeysWithUnderscores(specialContextKeys)
6767
};
6868
}
6969

@@ -77,9 +77,7 @@ export function runRecursively(formField, fn) {
7777
fn(formField);
7878
}
7979

80-
// helpers //////////////////////
81-
82-
function _wrapObjectKeysWithUnderscores(obj) {
80+
export function wrapObjectKeysWithUnderscores(obj) {
8381
const newObj = {};
8482
for (const [ key, value ] of Object.entries(obj)) {
8583
newObj[`_${key}_`] = value;

0 commit comments

Comments
 (0)