Skip to content

Commit 10df483

Browse files
Merge pull request #52 from formio/FIO-7964-add-resource-validation
FIO-7964: add resource-based select component validation
2 parents d311d92 + 5c599a9 commit 10df483

File tree

5 files changed

+104
-13
lines changed

5 files changed

+104
-13
lines changed

src/process/validation/rules/__tests__/validateRemoteSelectValue.test.ts src/process/validation/rules/__tests__/validateUrlSelectValue.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import { DataObject, SelectComponent } from 'types';
44
import { FieldError } from 'error';
55
import { simpleSelectOptions, simpleTextField } from './fixtures/components';
66
import { generateProcessorContext } from './fixtures/util';
7-
import { validateRemoteSelectValue, generateUrl } from '../validateRemoteSelectValue';
7+
import { validateUrlSelectValue, generateUrl } from '../validateUrlSelectValue';
88

99
it('Validating a component without the remote value validation parameter will return null', async () => {
1010
const component = simpleTextField;
1111
const data = {
1212
component: 'Hello, world!',
1313
};
1414
const context = generateProcessorContext(component, data);
15-
const result = await validateRemoteSelectValue(context);
15+
const result = await validateUrlSelectValue(context);
1616
expect(result).to.equal(null);
1717
});
1818

@@ -32,7 +32,7 @@ it('Validating a select component without the remote value validation parameter
3232
},
3333
};
3434
const context = generateProcessorContext(component, data);
35-
const result = await validateRemoteSelectValue(context);
35+
const result = await validateUrlSelectValue(context);
3636
expect(result).to.equal(null);
3737
});
3838

@@ -84,7 +84,7 @@ it('Validating a select component with the remote validation parameter will retu
8484
},
8585
};
8686
const context = generateProcessorContext(component, data);
87-
const result = await validateRemoteSelectValue(context);
87+
const result = await validateUrlSelectValue(context);
8888
expect(result).to.be.instanceOf(FieldError);
8989
expect(result?.errorKeyOrMessage).to.equal('select');
9090
});
@@ -108,7 +108,7 @@ it('Validating a select component with the remote validation parameter will retu
108108
},
109109
};
110110
const context = generateProcessorContext(component, data);
111-
const result = await validateRemoteSelectValue(context);
111+
const result = await validateUrlSelectValue(context);
112112
expect(result).to.be.instanceOf(FieldError);
113113
expect(result?.errorKeyOrMessage).to.equal('select');
114114
});
@@ -139,6 +139,6 @@ it('Validating a select component with the remote validation parameter will retu
139139
json: () => Promise.resolve([{ id: 'b', value: 2 }])
140140
});
141141
};
142-
const result = await validateRemoteSelectValue(context);
142+
const result = await validateUrlSelectValue(context);
143143
expect(result).to.equal(null);
144144
});
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ValidationRuleInfo } from "types";
2-
import { validateRemoteSelectValueInfo } from "./validateRemoteSelectValue";
2+
import { validateUrlSelectValueInfo } from "./validateUrlSelectValue";
33

44
// These are the validations that are asynchronouse (e.g. require fetch
55
export const asynchronousRules: ValidationRuleInfo[] = [
6-
validateRemoteSelectValueInfo,
6+
validateUrlSelectValueInfo,
77
];
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { ValidationRuleInfo } from "types";
22
import { validateUniqueInfo } from "./validateUnique";
33
import { validateCaptchaInfo } from "./validateCaptcha";
4+
import { validateResourceSelectValueInfo } from "./validateResourceSelectValue";
45

