Skip to content

Commit 8eb5565

Browse files
committed
feat: support propertyDependencies from draft/next
For now, causes an uncertainty from removeAdditional / useDefaults, but that could be resolved/optimized in some certain situations later. Refs: json-schema-org/json-schema-spec#1082 Refs: json-schema-org/json-schema-spec#1143
1 parent d514df2 commit 8eb5565

File tree

5 files changed

+62
-17
lines changed

5 files changed

+62
-17
lines changed

doc/samples/draft-next/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Based on JSON Schema Test Suite for `draft-next`.
5858
| [patternProperties](./patternProperties.md) | 5 | - | - | - |
5959
| [prefixItems](./prefixItems.md) | 4 | - | - | - |
6060
| [properties](./properties.md) | 6 | - | - | - |
61-
| [propertyDependencies](./propertyDependencies.md) | 3 | - | 3 | 1 |
61+
| [propertyDependencies](./propertyDependencies.md) | 3 | - | - | - |
6262
| [propertyNames](./propertyNames.md) | 3 | - | - | - |
6363
| [ref](./ref.md) | 29 | - | - | - |
6464
| [refRemote](./refRemote.md) | 13 | - | - | - |

doc/samples/draft-next/propertyDependencies.md

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@
1212

1313
```js
1414
'use strict'
15+
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
1516
const ref0 = function validate(data) {
17+
if (typeof data === "object" && data && !Array.isArray(data)) {
18+
if (data.foo !== undefined && hasOwn(data, "foo")) {
19+
if (data.foo === "bar") return false
20+
}
21+
}
1622
return true
1723
};
1824
return ref0
1925
```
2026

21-
### Warnings
27+
##### Strong mode notices
2228

23-
* `Keyword not supported: "propertyDependencies" at #`
29+
* `[requireValidation] type should be specified at #`
2430

2531

2632
## propertyDependencies doesn't act on non-string property values
@@ -35,15 +41,21 @@ return ref0
3541

3642
```js
3743
'use strict'
44+
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
3845
const ref0 = function validate(data) {
46+
if (typeof data === "object" && data && !Array.isArray(data)) {
47+
if (data.foo !== undefined && hasOwn(data, "foo")) {
48+
if (data.foo === "bar") return false
49+
}
50+
}
3951
return true
4052
};
4153
return ref0
4254
```
4355

44-
### Warnings
56+
##### Strong mode notices
4557

46-
* `Keyword not supported: "propertyDependencies" at #`
58+
* `[requireValidation] type should be specified at #`
4759

4860

4961
## multiple options selects the right one
@@ -67,17 +79,30 @@ return ref0
6779

6880
```js
6981
'use strict'
82+
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
7083
const ref0 = function validate(data) {
84+
if (typeof data === "object" && data && !Array.isArray(data)) {
85+
if (data.foo !== undefined && hasOwn(data, "foo")) {
86+
if (data.foo === "bar") {
87+
if (typeof data === "object" && data && !Array.isArray(data)) {
88+
if (Object.keys(data).length > 2) return false
89+
if (Object.keys(data).length < 2) return false
90+
}
91+
}
92+
if (data.foo === "baz") {
93+
if (typeof data === "object" && data && !Array.isArray(data)) {
94+
if (Object.keys(data).length > 1) return false
95+
}
96+
}
97+
if (data.foo === "quux") return false
98+
}
99+
}
71100
return true
72101
};
73102
return ref0
74103
```
75104

76-
### Warnings
77-
78-
* `Keyword not supported: "propertyDependencies" at #`
79-
80-
### Misclassified!
105+
##### Strong mode notices
81106

82-
**This schema caused 4 misclassifications!**
107+
* `[requireValidation] schema = true is not allowed at #/propertyDependencies/foo/qux`
83108

