graphql-compose / graphql-compose/graphql-compose-mongoose

Allow differing discriminator key values and discriminator model names

Open
#187 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
706
Forks
98
PR merge metrics
No merged PRs in 30d

Description

### 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()](https://mongoosejs.com/docs/api/model.html#model_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:

```js
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),
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:

```graphql
{
droidCharacterOne {
_id
name
type
__typename
}
}
```

Returns this response:

```json
{
"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 ",
" 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:

```graphql
{
characterOne {
_id
name
type
__typename
}
}
```

Returns:

```json
{
"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 ",
" 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.

```graphql
{
characterOne {
_id
name
__typename
}
}
```

```json
{
"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

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.