Skip to content

Commit

Permalink
feat: implement instance validation in validator
Browse files Browse the repository at this point in the history
Related to #1147
  • Loading branch information
Skaiir committed Apr 17, 2024
1 parent 9178e18 commit 8d575f6
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 34 deletions.
51 changes: 49 additions & 2 deletions packages/form-js-viewer/src/core/Validator.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { isNil, get, set } from 'min-dash';
import { countDecimals } from '../render/components/util/numberFieldUtil';
import { runExpressionEvaluation } from '../util/expressions';
import Big from 'big.js';

const EMAIL_PATTERN =
Expand All @@ -18,6 +19,8 @@ export class Validator {
}

/**
* Validate against a field definition, does not support proper expression evaluation.
*
* @deprecated use validateFieldInstance instead
*/
validateField(field, value) {
Expand All @@ -33,7 +36,7 @@ export class Validator {
return errors;
}

const evaluatedValidation = evaluateFEELValues(
const evaluatedValidation = oldEvaluateFEELValues(
validate,
this._expressionLanguage,
this._conditionChecker,
Expand All @@ -44,6 +47,37 @@ export class Validator {

return errors;
}

/**
* Validate a field instance.
*
* @param {Object} fieldInstance
* @param {string} value
*
* @returns {Array<string>}
*/
validateFieldInstance(fieldInstance, value) {
const { id, expressionContextInfo } = fieldInstance;

const field = this._formFieldRegistry.get(id);
const { type, validate } = field;

let errors = [];

if (type === 'number') {
errors = runNumberValidation(field, value, errors);
}

if (!validate) {
return errors;
}

const evaluatedValidation = evaluateFEELValues(validate, this._expressionLanguage, expressionContextInfo);

errors = runPresetValidation(field, evaluatedValidation, value, errors);

return errors;
}
}

Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form', 'formFieldRegistry'];
Expand Down Expand Up @@ -130,7 +164,20 @@ function runPresetValidation(field, validation, value, errors) {
return errors;
}

function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
function evaluateFEELValues(validate, expressionLanguage, expressionContextInfo) {
const evaluatedValidate = { ...validate };

VALIDATE_FEEL_PROPERTIES.forEach((property) => {
const path = property.split('.');
const value = get(evaluatedValidate, path);
const evaluatedValue = runExpressionEvaluation(expressionLanguage, value, expressionContextInfo);
set(evaluatedValidate, path, evaluatedValue === null ? undefined : evaluatedValue);
});

return evaluatedValidate;
}

function oldEvaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
const evaluatedValidate = { ...validate };

VALIDATE_FEEL_PROPERTIES.forEach((property) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/form-js-viewer/src/render/hooks/useCondition.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useService } from './useService.js';
import { useContext, useMemo } from 'preact/hooks';
import { LocalExpressionContext } from '../context/LocalExpressionContext.js';
import { buildExpressionContext } from '../../util/simple';
import { buildExpressionContext } from '../../util/expressions.js';

/**
* Evaluate if condition is met reactively based on the conditionChecker and form data.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useService } from './useService';
import { LocalExpressionContext } from '../context/LocalExpressionContext';
import { useContext, useMemo } from 'preact/hooks';
import { buildExpressionContext } from '../../util/simple';
import { runExpressionEvaluation } from '../../util/expressions';

/**
* Evaluate a string reactively based on the expressionLanguage and form data.
Expand All @@ -14,11 +14,8 @@ import { buildExpressionContext } from '../../util/simple';
export function useExpressionEvaluation(value) {
const expressionLanguage = useService('expressionLanguage');
const expressionContextInfo = useContext(LocalExpressionContext);

return useMemo(() => {
if (expressionLanguage && expressionLanguage.isExpression(value)) {
return expressionLanguage.evaluate(value, buildExpressionContext(expressionContextInfo));
}
return value;
}, [expressionLanguage, expressionContextInfo, value]);
return useMemo(
() => runExpressionEvaluation(expressionLanguage, value, expressionContextInfo),
[expressionLanguage, expressionContextInfo, value],
);
}
2 changes: 1 addition & 1 deletion packages/form-js-viewer/src/render/hooks/useReadonly.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildExpressionContext } from '../../util/simple';
import { buildExpressionContext } from '../../util/expressions.js';
import { LocalExpressionContext } from '../context/LocalExpressionContext.js';
import { useService } from './useService.js';
import { useContext } from 'preact/hooks';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useService } from './useService';
import { useContext, useMemo } from 'preact/hooks';
import { LocalExpressionContext } from '../context/LocalExpressionContext';
import { buildExpressionContext } from '../../util/simple';
import { buildExpressionContext } from '../../util/expressions';

/**
* Template a string reactively based on form data. If the string is not a template, it is returned as is.
Expand Down
38 changes: 38 additions & 0 deletions packages/form-js-viewer/src/util/expressions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { wrapObjectKeysWithUnderscores } from './simple';


/**
* Transform a LocalExpressionContext object into a usable FEEL context.
*
* @param {Object} context - The LocalExpressionContext object.
* @returns {Object} The usable FEEL context.
*/

export function buildExpressionContext(context) {
const {
data,
...specialContextKeys
} = context;

return {
...specialContextKeys,
...data,
...wrapObjectKeysWithUnderscores(specialContextKeys)
};
}

/**
* Evaluate a string based on the expressionLanguage and context information.
* If the string is not an expression, it is returned as is.
*
* @param {any} expressionLanguage - The expression language to use.
* @param {string} value - The string to evaluate.
* @param {Object} expressionContextInfo - The context information to use.
* @returns {any} - Evaluated value or the original value if not an expression.
*/
export function runExpressionEvaluation(expressionLanguage, value, expressionContextInfo) {
if (expressionLanguage && expressionLanguage.isExpression(value)) {
return expressionLanguage.evaluate(value, buildExpressionContext(expressionContextInfo));
}
return value;
}
1 change: 1 addition & 0 deletions packages/form-js-viewer/src/util/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './form';
export * from './getSchemaVariables';
export * from './simple';
export * from './structure';
export * from './expressions';
23 changes: 2 additions & 21 deletions packages/form-js-viewer/src/util/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,36 +42,17 @@ export function clone(data, replacer) {
return JSON.parse(JSON.stringify(data, replacer));
}

/**
* Transform a LocalExpressionContext object into a usable FEEL context.
*
* @param {Object} context - The LocalExpressionContext object.
* @returns {Object} The usable FEEL context.
*/

export function buildExpressionContext(context) {
const { data, ...specialContextKeys } = context;

return {
...specialContextKeys,
...data,
..._wrapObjectKeysWithUnderscores(specialContextKeys),
};
}

export function runRecursively(formField, fn) {
const components = formField.components || [];

components.forEach((component, index) => {
components.forEach((component, _) => {
runRecursively(component, fn);
});

fn(formField);
}

// helpers //////////////////////

function _wrapObjectKeysWithUnderscores(obj) {
export function wrapObjectKeysWithUnderscores(obj) {
const newObj = {};
for (const [key, value] of Object.entries(obj)) {
newObj[`_${key}_`] = value;
Expand Down

0 comments on commit 8d575f6

Please sign in to comment.