Allow differing discriminator key values and discriminator model names
Description
At the moment, graphql-compose-mongoose generates unexpected schema when the value used for a discriminator key is not the same as the model name of the discriminator.
If using the two-argument version of Mongoose's Model#discriminator() function, the GraphQL schema generated works as expected.
However, if using the three-argument version of that function and suppling a different model name for the discriminator than the string used as the value of the discriminator key, we encounter some odd behavior.
Example Case
Extending from the docs:
const DKey = 'type';
const typeValuesToModels = {
person: 'PersonCharacter',
droid: 'DroidCharacter',
};
// DEFINE BASE SCHEMA
const CharacterSchema = new mongoose.Schema({
type: {
type: String,
require: true,
enum: (Object.keys(typeValuesToModels): Array<string>),
description: 'Character type Droid or Person',
},
name: String,
height: Number,
mass: Number,
films: [String],
});
// DEFINE DISCRIMINATOR SCHEMAS
const DroidSchema = new mongoose.Schema({
makeDate: String,
primaryFunction: [String],
});
const PersonSchema = new mongoose.Schema({
gender: String,
hairColor: String,
starships: [String],
});
// set discriminator Key
CharacterSchema.set('discriminatorKey', DKey);
// create base Model
const CharacterModel = mongoose.model('Character', CharacterSchema);
// create mongoose discriminator models
const DroidModel = CharacterModel.discriminator(typeValuesToModels.droid, DroidSchema, 'droid');
const PersonModel = CharacterModel.discriminator(typeValuesToModels.person, PersonSchema, 'person');
^^ Key difference is in the last two lines of the above.
With this setup, I would expect that graphql-compose-mongoose would generate a schema that includes an type EnumDKeyCharacterType with the values person and droid, because these represent the string values saved in the document's type field that Mongoose uses to identify which discriminator model to use.
Instead, this setup will generate an EnumDKeyCharacterType with the values DroidCharacter and PersonCharacter (i.e. the model names for the discriminators), which prevents reading/writing the type field because the enum values used by Mongoose do not match the EnumDKeyCharacterType values used by GraphQL.
What this means in practice is that queries which include the discriminator key field throw errors.
Errors
For example, this query:
{
droidCharacterOne {
_id
name
type
__typename
}
}
Returns this response:
{
"errors": [
{
"message": "Expected a value of type \"EnumDKeyCharacterType\" but received: \"droid\"",
"locations": [
{
"line": 5,
"column": 5
}
],
"path": [
"droidCharacterOne",
"type"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Expected a value of type \"EnumDKeyCharacterType\" but received: \"droid\"",
" at completeLeafValue (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:638:11)",
" at completeValue (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:579:12)",
" at completeValueCatchingError (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:495:19)",
" at resolveField (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:435:10)",
" at executeFields (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:275:18)",
" at collectAndExecuteSubfields (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:713:10)",
" at completeObjectValue (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:703:10)",
" at completeValue (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:591:12)",
" at /Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:492:16",
" at <anonymous>",
" at process._tickDomainCallback (internal/process/next_tick.js:228:7)"
]
}
}
}
],
"data": {
"droidCharacterOne": {
"_id": "5ad7d3c623a9b5d7aec16c5c",
"name": "R2D2",
"type": null,
"__typename": "DroidCharacter"
}
}
}
Also, queries on the base model generate errors as well:
{
characterOne {
_id
name
type
__typename
}
}
Returns:
{
"errors": [
{
"message": "Cannot find ObjectTypeComposer with name droid",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"characterOne"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Cannot find ObjectTypeComposer with name droid",
" at SchemaComposer.getOTC (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql-compose/lib/SchemaComposer.js:463:11)",
" at resolveType (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql-compose-mongoose/node8/discriminators/DiscriminatorTypeComposer.js:111:40)",
" at completeAbstractValue (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:652:21)",
" at completeValue (/Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:585:12)",
" at /Users/tannerwelsh/Code/Swayable/swaypi/node_modules/graphql/execution/execute.js:492:16",
" at <anonymous>",
" at process._tickDomainCallback (internal/process/next_tick.js:228:7)"
]
}
}
}
],
"data": {
"characterOne": null
}
}
If we modify the above to remove the type field from the response, we don't get any errors but the __typename provided just uses the base model type, not the specific discriminator type for this object.
{
characterOne {
_id
name
__typename
}
}
{
"data": {
"droidCharacterOne": {
"_id": "5ad7d3c623a9b5d7aec16c5c",
"name": "R2D2",
"__typename": "Character"
}
}
}
Proposed Change
It would be great if this plugin could handle discriminators that have a non-equal model name and value used by the discriminator key, mirroring the functionality of Mongoose.
To achieve this, I suspect changes would need to be made to these functions:
- https://github.com/graphql-compose/graphql-compose-mongoose/blob/master/src/discriminators/DiscriminatorTypeComposer.js#L48
- https://github.com/graphql-compose/graphql-compose-mongoose/blob/master/src/discriminators/DiscriminatorTypeComposer.js#L152
@tannerwelsh I'll agree with your proposed changes. 👍
But according my current bandwidth I cannot introduce it myself. It will be great if you have time to do this! PR welcome!
Thanks for the reply @nodkz ! If I have time soon I'll see about writing a PR.