56
// These are the validations that require a database connection.
67
export const databaseRules: ValidationRuleInfo[] = [
78
validateUniqueInfo,
8-
validateCaptchaInfo
9+
validateCaptchaInfo,
10+
validateResourceSelectValueInfo
911
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { FieldError, ProcessorError } from 'error';
2+
import { SelectComponent, RuleFn, ValidationContext } from 'types';
3+
import { Evaluator } from 'utils';
4+
import { isEmptyObject, toBoolean } from '../util';
5+
import { getErrorMessage } from 'utils/error';
6+
import { ProcessorInfo } from 'types/process/ProcessorInfo';
7+
8+
const isValidatableSelectComponent = (component: any): component is SelectComponent => {
9+
return (
10+
component &&
11+
component.type === 'select' &&
12+
toBoolean(component.dataSrc === 'resource') &&
13+
toBoolean(component.validate?.select)
14+
);
15+
};
16+
17+
export const generateUrl = (baseUrl: URL, component: SelectComponent, value: any) => {
18+
const url = baseUrl;
19+
const query = url.searchParams;
20+
if (component.searchField) {
21+
let searchValue = value;
22+
if (component.valueProperty) {
23+
searchValue = value[component.valueProperty];
24+
} else {
25+
searchValue = value;
26+
}
27+
query.set(component.searchField, typeof searchValue === 'string' ? searchValue : JSON.stringify(searchValue))
28+
}
29+
if (component.selectFields) {
30+
query.set('select', component.selectFields);
31+
}
32+
if (component.sort) {
33+
query.set('sort', component.sort);
34+
}
35+
if (component.filter) {
36+
const filterQueryStrings = new URLSearchParams(component.filter);
37+
filterQueryStrings.forEach((value, key) => query.set(key, value));
38+
}
39+
return url;
40+
};
41+
42+
export const shouldValidate = (context: ValidationContext) => {
43+
const { component, value, data, config } = context;
44+
// Only run this validation if server-side
45+
if (!config?.server) {
46+
return false;
47+
}
48+
if (!isValidatableSelectComponent(component)) {
49+
return false;
50+
}
51+
if (
52+
!value ||
53+
isEmptyObject(value) ||
54+
(Array.isArray(value) && (value as Array<Record<string, any>>).length === 0)
55+
) {
56+
return false;
57+
}
58+
59+
// If given an invalid configuration, do not validate the remote value
60+
if (component.dataSrc !== 'resource' || !component.data?.resource) {
61+
return false;
62+
}
63+
64+
return true;
65+
};
66+
67+
export const validateResourceSelectValue: RuleFn = async (context: ValidationContext) => {
68+
const { value, config, component } = context;
69+
if (!shouldValidate(context)) {
70+
return null;
71+
}
72+
73+
if (!config || !config.database) {
74+
throw new ProcessorError("Can't validate for resource value without a database config object", context, 'validate:validateResourceSelectValue');
75+
}
76+
try {
77+
const resourceSelectValueResult: boolean = await config.database?.validateResourceSelectValue(context, value);
78+
return (resourceSelectValueResult === true) ? null : new FieldError('select', context);
79+
}
80+
catch (err: any) {
81+
throw new ProcessorError(err.message || err, context, 'validate:validateResourceSelectValue');
82+
}
83+
};
84+
85+
export const validateResourceSelectValueInfo: ProcessorInfo<ValidationContext, FieldError | null> = {
86+
name: 'validateResourceSelectValue',
87+
process: validateResourceSelectValue,
88+
shouldProcess: shouldValidate,
89+
}

src/process/validation/rules/validateRemoteSelectValue.ts src/process/validation/rules/validateUrlSelectValue.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export const shouldValidate = (context: ValidationContext) => {
6464
return true;
6565
};
6666

67-
export const validateRemoteSelectValue: RuleFn = async (context: ValidationContext) => {
67+
export const validateUrlSelectValue: RuleFn = async (context: ValidationContext) => {
6868
const { component, value, data, config } = context;
6969
let _fetch: FetchFn | null = null;
7070
try {
@@ -129,8 +129,8 @@ export const validateRemoteSelectValue: RuleFn = async (context: ValidationConte
129129
}
130130
};
131131

132-
export const validateRemoteSelectValueInfo: ProcessorInfo<ValidationContext, FieldError | null> = {
133-
name: 'validateRemoteSelectValue',
134-
process: validateRemoteSelectValue,
132+
export const validateUrlSelectValueInfo: ProcessorInfo<ValidationContext, FieldError | null> = {
133+
name: 'validateUrlSelectValue',
134+
process: validateUrlSelectValue,
135135
shouldProcess: shouldValidate,
136136
}

0 commit comments

Comments
 (0)