src/compile.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,7 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
764764
errorIf(safeand(present(item), condition), errorArgs)
765765
}
766766
} else if (isSchemaish(deps) && dependencies !== 'dependentRequired') {
767-
uncertain(dependencies)
767+
uncertain(dependencies) // TODO: we don't always need this, remove when no uncertainity?
768768
fun.if(item.checked ? true : present(item), () => {
769769
const delta = rule(current, deps, subPath(dependencies, key), dyn)
770770
evaluateDelta(orDelta({}, delta))
@@ -776,6 +776,27 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
776776
})
777777
}
778778

779+
handle('propertyDependencies', ['object'], (propertyDependencies) => {
780+
for (const [key, variants] of Object.entries(propertyDependencies)) {
781+
enforce(isPlainObject(variants), 'propertyDependencies must be an object')
782+
uncertain('propertyDependencies') // TODO: we don't always need this, remove when no uncertainity?
783+
const item = currPropImm(key, checked(key))
784+
// NOTE: would it be useful to also check if it's a string?
785+
fun.if(item.checked ? true : present(item), () => {
786+
for (const [val, deps] of Object.entries(variants)) {
787+
enforce(isSchemaish(deps), 'propertyDependencies must contain schemas')
788+
fun.if(compare(buildName(item), val), () => {
789+
// TODO: we already know that we have an object here, optimize?
790+
const delta = rule(current, deps, subPath('propertyDependencies', key, val), dyn)
791+
evaluateDelta(orDelta({}, delta))
792+
evaluateDeltaDynamic(delta)
793+
})
794+
}
795+
})
796+
}
797+
return null
798+
})
799+
779800
handle('properties', ['object'], (properties) => {
780801
for (const p of Object.keys(properties)) {
781802
if (constProp === p) continue // checked in discriminator, avoid double-check
@@ -1181,8 +1202,9 @@ const compileSchema = (schema, root, opts, scope, basePathRoot = '') => {
11811202
const logicalOp = ['not', 'if', 'then', 'else'].includes(schemaPath[schemaPath.length - 1])
11821203
const branchOp = ['oneOf', 'anyOf', 'allOf'].includes(schemaPath[schemaPath.length - 2])
11831204
const depOp = ['dependencies', 'dependentSchemas'].includes(schemaPath[schemaPath.length - 2])
1205+
const propDepOp = ['propertyDependencies'].includes(schemaPath[schemaPath.length - 3])
11841206
// Coherence check, unreachable, double-check that we came from expected path
1185-
enforce(logicalOp || branchOp || depOp, 'Unexpected')
1207+
enforce(logicalOp || branchOp || depOp || propDepOp, 'Unexpected logical path')
11861208
} else if (!schemaPath.includes('not')) {
11871209
// 'not' does not mark anything as evaluated (unlike even if/then/else), so it's safe to exclude from these
11881210
// checks, as we are sure that everything will be checked without it. It can be viewed as a pure add-on.

src/known-keywords.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ const knownKeywords = [
1313
...['maxLength', 'minLength', 'format', 'pattern'], // strings
1414
...['contentEncoding', 'contentMediaType', 'contentSchema'], // strings content
1515
...['properties', 'maxProperties', 'minProperties', 'additionalProperties', 'patternProperties'], // objects
16-
...['propertyNames', 'dependencies', 'dependentRequired', 'dependentSchemas'], // objects
16+
...['propertyNames'], // objects
17+
...['dependencies', 'dependentRequired', 'dependentSchemas', 'propertyDependencies'], // objects (dependencies)
1718
...['unevaluatedProperties', 'unevaluatedItems'], // see-through
1819
// Unused meta keywords not affecting validation (annotations and comments)
1920
// https://json-schema.org/understanding-json-schema/reference/generic.html

test/json-schema.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,6 @@ const unsupported = new Set([
119119

120120
// draft-next changes to bookending requirement in dynamicRef
121121
'draft-next/dynamicRef.json',
122-
123-
// draft-next only, new features
124-
'draft-next/propertyDependencies.json',
125122
])
126123
const unsupportedMask = []
127124

0 commit comments

Comments
 (0)