1
1
import { isSchemaNode , SchemaNode } from "../types" ;
2
2
import { Keyword , JsonSchemaReducerParams , JsonSchemaValidatorParams , ValidationResult } from "../Keyword" ;
3
- import { getValue } from "../utils/getValue" ;
4
3
import { isObject } from "../utils/isObject" ;
5
- import { validateNode } from "../validateNode" ;
6
4
import { mergeNode } from "../mergeNode" ;
5
+ import { hasProperty } from "../utils/hasProperty" ;
6
+ import { validateDependentRequired } from "./dependentRequired" ;
7
+ import { validateDependentSchemas } from "./dependentSchemas" ;
7
8
8
9
export const dependenciesKeyword : Keyword = {
9
10
id : "dependencies" ,
10
11
keyword : "dependencies" ,
11
12
parse : parseDependencies ,
12
- addReduce : ( node ) => node . dependencies != null ,
13
+ order : - 9 ,
14
+ addReduce : ( node ) => node . schema . dependencies != null ,
13
15
reduce : reduceDependencies ,
14
- addValidate : ( node ) => node . dependencies != null ,
16
+ addValidate : ( node ) => node . schema . dependencies != null ,
15
17
validate : validateDependencies
16
18
} ;
17
19
@@ -20,44 +22,72 @@ export function parseDependencies(node: SchemaNode) {
20
22
if ( ! isObject ( dependencies ) ) {
21
23
return ;
22
24
}
23
-
24
25
const schemas = Object . keys ( dependencies ) ;
25
- if ( schemas . length === 0 ) {
26
- return ;
27
- }
28
- node . dependencies = { } ;
29
26
schemas . forEach ( ( property ) => {
30
- const schema = dependencies [ property ] ;
27
+ const schema = dependencies [ property ] as string [ ] ;
31
28
if ( isObject ( schema ) || typeof schema === "boolean" ) {
32
- node . dependencies [ property ] = node . compileSchema (
33
- // @ts -expect-error boolean schema
29
+ node . dependentSchemas = node . dependentSchemas ?? { } ;
30
+ node . dependentSchemas [ property ] = node . compileSchema (
34
31
schema ,
35
32
`${ node . spointer } /dependencies/${ property } ` ,
36
33
`${ node . schemaId } /dependencies/${ property } `
37
34
) ;
38
- } else if ( Array . isArray ( schema ) ) {
39
- node . dependencies [ property ] = schema ;
35
+ } else {
36
+ node . dependentRequired = node . dependentRequired ?? { } ;
37
+ node . dependentRequired [ property ] = schema ;
40
38
}
41
39
} ) ;
42
40
}
43
41
44
42
export function reduceDependencies ( { node, data, key, path } : JsonSchemaReducerParams ) {
45
- if ( ! isObject ( data ) || node . dependencies == null ) {
43
+ if ( ! isObject ( data ) ) {
46
44
// @todo remove dependentSchemas
47
45
return node ;
48
46
}
49
47
48
+ if ( node . dependentRequired == null && node . dependentSchemas == null ) {
49
+ return node ;
50
+ }
51
+
50
52
let workingNode = node . compileSchema ( node . schema , node . spointer , node . schemaId ) ;
51
- const { dependencies } = node ;
52
53
let required = workingNode . schema . required ?? [ ] ;
53
- Object . keys ( dependencies ) . forEach ( ( propertyName ) => {
54
- if ( isSchemaNode ( dependencies [ propertyName ] ) ) {
55
- const reducedDependency = dependencies [ propertyName ] . reduceNode ( data , { key, path } ) . node ;
54
+
55
+ if ( node . dependentRequired ) {
56
+ Object . keys ( node . dependentRequired ) . forEach ( ( propertyName ) => {
57
+ if ( ! hasProperty ( data , propertyName ) && ! required . includes ( propertyName ) ) {
58
+ console . log ( propertyName , "abort" , required ) ;
59
+ return ;
60
+ }
61
+ if ( node . dependentRequired [ propertyName ] == null ) {
62
+ return ;
63
+ }
64
+ required . push ( ...node . dependentRequired [ propertyName ] ) ;
65
+ } ) ;
66
+ }
67
+
68
+ if ( node . dependentSchemas ) {
69
+ Object . keys ( node . dependentSchemas ) . forEach ( ( propertyName ) => {
70
+ if ( ! hasProperty ( data , propertyName ) && ! required . includes ( propertyName ) ) {
71
+ return true ;
72
+ }
73
+ const dependency = node . dependentSchemas [ propertyName ] ;
74
+ if ( ! isSchemaNode ( dependency ) ) {
75
+ return true ;
76
+ }
77
+
78
+ // @note pass on updated required-list to resolve nested dependencies. This is currently supported,
79
+ // but probably not how json-schema spec defines this behaviour (resolve only within sub-schema)
80
+ const reducedDependency = { ...dependency , schema : { ...dependency . schema , required } } . reduceNode ( data , {
81
+ key,
82
+ path
83
+ } ) . node ;
84
+
56
85
workingNode = mergeNode ( workingNode , reducedDependency ) ;
57
- } else if ( Array . isArray ( dependencies [ propertyName ] ) && data [ propertyName ] !== undefined ) {
58
- required . push ( ...dependencies [ propertyName ] ) ;
59
- }
60
- } ) ;
86
+ if ( workingNode . schema . required ) {
87
+ required . push ( ...workingNode . schema . required ) ;
88
+ }
89
+ } ) ;
90
+ }
61
91
62
92
if ( workingNode === node ) {
63
93
return node ;
@@ -72,48 +102,25 @@ export function reduceDependencies({ node, data, key, path }: JsonSchemaReducerP
72
102
}
73
103
74
104
required = workingNode . schema . required ? workingNode . schema . required . concat ( ...required ) : required ;
105
+ required = required . filter ( ( r : string , index : number , list : string [ ] ) => list . indexOf ( r ) === index ) ;
106
+ workingNode = mergeNode ( workingNode , workingNode , "dependencies" ) ;
75
107
return workingNode . compileSchema ( { ...workingNode . schema , required } , workingNode . spointer , workingNode . schemaId ) ;
76
108
}
77
109
78
110
function validateDependencies ( { node, data, pointer, path } : JsonSchemaValidatorParams ) {
79
111
if ( ! isObject ( data ) ) {
80
112
return undefined ;
81
113
}
82
-
83
- const errors : ValidationResult [ ] = [ ] ;
84
- const dependencies = node . dependencies ;
85
- Object . keys ( data ) . forEach ( ( property ) => {
86
- const propertyValue = dependencies [ property ] ;
87
- if ( propertyValue === undefined ) {
88
- return ;
89
- }
90
- // @draft >= 6 boolean schema
91
- if ( propertyValue === true ) {
92
- return ;
93
- }
94
- if ( propertyValue === false ) {
95
- errors . push ( node . createError ( "missing-dependency-error" , { pointer, schema : node . schema , value : data } ) ) ;
96
- return ;
97
- }
98
-
99
- if ( Array . isArray ( propertyValue ) ) {
100
- propertyValue
101
- . filter ( ( dependency : any ) => getValue ( data , dependency ) === undefined )
102
- . forEach ( ( missingProperty : any ) =>
103
- errors . push (
104
- node . createError ( "missing-dependency-error" , {
105
- missingProperty,
106
- pointer : `${ pointer } /${ missingProperty } ` ,
107
- schema : node . schema ,
108
- value : data
109
- } )
110
- )
111
- ) ;
112
- } else if ( isSchemaNode ( propertyValue ) ) {
113
- errors . push ( ...validateNode ( propertyValue , data , pointer , path ) ) ;
114
- } else {
115
- throw new Error ( `Invalid dependency definition for ${ pointer } /${ property } . Must be string[] or schema` ) ;
114
+ let errors : ValidationResult [ ] ;
115
+ if ( node . dependentRequired ) {
116
+ errors = validateDependentRequired ( { node, data, pointer, path } ) ?? [ ] ;
117
+ }
118
+ if ( node . dependentSchemas ) {
119
+ const schemaErrors = validateDependentSchemas ( { node, data, pointer, path } ) ;
120
+ if ( schemaErrors ) {
121
+ errors = errors ?? [ ] ;
122
+ errors . push ( ...schemaErrors ) ;
116
123
}
117
- } ) ;
124
+ }
118
125
return errors ;
119
126
}
0 commit comments