@@ -21,13 +21,15 @@ import {
21
21
SchemaValidator ,
22
22
SchemaValidatorResult ,
23
23
} from './interface' ;
24
- import { JsonPointer , JsonVisitor , parseJsonPointer , visitJson , visitJsonSchema } from './visitor' ;
24
+ import { addUndefinedDefaults } from './transforms' ;
25
+ import { JsonVisitor , visitJson } from './visitor' ;
25
26
26
27
27
28
export class CoreSchemaRegistry implements SchemaRegistry {
28
29
private _ajv : ajv . Ajv ;
29
30
private _uriCache = new Map < string , JsonObject > ( ) ;
30
31
private _pre = new PartiallyOrderedSet < JsonVisitor > ( ) ;
32
+ private _post = new PartiallyOrderedSet < JsonVisitor > ( ) ;
31
33
32
34
constructor ( formats : SchemaFormat [ ] = [ ] ) {
33
35
/**
@@ -48,74 +50,8 @@ export class CoreSchemaRegistry implements SchemaRegistry {
48
50
} ) ;
49
51
50
52
this . _ajv . addMetaSchema ( require ( 'ajv/lib/refs/json-schema-draft-04.json' ) ) ;
51
- }
52
-
53
- private _clean (
54
- data : any , // tslint:disable-line:no-any
55
- schema : JsonObject ,
56
- validate : ajv . ValidateFunction ,
57
- parentDataCache : WeakMap < object , any > , // tslint:disable-line:no-any
58
- ) {
59
- visitJsonSchema (
60
- schema ,
61
- ( currentSchema : object , pointer : JsonPointer , parentSchema ?: object , index ?: string ) => {
62
- // If we're at the root, skip.
63
- if ( parentSchema === undefined || index === undefined ) {
64
- return ;
65
- }
66
-
67
- const parsedPointer = parseJsonPointer ( pointer ) ;
68
- // Every other path fragment is either 'properties', 'items', 'allOf', ...
69
- const nonPropertyParsedPP = parsedPointer . filter ( ( _ , i ) => ! ( i % 2 ) ) ;
70
- // Skip if it's part of a definitions or too complex for us to analyze.
71
- if ( nonPropertyParsedPP . some ( f => f == 'definitions' || f == 'allOf' || f == 'anyOf' ) ) {
72
- return ;
73
- }
74
-
75
- let maybeParentData = parentDataCache . get ( parentSchema ) ;
76
- if ( ! maybeParentData ) {
77
- // Every other path fragment is either 'properties' or 'items' in this model.
78
- const parentDataPointer = parsedPointer . filter ( ( _ , i ) => i % 2 ) ;
79
-
80
- // Find the parentData from the list.
81
- maybeParentData = data ;
82
- for ( const index of parentDataPointer . slice ( 0 , - 1 ) ) {
83
- if ( maybeParentData [ index ] === undefined ) {
84
- // tslint:disable-next-line:no-any
85
- if ( parentSchema . hasOwnProperty ( 'items' ) || ( parentSchema as any ) [ 'type' ] == 'array' ) {
86
- maybeParentData [ index ] = [ ] ;
87
- } else {
88
- maybeParentData [ index ] = { } ;
89
- }
90
- }
91
- maybeParentData = maybeParentData [ index ] ;
92
- }
93
- parentDataCache . set ( parentSchema , maybeParentData ) ;
94
- }
95
-
96
- if ( currentSchema . hasOwnProperty ( '$ref' ) ) {
97
- const $ref = ( currentSchema as { $ref : string } ) [ '$ref' ] ;
98
- const refHash = $ref . split ( '#' , 2 ) [ 1 ] ;
99
- const refUrl = $ref . startsWith ( '#' ) ? $ref : $ref . split ( '#' , 1 ) ;
100
-
101
- let refVal = validate ;
102
- if ( ! $ref . startsWith ( '#' ) ) {
103
- // tslint:disable-next-line:no-any
104
- refVal = ( validate . refVal as any ) [ ( validate . refs as any ) [ refUrl [ 0 ] ] ] ;
105
- }
106
- if ( refHash ) {
107
- // tslint:disable-next-line:no-any
108
- refVal = ( refVal . refVal as any ) [ ( refVal . refs as any ) [ '#' + refHash ] ] ;
109
- }
110
53
111
- maybeParentData [ index ] = { } ;
112
- this . _clean ( maybeParentData [ index ] , refVal . schema as JsonObject , refVal , parentDataCache ) ;
113
-
114
- return ;
115
- } else if ( ! maybeParentData . hasOwnProperty ( index ) ) {
116
- maybeParentData [ index ] = undefined ;
117
- }
118
- } ) ;
54
+ this . addPostTransform ( addUndefinedDefaults ) ;
119
55
}
120
56
121
57
private _fetch ( uri : string ) : Promise < JsonObject > {
@@ -160,7 +96,41 @@ export class CoreSchemaRegistry implements SchemaRegistry {
160
96
this . _pre . add ( visitor , deps ) ;
161
97
}
162
98
163
- compile ( schema : Object ) : Observable < SchemaValidator > {
99
+ /**
100
+ * Add a transformation step after the validation of any Json. The JSON will not be validated
101
+ * after the POST, so if transformations are not compatible with the Schema it will not result
102
+ * in an error.
103
+ * @param {JsonVisitor } visitor The visitor to transform every value.
104
+ * @param {JsonVisitor[] } deps A list of other visitors to run before.
105
+ */
106
+ addPostTransform ( visitor : JsonVisitor , deps ?: JsonVisitor [ ] ) {
107
+ this . _post . add ( visitor , deps ) ;
108
+ }
109
+
110
+ protected _resolver (
111
+ ref : string ,
112
+ validate : ajv . ValidateFunction ,
113
+ ) : { context ?: ajv . ValidateFunction , schema ?: JsonObject } {
114
+ if ( ! validate ) {
115
+ return { } ;
116
+ }
117
+
118
+ const refHash = ref . split ( '#' , 2 ) [ 1 ] ;
119
+ const refUrl = ref . startsWith ( '#' ) ? ref : ref . split ( '#' , 1 ) ;
120
+
121
+ if ( ! ref . startsWith ( '#' ) ) {
122
+ // tslint:disable-next-line:no-any
123
+ validate = ( validate . refVal as any ) [ ( validate . refs as any ) [ refUrl [ 0 ] ] ] ;
124
+ }
125
+ if ( validate && refHash ) {
126
+ // tslint:disable-next-line:no-any
127
+ validate = ( validate . refVal as any ) [ ( validate . refs as any ) [ '#' + refHash ] ] ;
128
+ }
129
+
130
+ return { context : validate , schema : validate && validate . schema as JsonObject } ;
131
+ }
132
+
133
+ compile ( schema : JsonObject ) : Observable < SchemaValidator > {
164
134
// Supports both synchronous and asynchronous compilation, by trying the synchronous
165
135
// version first, then if refs are missing this will fails.
166
136
// We also add any refs from external fetched schemas so that those will also be used
@@ -193,7 +163,9 @@ export class CoreSchemaRegistry implements SchemaRegistry {
193
163
let dataObs = observableOf ( data ) ;
194
164
this . _pre . forEach ( visitor =>
195
165
dataObs = dataObs . pipe (
196
- concatMap ( data => visitJson ( data as JsonValue , visitor ) ) ,
166
+ concatMap ( data => {
167
+ return visitJson ( data as JsonValue , visitor , schema , this . _resolver , validate ) ;
168
+ } ) ,
197
169
) ,
198
170
) ;
199
171
@@ -206,14 +178,36 @@ export class CoreSchemaRegistry implements SchemaRegistry {
206
178
: fromPromise ( ( result as PromiseLike < boolean > )
207
179
. then ( result => [ updatedData , result ] ) ) ;
208
180
} ) ,
181
+ switchMap ( ( [ data , valid ] ) => {
182
+ if ( valid ) {
183
+ let dataObs = observableOf ( data ) ;
184
+ this . _post . forEach ( visitor =>
185
+ dataObs = dataObs . pipe (
186
+ concatMap ( data => {
187
+ return visitJson (
188
+ data as JsonValue ,
189
+ visitor ,
190
+ schema ,
191
+ this . _resolver ,
192
+ validate ,
193
+ ) ;
194
+ } ) ,
195
+ ) ,
196
+ ) ;
197
+
198
+ return dataObs . pipe (
199
+ map ( data => [ data , valid ] ) ,
200
+ ) ;
201
+ } else {
202
+ return observableOf ( [ data , valid ] ) ;
203
+ }
204
+ } ) ,
209
205
map ( ( [ data , valid ] ) => {
210
206
if ( valid ) {
211
207
// tslint:disable-next-line:no-any
212
208
const schemaDataMap = new WeakMap < object , any > ( ) ;
213
209
schemaDataMap . set ( schema , data ) ;
214
210
215
- this . _clean ( data , schema as JsonObject , validate , schemaDataMap ) ;
216
-
217
211
return { data, success : true } as SchemaValidatorResult ;
218
212
}
219
213
0 commit comments