@@ -27,6 +27,8 @@ import debug from 'debug'
2727import { handleWarning , getCommonPropertyNames , MitigationTypes } from './utils'
2828import { GraphQLOperationType } from './types/graphql'
2929import { methodToHttpMethod } from './oas_3_tools'
30+ import { GraphQLObjectType } from 'graphql'
31+ import { getGraphQLType } from './schema_builder'
3032
3133const preprocessingLog = debug ( 'preprocessing' )
3234
@@ -651,7 +653,8 @@ export function createDataDef<TSource, TContext, TArgs>(
651653 isInputObjectType : boolean ,
652654 data : PreprocessingData < TSource , TContext , TArgs > ,
653655 oas : Oas3 ,
654- links ?: { [ key : string ] : LinkObject }
656+ links ?: { [ key : string ] : LinkObject } ,
657+ resolveDiscriminator : boolean = true
655658) : DataDefinition {
656659 const preferredName = getPreferredName ( names )
657660
@@ -866,6 +869,26 @@ export function createDataDef<TSource, TContext, TArgs>(
866869 }
867870 }
868871
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+
869892 if ( targetGraphQLType ) {
870893 switch ( targetGraphQLType ) {
871894 case 'list' :
@@ -944,6 +967,12 @@ export function createDataDef<TSource, TContext, TArgs>(
944967 }
945968}
946969
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+
947976/**
948977 * Returns the index of the data definition object in the given list that
949978 * contains the same schema and preferred name as the given one. Returns -1 if
@@ -1138,6 +1167,248 @@ function addObjectPropertiesToDataDef<TSource, TContext, TArgs>(
11381167 }
11391168}
11401169
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+
11411412/**
11421413 * Recursively traverse a schema and resolve allOf by appending the data to the
11431414 * parent schema
@@ -1284,23 +1555,26 @@ function getMemberSchemaData<TSource, TContext, TArgs>(
12841555 schema = Oas3Tools . resolveRef ( schema [ '$ref' ] , oas ) as SchemaObject
12851556 }
12861557
1558+ const collapsedSchema = resolveAllOf ( schema , { } , data , oas )
1559+
12871560 // Consolidate target GraphQL type
12881561 const memberTargetGraphQLType = Oas3Tools . getSchemaTargetGraphQLType (
1289- schema ,
1562+ collapsedSchema ,
12901563 data
12911564 )
1565+
12921566 if ( memberTargetGraphQLType ) {
12931567 result . allTargetGraphQLTypes . push ( memberTargetGraphQLType )
12941568 }
12951569
12961570 // Consolidate properties
1297- if ( schema . properties ) {
1298- result . allProperties . push ( schema . properties )
1571+ if ( collapsedSchema . properties ) {
1572+ result . allProperties . push ( collapsedSchema . properties )
12991573 }
13001574
13011575 // 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 )
13041578 }
13051579 } )
13061580
@@ -1587,21 +1861,32 @@ function createDataDefFromOneOf<TSource, TContext, TArgs>(
15871861 ) as SchemaObject
15881862 }
15891863
1864+ const collapsedMemberSchema = resolveAllOf (
1865+ memberSchema ,
1866+ { } ,
1867+ data ,
1868+ oas
1869+ )
1870+
15901871 // Member types of GraphQL unions must be object types
15911872 if (
1592- Oas3Tools . getSchemaTargetGraphQLType ( memberSchema , data ) ===
1593- 'object'
1873+ Oas3Tools . getSchemaTargetGraphQLType (
1874+ collapsedMemberSchema ,
1875+ data
1876+ ) === 'object'
15941877 ) {
15951878 const subDefinition = createDataDef (
15961879 {
15971880 fromRef,
1598- fromSchema : memberSchema . title ,
1881+ fromSchema : collapsedMemberSchema . title ,
15991882 fromPath : `${ saneName } Member`
16001883 } ,
16011884 memberSchema ,
16021885 isInputObjectType ,
16031886 data ,
1604- oas
1887+ oas ,
1888+ { } ,
1889+ false
16051890 )
16061891 ; ( def . subDefinitions as DataDefinition [ ] ) . push ( subDefinition )
16071892 } else {
0 commit comments