Skip to content

Commit 8fc90d3

Browse files
feat: 'failWhenUndefined' option in rules
Declaring a rule to fail when the pointed at attribute does not exist requires to lift the scope to the parent level. With the optional 'failWhenUndefined' property, conditions can now take a short cut for these use cases. Co-authored-by: Stefan Dirix <[email protected]>
1 parent 29e42b7 commit 8fc90d3

File tree

3 files changed

+69
-0
lines changed

3 files changed

+69
-0
lines changed

packages/core/src/models/uischema.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ export interface LeafCondition extends Condition, Scoped {
135135

136136
export interface SchemaBasedCondition extends Condition, Scoped {
137137
schema: JsonSchema;
138+
139+
/**
140+
* When the scope resolves to undefined and `failWhenUndefined` is set to `true`, the condition
141+
* will fail. Therefore the reverse effect will be applied.
142+
*
143+
* Background:
144+
* Most JSON Schemas will successfully validate against `undefined` data. Specifying that a
145+
* condition shall fail when data is `undefined` requires to lift the scope to being able to use
146+
* JSON Schema's `required`.
147+
*
148+
* Using `failWhenUndefined` allows to more conveniently express this condition.
149+
*/
150+
failWhenUndefined?: boolean;
138151
}
139152

140153
/**

packages/core/src/util/runtime.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ const evaluateCondition = (
7979
return value === condition.expectedValue;
8080
} else if (isSchemaCondition(condition)) {
8181
const value = resolveData(data, getConditionScope(condition, path));
82+
if (condition.failWhenUndefined && value === undefined) {
83+
return false;
84+
}
8285
return ajv.validate(condition.schema, value) as boolean;
8386
} else {
8487
// unknown condition

packages/core/test/util/runtime.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,59 @@ test('evalEnablement disable invalid case based on schema condition', (t) => {
552552
);
553553
});
554554

555+
test('evalEnablement fail on failWhenUndefined', (t) => {
556+
const condition: SchemaBasedCondition = {
557+
scope: '#/properties/ruleValue',
558+
schema: {
559+
enum: ['bar', 'baz'],
560+
},
561+
};
562+
const failConditionTrue: SchemaBasedCondition = {
563+
...condition,
564+
failWhenUndefined: true,
565+
};
566+
const failConditionFalse: SchemaBasedCondition = {
567+
...condition,
568+
failWhenUndefined: false,
569+
};
570+
571+
const uischema: ControlElement = {
572+
type: 'Control',
573+
scope: '#/properties/value',
574+
rule: {
575+
effect: RuleEffect.DISABLE,
576+
condition: failConditionTrue,
577+
},
578+
};
579+
const failConditionTrueUischema: ControlElement = {
580+
type: 'Control',
581+
scope: '#/properties/value',
582+
rule: {
583+
effect: RuleEffect.DISABLE,
584+
condition: failConditionTrue,
585+
},
586+
};
587+
const failConditionFalseUischema: ControlElement = {
588+
type: 'Control',
589+
scope: '#/properties/value',
590+
rule: {
591+
effect: RuleEffect.DISABLE,
592+
condition: failConditionFalse,
593+
},
594+
};
595+
const data = {
596+
value: 'foo',
597+
};
598+
t.is(
599+
evalEnablement(failConditionTrueUischema, data, undefined, createAjv()),
600+
true
601+
);
602+
t.is(
603+
evalEnablement(failConditionFalseUischema, data, undefined, createAjv()),
604+
evalEnablement(uischema, data, undefined, createAjv())
605+
);
606+
});
607+
555608
test('isInherentlyEnabled disabled globally', (t) => {
556609
t.false(
557610
isInherentlyEnabled(

0 commit comments

Comments
 (0)