@@ -27,6 +27,8 @@ import debug from 'debug'
27
27
import { handleWarning , getCommonPropertyNames , MitigationTypes } from './utils'
28
28
import { GraphQLOperationType } from './types/graphql'
29
29
import { methodToHttpMethod } from './oas_3_tools'
30
+ import { GraphQLObjectType } from 'graphql'
31
+ import { getGraphQLType } from './schema_builder'
30
32
31
33
const preprocessingLog = debug ( 'preprocessing' )
32
34
@@ -651,7 +653,8 @@ export function createDataDef<TSource, TContext, TArgs>(
651
653
isInputObjectType : boolean ,
652
654
data : PreprocessingData < TSource , TContext , TArgs > ,
653
655
oas : Oas3 ,
654
- links ?: { [ key : string ] : LinkObject }
656
+ links ?: { [ key : string ] : LinkObject } ,
657
+ resolveDiscriminator : boolean = true
655
658
) : DataDefinition {
656
659
const preferredName = getPreferredName ( names )
657
660
@@ -866,6 +869,26 @@ export function createDataDef<TSource, TContext, TArgs>(
866
869
}
867
870
}
868
871
872
+ /**
873
+ * Union types will be extracted either from the discriminator mapping
874
+ * or from the enum list defined for discriminator property
875
+ */
876
+ if ( hasDiscriminator ( schema ) && resolveDiscriminator ) {
877
+ const unionDef = createDataDefFromDiscriminator (
878
+ saneName ,
879
+ schema ,
880
+ isInputObjectType ,
881
+ def ,
882
+ data ,
883
+ oas
884
+ )
885
+
886
+ if ( unionDef && typeof unionDef === 'object' ) {
887
+ def . targetGraphQLType = 'json'
888
+ return def
889
+ }
890
+ }
891
+
869
892
if ( targetGraphQLType ) {
870
893
switch ( targetGraphQLType ) {
871
894
case 'list' :
@@ -944,6 +967,12 @@ export function createDataDef<TSource, TContext, TArgs>(
944
967
}
945
968
}
946
969
970
+ // Checks if schema object has discriminator field
971
+ function hasDiscriminator ( schema : SchemaObject ) : boolean {
972
+ const collapsedSchema : SchemaObject = JSON . parse ( JSON . stringify ( schema ) )
973
+ return collapsedSchema . discriminator ?. propertyName ? true : false
974
+ }
975
+
947
976
/**
948
977
* Returns the index of the data definition object in the given list that
949
978
* contains the same schema and preferred name as the given one. Returns -1 if
@@ -1138,6 +1167,248 @@ function addObjectPropertiesToDataDef<TSource, TContext, TArgs>(
1138
1167
}
1139
1168
}
1140
1169
1170
+ /**
1171
+ * Iterate through discriminator object mapping or through discriminator
1172
+ * enum values and resolve derived schemas
1173
+ */
1174
+ function createDataDefFromDiscriminator < TSource , TContext , TArgs > (
1175
+ saneName : string ,
1176
+ schema : SchemaObject ,
1177
+ isInputObjectType = false ,
1178
+ def : DataDefinition ,
1179
+ data : PreprocessingData < TSource , TContext , TArgs > ,
1180
+ oas : Oas3
1181
+ ) : DataDefinition {
1182
+ /**
1183
+ * Check if discriminator exists and if it has
1184
+ * defined property name
1185
+ */
1186
+ if ( ! schema . discriminator ?. propertyName ) {
1187
+ return null
1188
+ }
1189
+
1190
+ const unionTypes : DataDefinition [ ] = [ ]
1191
+ const schemaToTypeMap : Map < string , string > = new Map ( )
1192
+
1193
+ // Get the discriminator property name
1194
+ const discriminator = schema . discriminator . propertyName
1195
+
1196
+ /**
1197
+ * Check if there is defined property pointed by discriminator
1198
+ * and if that property is in the required properties list
1199
+ */
1200
+ if (
1201
+ schema . properties &&
1202
+ schema . properties [ discriminator ] &&
1203
+ schema . required &&
1204
+ schema . required . indexOf ( discriminator ) > - 1
1205
+ ) {
1206
+ let discriminatorProperty = schema . properties [ discriminator ]
1207
+
1208
+ // Dereference discriminator property
1209
+ if ( '$ref' in discriminatorProperty ) {
1210
+ discriminatorProperty = Oas3Tools . resolveRef (
1211
+ discriminatorProperty [ '$ref' ] ,
1212
+ oas
1213
+ ) as SchemaObject
1214
+ }
1215
+
1216
+ /**
1217
+ * Check if there is mapping defined for discriminator property
1218
+ * and iterate through the map in order to generate derived types
1219
+ */
1220
+ if ( schema . discriminator . mapping ) {
1221
+ for ( const key in schema . discriminator . mapping ) {
1222
+ const unionTypeDef = createUnionSubDefinitionFromDiscriminator (
1223
+ schema ,
1224
+ saneName ,
1225
+ schema . discriminator . mapping [ key ] ,
1226
+ isInputObjectType ,
1227
+ data ,
1228
+ oas
1229
+ )
1230
+
1231
+ if ( unionTypeDef ) {
1232
+ unionTypes . push ( unionTypeDef )
1233
+ schemaToTypeMap . set ( key , unionTypeDef . preferredName )
1234
+ }
1235
+ }
1236
+ } else if (
1237
+ /**
1238
+ * If there is no defined mapping, check if discriminator property
1239
+ * schema has defined enum, and if enum exists iterate through
1240
+ * the enum values and generate derived types
1241
+ */
1242
+ discriminatorProperty . enum &&
1243
+ discriminatorProperty . enum . length > 0
1244
+ ) {
1245
+ const discriminatorAllowedValues = discriminatorProperty . enum
1246
+ discriminatorAllowedValues . forEach ( ( enumValue ) => {
1247
+ const unionTypeDef = createUnionSubDefinitionFromDiscriminator (
1248
+ schema ,
1249
+ saneName ,
1250
+ enumValue ,
1251
+ isInputObjectType ,
1252
+ data ,
1253
+ oas
1254
+ )
1255
+
1256
+ if ( unionTypeDef ) {
1257
+ unionTypes . push ( unionTypeDef )
1258
+ schemaToTypeMap . set ( enumValue , unionTypeDef . preferredName )
1259
+ }
1260
+ } )
1261
+ }
1262
+ }
1263
+
1264
+ // Union type will be created if unionTypes list is not empty
1265
+ if ( unionTypes . length > 0 ) {
1266
+ const iteration = 0
1267
+
1268
+ /**
1269
+ * Get GraphQL types for union type members so that
1270
+ * these types can be used in resolveType method for
1271
+ * this union
1272
+ */
1273
+ const types = Object . values ( unionTypes ) . map ( ( memberTypeDefinition ) => {
1274
+ return getGraphQLType ( {
1275
+ def : memberTypeDefinition ,
1276
+ data,
1277
+ iteration : iteration + 1 ,
1278
+ isInputObjectType
1279
+ } ) as GraphQLObjectType
1280
+ } )
1281
+
1282
+ /**
1283
+ * TODO: Refactor this when GraphQL receives a support for input unions.
1284
+ * Create DataDefinition object for union with custom resolveType function
1285
+ * which resolves union types based on discriminator provided in the Open API
1286
+ * schema. The union data definition should be used for generating response
1287
+ * type and for inputs parent data definition should be used
1288
+ */
1289
+ def . unionDefinition = {
1290
+ ...def ,
1291
+ targetGraphQLType : 'union' ,
1292
+ subDefinitions : unionTypes ,
1293
+ resolveType : ( source , context , info ) => {
1294
+ // Find the appropriate union member type
1295
+ return types . find ( ( type ) => {
1296
+ // Check if source contains not empty discriminator field
1297
+ if ( source [ discriminator ] ) {
1298
+ const typeName = schemaToTypeMap . get ( source [ discriminator ] )
1299
+ return typeName === type . name
1300
+ }
1301
+
1302
+ return false
1303
+ } )
1304
+ }
1305
+ }
1306
+
1307
+ return def
1308
+ }
1309
+
1310
+ return null
1311
+ }
1312
+
1313
+ function createUnionSubDefinitionFromDiscriminator < TSource , TContext , TArgs > (
1314
+ unionSchema : SchemaObject ,
1315
+ unionSaneName : string ,
1316
+ subSchemaName : string ,
1317
+ isInputObjectType : boolean ,
1318
+ data : PreprocessingData < TSource , TContext , TArgs > ,
1319
+ oas : Oas3
1320
+ ) : DataDefinition {
1321
+ // Find schema for derived type using schemaName
1322
+ let schema = oas . components . schemas [ subSchemaName ]
1323
+
1324
+ // Resolve reference
1325
+ if ( schema && '$ref' in schema ) {
1326
+ schema = Oas3Tools . resolveRef ( schema [ '$ref' ] , oas ) as SchemaObject
1327
+ }
1328
+
1329
+ if ( ! schema ) {
1330
+ handleWarning ( {
1331
+ mitigationType : MitigationTypes . MISSING_SCHEMA ,
1332
+ message : `Resolving schema from discriminator with name ${ subSchemaName } in schema '${ JSON . stringify (
1333
+ unionSchema
1334
+ ) } failed because such schema was not found.`,
1335
+ data,
1336
+ log : preprocessingLog
1337
+ } )
1338
+ return null
1339
+ }
1340
+
1341
+ if ( ! isSchemaDerivedFrom ( schema , unionSchema , oas ) ) {
1342
+ return null
1343
+ }
1344
+
1345
+ const collapsedSchema = resolveAllOf ( schema , { } , data , oas )
1346
+
1347
+ if (
1348
+ collapsedSchema &&
1349
+ Oas3Tools . getSchemaTargetGraphQLType ( collapsedSchema , data ) === 'object'
1350
+ ) {
1351
+ let subNames = { }
1352
+ if ( deepEqual ( unionSchema , schema ) ) {
1353
+ subNames = {
1354
+ fromRef : `${ unionSaneName } Member` ,
1355
+ fromSchema : collapsedSchema . title
1356
+ }
1357
+ } else {
1358
+ subNames = {
1359
+ fromRef : subSchemaName ,
1360
+ fromSchema : collapsedSchema . title
1361
+ }
1362
+ }
1363
+
1364
+ return createDataDef (
1365
+ subNames ,
1366
+ schema ,
1367
+ isInputObjectType ,
1368
+ data ,
1369
+ oas ,
1370
+ { } ,
1371
+ false
1372
+ )
1373
+ }
1374
+
1375
+ return null
1376
+ }
1377
+
1378
+ /**
1379
+ * Check if child schema is derived from parent schema by recursively
1380
+ * looking into schemas references in child's allOf property
1381
+ */
1382
+ function isSchemaDerivedFrom (
1383
+ childSchema : SchemaObject ,
1384
+ parentSchema : SchemaObject ,
1385
+ oas : Oas3
1386
+ ) {
1387
+ if ( ! childSchema . allOf ) {
1388
+ return false
1389
+ }
1390
+
1391
+ for ( const allOfSchema of childSchema . allOf ) {
1392
+ let resolvedSchema : SchemaObject = null
1393
+ if ( allOfSchema && '$ref' in allOfSchema ) {
1394
+ resolvedSchema = Oas3Tools . resolveRef (
1395
+ allOfSchema [ '$ref' ] ,
1396
+ oas
1397
+ ) as SchemaObject
1398
+ } else {
1399
+ resolvedSchema = allOfSchema
1400
+ }
1401
+
1402
+ if ( deepEqual ( resolvedSchema , parentSchema ) ) {
1403
+ return true
1404
+ } else if ( isSchemaDerivedFrom ( resolvedSchema , parentSchema , oas ) ) {
1405
+ return true
1406
+ }
1407
+ }
1408
+
1409
+ return false
1410
+ }
1411
+
1141
1412
/**
1142
1413
* Recursively traverse a schema and resolve allOf by appending the data to the
1143
1414
* parent schema
@@ -1284,23 +1555,26 @@ function getMemberSchemaData<TSource, TContext, TArgs>(
1284
1555
schema = Oas3Tools . resolveRef ( schema [ '$ref' ] , oas ) as SchemaObject
1285
1556
}
1286
1557
1558
+ const collapsedSchema = resolveAllOf ( schema , { } , data , oas )
1559
+
1287
1560
// Consolidate target GraphQL type
1288
1561
const memberTargetGraphQLType = Oas3Tools . getSchemaTargetGraphQLType (
1289
- schema ,
1562
+ collapsedSchema ,
1290
1563
data
1291
1564
)
1565
+
1292
1566
if ( memberTargetGraphQLType ) {
1293
1567
result . allTargetGraphQLTypes . push ( memberTargetGraphQLType )
1294
1568
}
1295
1569
1296
1570
// Consolidate properties
1297
- if ( schema . properties ) {
1298
- result . allProperties . push ( schema . properties )
1571
+ if ( collapsedSchema . properties ) {
1572
+ result . allProperties . push ( collapsedSchema . properties )
1299
1573
}
1300
1574
1301
1575
// Consolidate required
1302
- if ( schema . required ) {
1303
- result . allRequired = result . allRequired . concat ( schema . required )
1576
+ if ( collapsedSchema . required ) {
1577
+ result . allRequired = result . allRequired . concat ( collapsedSchema . required )
1304
1578
}
1305
1579
} )
1306
1580
@@ -1587,21 +1861,32 @@ function createDataDefFromOneOf<TSource, TContext, TArgs>(
1587
1861
) as SchemaObject
1588
1862
}
1589
1863
1864
+ const collapsedMemberSchema = resolveAllOf (
1865
+ memberSchema ,
1866
+ { } ,
1867
+ data ,
1868
+ oas
1869
+ )
1870
+
1590
1871
// Member types of GraphQL unions must be object types
1591
1872
if (
1592
- Oas3Tools . getSchemaTargetGraphQLType ( memberSchema , data ) ===
1593
- 'object'
1873
+ Oas3Tools . getSchemaTargetGraphQLType (
1874
+ collapsedMemberSchema ,
1875
+ data
1876
+ ) === 'object'
1594
1877
) {
1595
1878
const subDefinition = createDataDef (
1596
1879
{
1597
1880
fromRef,
1598
- fromSchema : memberSchema . title ,
1881
+ fromSchema : collapsedMemberSchema . title ,
1599
1882
fromPath : `${ saneName } Member`
1600
1883
} ,
1601
1884
memberSchema ,
1602
1885
isInputObjectType ,
1603
1886
data ,
1604
- oas
1887
+ oas ,
1888
+ { } ,
1889
+ false
1605
1890
)
1606
1891
; ( def . subDefinitions as DataDefinition [ ] ) . push ( subDefinition )
1607
1892
} else {
0 commit comments