@@ -13,6 +13,8 @@ import {
13
13
SCHEMA_OPTIONS_SYMBOL ,
14
14
isEmptyObject
15
15
} from './helpers' ;
16
+ import { ValidationError , ERRORS , targetHasErrors , ValidationErrors , reporter , Reporter } from './validation/reporter' ;
17
+ import { ValidatorError } from './validation/validators/ValidatorError' ;
16
18
17
19
export enum NodeKind {
18
20
Root = 'Root' ,
@@ -47,12 +49,58 @@ type AddNode<Target, Source> = Overwrite<
47
49
preparedAction ?: ( ...args : any ) => any ;
48
50
}
49
51
> ;
52
+
53
+ /**
54
+ * Options attached to a `Schema` or `StrictSchema`
55
+ */
50
56
export interface SchemaOptions < Target = any > {
51
- class ?: { automapping : boolean } ;
57
+ /**
58
+ * Specify how to handle ES6 Class
59
+ * @memberof SchemaOptions
60
+ */
61
+ class ?: {
62
+ /**
63
+ * Specify wether ES6 Class fields should be automapped if names on source and target match
64
+ * @default true
65
+ * @type {boolean }
66
+ */
67
+ automapping : boolean ;
68
+ } ;
69
+ /**
70
+ * Specify how to handle undefined values mapped during the transformations
71
+ * @memberof SchemaOptions
72
+ */
52
73
undefinedValues ?: {
74
+ /**
75
+ * Undefined values should be removed from the target
76
+ * @default false
77
+ * @type {boolean }
78
+ */
53
79
strip : boolean ;
80
+ /**
81
+ * Optional callback to be executed for every undefined property on the Target
82
+ * @function default
83
+ */
54
84
default ?: ( target : Target , propertyPath : string ) => any ;
55
85
} ;
86
+ /**
87
+ * Schema validation options
88
+ * @memberof SchemaOptions
89
+ */
90
+ validation ?: {
91
+ /**
92
+ * Should throw when property validation fails
93
+ * @default false
94
+ * @type {boolean }
95
+ */
96
+ throw : boolean ;
97
+ /**
98
+ * Custom reporter to use when throw option is set to true
99
+ * @default false
100
+ * @type {boolean }
101
+ */
102
+ reporter ?: Reporter ;
103
+ } ;
56
104
}
57
105
58
106
/**
@@ -99,8 +147,12 @@ export class MorphismSchemaTree<Target, Source> {
99
147
parentKeyPath = parentKeyPath ? `${ parentKeyPath } .${ actionKey } ` : actionKey ;
100
148
} else {
101
149
if ( actionKey ) {
150
+ if ( isObject ( partialSchema ) && isEmptyObject ( partialSchema as any ) )
151
+ throw new Error (
152
+ `A value of a schema property can't be an empty object. Value ${ JSON . stringify ( partialSchema ) } found for property ${ actionKey } `
153
+ ) ;
102
154
// check if actionKey exists to verify if not root node
103
- this . add ( { propertyName : actionKey , action : null } , parentKeyPath ) ;
155
+ this . add ( { propertyName : actionKey , action : partialSchema as Actions < Target , Source > } , parentKeyPath ) ;
104
156
parentKeyPath = parentKeyPath ? `${ parentKeyPath } .${ actionKey } ` : actionKey ;
105
157
}
106
158
@@ -121,23 +173,19 @@ export class MorphismSchemaTree<Target, Source> {
121
173
queue . push ( this . root ) ;
122
174
while ( queue . length > 0 ) {
123
175
let node = queue . shift ( ) ;
124
-
125
176
if ( node ) {
126
177
for ( let i = 0 , length = node . children . length ; i < length ; i ++ ) {
127
178
queue . push ( node . children [ i ] ) ;
128
179
}
129
180
if ( node . data . kind !== NodeKind . Root ) {
130
181
yield node ;
131
182
}
132
- } else {
133
- return ;
134
183
}
135
184
}
136
185
}
137
186
138
187
add ( data : AddNode < Target , Source > , targetPropertyPath ?: string ) {
139
- const kind = this . getActionKind ( data . action ) ;
140
- if ( ! kind ) throw new Error ( `The action specified for ${ data . propertyName } is not supported.` ) ;
188
+ const kind = this . getActionKind ( data ) ;
141
189
142
190
const nodeToAdd : SchemaNode < Target , Source > = {
143
191
data : { ...data , kind, targetPropertyPath : '' } ,
@@ -161,15 +209,16 @@ export class MorphismSchemaTree<Target, Source> {
161
209
}
162
210
}
163
211
164
- getActionKind ( action : Actions < Target , Source > | null ) {
165
- if ( isActionString ( action ) ) return NodeKind . ActionString ;
166
- if ( isFunction ( action ) ) return NodeKind . ActionFunction ;
167
- if ( isActionSelector ( action ) ) return NodeKind . ActionSelector ;
168
- if ( isActionAggregator ( action ) ) return NodeKind . ActionAggregator ;
169
- if ( action === null ) return NodeKind . Property ;
212
+ getActionKind ( data : AddNode < Target , Source > ) {
213
+ if ( isActionString ( data . action ) ) return NodeKind . ActionString ;
214
+ if ( isFunction ( data . action ) ) return NodeKind . ActionFunction ;
215
+ if ( isActionSelector ( data . action ) ) return NodeKind . ActionSelector ;
216
+ if ( isActionAggregator ( data . action ) ) return NodeKind . ActionAggregator ;
217
+ if ( isObject ( data . action ) ) return NodeKind . Property ;
218
+ throw new Error ( `The action specified for ${ data . propertyName } is not supported.` ) ;
170
219
}
171
220
172
- getPreparedAction ( nodeData : SchemaNodeData < Target , Source > ) : PreparedAction | null {
221
+ getPreparedAction ( nodeData : SchemaNodeData < Target , Source > ) : PreparedAction | null | undefined {
173
222
const { propertyName : targetProperty , action, kind } = nodeData ;
174
223
// iterate on every action of the schema
175
224
if ( isActionString ( action ) ) {
@@ -185,26 +234,52 @@ export class MorphismSchemaTree<Target, Source> {
185
234
// Action<Object>: a path and a function: [ destination : { path: 'source', fn:(fieldValue, items) }]
186
235
return ( { object, items, objectToCompute } ) => {
187
236
let result ;
188
- try {
189
- let value ;
237
+ if ( action . path ) {
190
238
if ( Array . isArray ( action . path ) ) {
191
- value = aggregator ( action . path , object ) ;
239
+ result = aggregator ( action . path , object ) ;
192
240
} else if ( isString ( action . path ) ) {
193
- value = get ( object , action . path ) ;
241
+ result = get ( object , action . path ) ;
242
+ }
243
+ } else {
244
+ result = object ;
245
+ }
246
+
247
+ if ( action . fn ) {
248
+ try {
249
+ result = action . fn . call ( undefined , result , object , items , objectToCompute ) ;
250
+ } catch ( e ) {
251
+ e . message = `Unable to set target property [${ targetProperty } ].
252
+ \n An error occured when applying [${ action . fn . name } ] on property [${ action . path } ]
253
+ \n Internal error: ${ e . message } ` ;
254
+ throw e ;
255
+ }
256
+ }
257
+
258
+ if ( action . validation ) {
259
+ try {
260
+ result = action . validation . validate ( result ) ;
261
+ } catch ( error ) {
262
+ if ( error instanceof ValidatorError ) {
263
+ const validationError = new ValidationError ( { targetProperty, expect : error . expect , value : error . value } ) ;
264
+ if ( targetHasErrors ( objectToCompute ) ) {
265
+ objectToCompute [ ERRORS ] . addError ( validationError ) ;
266
+ } else {
267
+ if ( this . schemaOptions . validation && this . schemaOptions . validation . reporter ) {
268
+ objectToCompute [ ERRORS ] = new ValidationErrors ( this . schemaOptions . validation . reporter , objectToCompute ) ;
269
+ } else {
270
+ objectToCompute [ ERRORS ] = new ValidationErrors ( reporter , objectToCompute ) ;
271
+ }
272
+ objectToCompute [ ERRORS ] . addError ( validationError ) ;
273
+ }
274
+ } else {
275
+ throw error ;
276
+ }
194
277
}
195
- result = action . fn . call ( undefined , value , object , items , objectToCompute ) ;
196
- } catch ( e ) {
197
- e . message = `Unable to set target property [${ targetProperty } ].
198
- \n An error occured when applying [${ action . fn . name } ] on property [${ action . path } ]
199
- \n Internal error: ${ e . message } ` ;
200
- throw e ;
201
278
}
202
279
return result ;
203
280
} ;
204
281
} else if ( kind === NodeKind . Property ) {
205
282
return null ;
206
- } else {
207
- throw new Error ( `The action specified for ${ targetProperty } is not supported.` ) ;
208
283
}
209
284
}
210
285
}
0 commit comments