feat: Filter parameter: Improve loose type validation for booleans (#10702)

This commit is contained in:
Elias Meire
2024-09-09 09:54:36 +02:00
committed by GitHub
parent b18313f219
commit e9b8d99084
14 changed files with 261 additions and 22 deletions

View File

@@ -1292,13 +1292,14 @@ type NonEmptyArray<T> = [T, ...T[]];
export type FilterTypeCombinator = 'and' | 'or';
export type FilterTypeOptions = Partial<{
caseSensitive: boolean | string; // default = true
leftValue: string; // when set, user can't edit left side of condition
allowedCombinators: NonEmptyArray<FilterTypeCombinator>; // default = ['and', 'or']
maxConditions: number; // default = 10
typeValidation: 'strict' | 'loose' | {}; // default = strict, `| {}` is a TypeScript trick to allow custom strings, but still give autocomplete
}>;
export type FilterTypeOptions = {
version: 1 | 2 | {}; // required so nodes are pinned on a version
caseSensitive?: boolean | string; // default = true
leftValue?: string; // when set, user can't edit left side of condition
allowedCombinators?: NonEmptyArray<FilterTypeCombinator>; // default = ['and', 'or']
maxConditions?: number; // default = 10
typeValidation?: 'strict' | 'loose' | {}; // default = strict, `| {}` is a TypeScript trick to allow custom strings (expressions), but still give autocomplete
};
export type AssignmentTypeOptions = Partial<{
hideType?: boolean; // visible by default
@@ -2554,6 +2555,7 @@ export type FilterOptionsValue = {
caseSensitive: boolean;
leftValue: string;
typeValidation: 'strict' | 'loose';
version: 1 | 2;
};
export type FilterValue = {

View File

@@ -32,12 +32,18 @@ function parseSingleFilterValue(
value: unknown,
type: FilterOperatorType,
strict = false,
version: FilterOptionsValue['version'] = 1,
): ValidationResult {
if (type === 'any' || value === null || value === undefined) {
return { valid: true, newValue: value } as ValidationResult;
}
if (type === 'boolean' && !strict) {
if (version >= 2) {
const result = validateFieldType('filter', value, type);
if (result.valid) return result;
}
return { valid: true, newValue: Boolean(value) };
}
@@ -53,6 +59,7 @@ const withIndefiniteArticle = (noun: string): string => {
return `${article} ${noun}`;
};
// eslint-disable-next-line complexity
function parseFilterConditionValues(
condition: FilterConditionValue,
options: FilterOptionsValue,
@@ -62,10 +69,16 @@ function parseFilterConditionValues(
const itemIndex = metadata.itemIndex ?? 0;
const errorFormat = metadata.errorFormat ?? 'full';
const strict = options.typeValidation === 'strict';
const version = options.version ?? 1;
const { operator } = condition;
const rightType = operator.rightType ?? operator.type;
const parsedLeftValue = parseSingleFilterValue(condition.leftValue, operator.type, strict);
const parsedRightValue = parseSingleFilterValue(condition.rightValue, rightType, strict);
const parsedLeftValue = parseSingleFilterValue(
condition.leftValue,
operator.type,
strict,
version,
);
const parsedRightValue = parseSingleFilterValue(condition.rightValue, rightType, strict, version);
const leftValid =
parsedLeftValue.valid ||
(metadata.unresolvedExpressions &&
@@ -96,7 +109,7 @@ function parseFilterConditionValues(
const getTypeDescription = (isStrict: boolean) => {
if (isStrict)
return 'Try changing the type of the comparison, or enabling less strict type validation.';
return "Try changing the type of comparison. Alternatively you can enable 'Convert Value Types'.";
return 'Try changing the type of the comparison.';
};
@@ -122,7 +135,7 @@ function parseFilterConditionValues(
return `
<p>Try either:</p>
<ol>
<li>Enabling less strict type validation</li>
<li>Enabling 'Convert Value Types'</li>
<li>Converting the ${valuePosition} field to ${expectedType}${suggestFunction}</li>
</ol>
`;

View File

@@ -19,6 +19,7 @@ const filterFactory = (data: DeepPartial<FilterValue> = {}): FilterValue =>
combinator: 'and',
conditions: [],
options: {
version: 1,
leftValue: '',
caseSensitive: false,
typeValidation: 'strict',
@@ -234,6 +235,48 @@ describe('FilterParameter', () => {
});
});
describe('options.version', () => {
describe('version 1', () => {
it('should parse "false" as true', () => {
expect(
executeFilter(
filterFactory({
conditions: [
{
id: '1',
leftValue: 'false',
rightValue: false,
operator: { operation: 'equals', type: 'boolean' },
},
],
options: { typeValidation: 'loose', version: 1 },
}),
),
).toEqual(false);
});
});
describe('version 2', () => {
it('should parse "false" as false', () => {
expect(
executeFilter(
filterFactory({
conditions: [
{
id: '1',
leftValue: 'false',
rightValue: false,
operator: { operation: 'equals', type: 'boolean' },
},
],
options: { typeValidation: 'loose', version: 2 },
}),
),
).toEqual(true);
});
});
});
describe('operators', () => {
describe('exists', () => {
it.each([