@@ -111,6 +111,8 @@ class Type {
111
111
wrapUnions = 'auto' ;
112
112
} else if ( typeof wrapUnions == 'string' ) {
113
113
wrapUnions = wrapUnions . toLowerCase ( ) ;
114
+ } else if ( typeof wrapUnions === 'function' ) {
115
+ wrapUnions = 'auto' ;
114
116
}
115
117
switch ( wrapUnions ) {
116
118
case 'always' :
@@ -196,11 +198,20 @@ class Type {
196
198
let types = schema . map ( ( obj ) => {
197
199
return Type . forSchema ( obj , opts ) ;
198
200
} ) ;
201
+ let projectionFn ;
199
202
if ( ! UnionType ) {
200
- UnionType = isAmbiguous ( types ) ? WrappedUnionType : UnwrappedUnionType ;
203
+ if ( typeof opts . wrapUnions === 'function' ) {
204
+ // we have a projection function
205
+ projectionFn = opts . wrapUnions ( types ) ;
206
+ UnionType = typeof projectionFn !== 'undefined'
207
+ ? UnwrappedUnionType
208
+ : WrappedUnionType ;
209
+ } else {
210
+ UnionType = isAmbiguous ( types ) ? WrappedUnionType : UnwrappedUnionType ;
211
+ }
201
212
}
202
213
LOGICAL_TYPE = logicalType ;
203
- type = new UnionType ( types , opts ) ;
214
+ type = new UnionType ( types , opts , projectionFn ) ;
204
215
} else { // New type definition.
205
216
type = ( function ( typeName ) {
206
217
let Type = TYPES [ typeName ] ;
@@ -341,10 +352,10 @@ class Type {
341
352
return branchTypes [ name ] ;
342
353
} ) , opts ) ;
343
354
} catch ( err ) {
344
- opts . wrapUnions = wrapUnions ;
345
355
throw err ;
356
+ } finally {
357
+ opts . wrapUnions = wrapUnions ;
346
358
}
347
- opts . wrapUnions = wrapUnions ;
348
359
return unionType ;
349
360
}
350
361
@@ -1226,6 +1237,60 @@ UnionType.prototype._branchConstructor = function () {
1226
1237
throw new Error ( 'unions cannot be directly wrapped' ) ;
1227
1238
} ;
1228
1239
1240
+
1241
+ function generateProjectionIndexer ( projectionFn ) {
1242
+ return ( val ) => {
1243
+ const index = projectionFn ( val ) ;
1244
+ if ( typeof index !== 'number' ) {
1245
+ throw new Error ( `Projected index '${ index } ' is not valid` ) ;
1246
+ }
1247
+ return index ;
1248
+ } ;
1249
+ }
1250
+
1251
+ function generateDefaultIndexer ( types ) {
1252
+ const dynamicBranches = [ ] ;
1253
+ const bucketIndices = { } ;
1254
+
1255
+ const getBranchIndex = ( any , index ) => {
1256
+ let logicalBranches = dynamicBranches ;
1257
+ for ( let i = 0 , l = logicalBranches . length ; i < l ; i ++ ) {
1258
+ let branch = logicalBranches [ i ] ;
1259
+ if ( branch . type . _check ( any ) ) {
1260
+ if ( index === undefined ) {
1261
+ index = branch . index ;
1262
+ } else {
1263
+ // More than one branch matches the value so we aren't guaranteed to
1264
+ // infer the correct type. We throw rather than corrupt data. This can
1265
+ // be fixed by "tightening" the logical types.
1266
+ throw new Error ( 'ambiguous conversion' ) ;
1267
+ }
1268
+ }
1269
+ }
1270
+ return index ;
1271
+ }
1272
+
1273
+ types . forEach ( function ( type , index ) {
1274
+ if ( Type . isType ( type , 'abstract' , 'logical' ) ) {
1275
+ dynamicBranches . push ( { index, type} ) ;
1276
+ } else {
1277
+ let bucket = getTypeBucket ( type ) ;
1278
+ if ( bucketIndices [ bucket ] !== undefined ) {
1279
+ throw new Error ( `ambiguous unwrapped union: ${ j ( this ) } ` ) ;
1280
+ }
1281
+ bucketIndices [ bucket ] = index ;
1282
+ }
1283
+ } ) ;
1284
+ return ( val ) => {
1285
+ let index = bucketIndices [ getValueBucket ( val ) ] ;
1286
+ if ( dynamicBranches . length ) {
1287
+ // Slower path, we must run the value through all branches.
1288
+ index = getBranchIndex ( val , index ) ;
1289
+ }
1290
+ return index ;
1291
+ } ;
1292
+ }
1293
+
1229
1294
/**
1230
1295
* "Natural" union type.
1231
1296
*
@@ -1246,54 +1311,17 @@ UnionType.prototype._branchConstructor = function () {
1246
1311
* + `map`, `record`
1247
1312
*/
1248
1313
class UnwrappedUnionType extends UnionType {
1249
- constructor ( schema , opts ) {
1314
+ constructor ( schema , opts , /* @private parameter */ _projectionFn ) {
1250
1315
super ( schema , opts ) ;
1251
1316
1252
- this . _dynamicBranches = null ;
1253
- this . _bucketIndices = { } ;
1254
- this . types . forEach ( function ( type , index ) {
1255
- if ( Type . isType ( type , 'abstract' , 'logical' ) ) {
1256
- if ( ! this . _dynamicBranches ) {
1257
- this . _dynamicBranches = [ ] ;
1258
- }
1259
- this . _dynamicBranches . push ( { index, type} ) ;
1260
- } else {
1261
- let bucket = getTypeBucket ( type ) ;
1262
- if ( this . _bucketIndices [ bucket ] !== undefined ) {
1263
- throw new Error ( `ambiguous unwrapped union: ${ j ( this ) } ` ) ;
1264
- }
1265
- this . _bucketIndices [ bucket ] = index ;
1266
- }
1267
- } , this ) ;
1268
-
1269
- Object . freeze ( this ) ;
1270
- }
1271
-
1272
- _getIndex ( val ) {
1273
- let index = this . _bucketIndices [ getValueBucket ( val ) ] ;
1274
- if ( this . _dynamicBranches ) {
1275
- // Slower path, we must run the value through all branches.
1276
- index = this . _getBranchIndex ( val , index ) ;
1317
+ if ( ! _projectionFn && opts && typeof opts . wrapUnions === 'function' ) {
1318
+ _projectionFn = opts . wrapUnions ( this . types ) ;
1277
1319
}
1278
- return index ;
1279
- }
1320
+ this . _getIndex = _projectionFn
1321
+ ? generateProjectionIndexer ( _projectionFn )
1322
+ : generateDefaultIndexer ( this . types ) ;
1280
1323
1281
- _getBranchIndex ( any , index ) {
1282
- let logicalBranches = this . _dynamicBranches ;
1283
- for ( let i = 0 , l = logicalBranches . length ; i < l ; i ++ ) {
1284
- let branch = logicalBranches [ i ] ;
1285
- if ( branch . type . _check ( any ) ) {
1286
- if ( index === undefined ) {
1287
- index = branch . index ;
1288
- } else {
1289
- // More than one branch matches the value so we aren't guaranteed to
1290
- // infer the correct type. We throw rather than corrupt data. This can
1291
- // be fixed by "tightening" the logical types.
1292
- throw new Error ( 'ambiguous conversion' ) ;
1293
- }
1294
- }
1295
- }
1296
- return index ;
1324
+ Object . freeze ( this ) ;
1297
1325
}
1298
1326
1299
1327
_check ( val , flags , hook , path ) {
@@ -1355,16 +1383,18 @@ class UnwrappedUnionType extends UnionType {
1355
1383
// Using the `coerceBuffers` option can cause corruption and erroneous
1356
1384
// failures with unwrapped unions (in rare cases when the union also
1357
1385
// contains a record which matches a buffer's JSON representation).
1358
- if ( isJsonBuffer ( val ) && this . _bucketIndices . buffer !== undefined ) {
1359
- index = this . _bucketIndices . buffer ;
1360
- } else {
1361
- index = this . _getIndex ( val ) ;
1386
+ if ( isJsonBuffer ( val ) ) {
1387
+ let bufIndex = this . types . findIndex ( t => getTypeBucket ( t ) === 'buffer' ) ;
1388
+ if ( bufIndex !== - 1 ) {
1389
+ index = bufIndex ;
1390
+ }
1362
1391
}
1392
+ index ??= this . _getIndex ( val ) ;
1363
1393
break ;
1364
1394
case 2 :
1365
1395
// Decoding from JSON, we must unwrap the value.
1366
1396
if ( val === null ) {
1367
- index = this . _bucketIndices [ ' null' ] ;
1397
+ index = this . _getIndex ( null ) ;
1368
1398
} else if ( typeof val === 'object' ) {
1369
1399
let keys = Object . keys ( val ) ;
1370
1400
if ( keys . length === 1 ) {
0 commit comments