_I hope that this is the right place to ask for help with a GraphQL.js issue, but please let me know if it's not. To be clear: this is not a bug or a suggestion, but most probably a problem in my own code somewhere. I've also posted my question on Stack Overflow, but it didn't get a reaction or even a single up- or downvote :(_
It seems like my 'Account' Interface that I want to use for a few different account types is not recognized by GraphQL.js as a valid Interface type. I keep getting the following error message when trying to run Node with my GraphQL schema:
e:\Development\project-name\node_modules\graphql\jsutils\invariant.js:19
throw new Error(message);
^
Error: RetailerAccount may only implement Interface types, it cannot implement: [object Object].
at invariant (e:\Development\project-name\node_modules\graphql\jsutils\invariant.js:19:11)
at e:\Development\project-name\node_modules\graphql\type\definition.js:310:29
at Array.forEach (native)
at defineInterfaces (e:\Development\project-name\node_modules\graphql\type\definition.js:309:14)
at GraphQLObjectType.getInterfaces (e:\Development\project-name\node_modules\graphql\type\definition.js:288:52)
at typeMapReducer (e:\Development\project-name\node_modules\graphql\type\schema.js:202:23)
at Array.reduce (native)
at typeMapReducer (e:\Development\project-name\node_modules\graphql\type\schema.js:198:34)
at e:\Development\project-name\node_modules\graphql\type\schema.js:216:20
at Array.forEach (native)
at typeMapReducer (e:\Development\project-name\node_modules\graphql\type\schema.js:207:27)
at e:\Development\project-name\node_modules\graphql\type\schema.js:216:20
at Array.forEach (native)
at typeMapReducer (e:\Development\project-name\node_modules\graphql\type\schema.js:207:27)
at Array.reduce (native)
at new GraphQLSchema (e:\Development\project-name\node_modules\graphql\type\schema.js:95:34)
at Object.<anonymous> (e:\Development\project-name\api\config\graphql.config.js:8:14)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
[nodemon] app crashed - waiting for file changes before starting...
This is the code for my Account Interface:
var path = require('path');
var graphql = require('graphql');
// Custom Enum types
var enums = require(path.join(__dirname, 'enums'));
// Custom Scalar types
var scalars = require(path.join(__dirname, 'scalars'));
// Required ObjectTypes for fields
var Phone = require(path.join(__dirname, 'phone.type'));
var Address = require(path.join(__dirname, 'address.type'));
var Order = require(path.join(__dirname, 'order.type'));
var Upload = require(path.join(__dirname, 'upload.type'));
// ObjectTypes that implement this Interface, used for the resolveType() function
var UserAccount = require(path.join(__dirname, 'userAccount.type'));
var RetailerAccount = require(path.join(__dirname, 'retailerAccount.type'));
var WholesalerAccount = require(path.join(__dirname, 'wholesalerAccount.type'));
var ManufacturerAccount = require(path.join(__dirname, 'manufacturerAccount.type'));
var AdminAccount = require(path.join(__dirname, 'adminAccount.type'));
module.exports = new graphql.GraphQLInterfaceType({
name: 'Account',
fields: function() {
return {
_id: {type: new graphql.GraphQLNonNull(graphql.GraphQLID)},
email: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
username: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
password: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
passwordSalt: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
verificationCode: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
isVerified: {type: new graphql.GraphQLNonNull(graphql.GraphQLBoolean)},
mustResetPassword: {type: new graphql.GraphQLNonNull(graphql.GraphQLBoolean)},
dateLastLoggedIn: {type: scalars.Date},
role: {type: new graphql.GraphQLNonNull(enums.AccountRole)},
initials: {type: graphql.GraphQLString},
firstName: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
lastNamePrefix: {type: graphql.GraphQLString},
lastName: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
gender: {type: graphql.GraphQLBoolean},
dateOfBirth: {type: scalars.Date},
phone: {type: new graphql.GraphQLList(new graphql.GraphQLNonNull(Phone))},
address: {type: new graphql.GraphQLList(new graphql.GraphQLNonNull(Address))},
orders: {type: new graphql.GraphQLList(new graphql.GraphQLNonNull(Order))},
uploads: {type: new graphql.GraphQLList(new graphql.GraphQLNonNull(Upload))},
preferredLanguage: {type: new graphql.GraphQLNonNull(enums.Language)},
}
},
resolveType(data) {
switch(data.AccountRole) {
case 'USER' || 0:
return UserAccount;
case 'RETAILER' || 1:
return RetailerAccount;
case 'WHOLESALER' || 2:
return WholesalerAccount;
case 'MANUFACTURER' || 3:
return ManufacturerAccount;
case 'ADMIN' || 4:
return AdminAccount;
default:
return UserAccount;
}
}
});
And this is the code for one of the implementing ObjectTypes (the one that gives the error message currently):
var path = require('path');
var graphql = require('graphql');
// Required Interfaces
var Account = require(path.join(__dirname, 'account.interface'));
var Metadata = require(path.join(__dirname, 'metadata.interface'));
var Name = require(path.join(__dirname, 'name.interface'));
// Required ObjectTypes
var Retailer = require(path.join(__dirname, 'retailer.type'));
var Shop = require(path.join(__dirname, 'shop.type'));
module.exports = new graphql.GraphQLObjectType({
name: 'RetailerAccount',
interfaces: [Account, Metadata, Name],
fields: function() {
return {
retailer: {type: new graphql.GraphQLNonNull(Retailer)},
shops: {type: new graphql.GraphQLList(new graphql.GraphQLNonNull(Shop))}
}
}
});
The strange thing is that the other interfaces that these Account types implement seem to be working without any problems. I don't get this error message anymore when I remove the Account interface. I've been looking over the syntax, trying to find differences with Name or Metadata. I've also been making changes (and reverting them) to the resolveType() function, the AccountRole enum, and the Interface and the ObjectType themselves, but that doesn't help either.
I'm using the following software/plugin/module versions:
Some pages that I used while trying to figure out what was wrong with my code (I wasn't able to fix my issue with any of these):
I've been unable to solve this problem on my own for a while now. I would be very grateful if someone here could help me out!
Hey @thomasdingemanse - I ran into a similar issue, and spent the past few hours debugging.
The issue ended up being that I needed to include my implementations of an interface in the same file. So in your case, this would mean including the RetailerAccount and ShopAccount in the same file and exporting it as such module.exports = { RetailerAccount, ShopAccount }.
I think the issue is based on how the thunks from @leebyron 's solution in #338 are parsed and/or resolved in the RootQueryType. I saw that when I switched the ordering of the require statements in my RootQuery file, the Type throwing the error would also switch.
Thomas, in your case, if you switched the order for require('retailer-account') and require('shop-account') in your RootQuery file, you may find the same problem where ShopAccount is now throwing the error.
It looks like our error messages for this could be much much better. The issue is that if an Object type claims to implement an Interface type, then it must define at least all of the fields that the Interface type requires.
In your code it's clear that the RetailerAccount type claims to implement Account, however there are numerous fields required by the Interface type Account that RetailerAccount does not define.
Also, unrelated but noticed in your code:
password: {type: new graphql.GraphQLNonNull(graphql.GraphQLString)},
This seems like a huge security issue that passwords may be stored in plaintext somewhere on your system, it's a further security issue that those plaintext passwords could potentially be accessible over your API. Please use caution with passwords and never store them in the clear!
@michaelolo24 Thanks for the detailed reply! This helped me a lot.
@leebyron Thanks for the explanation, I thought these properties were actually inherited instead of defined as a set of required fields for implementing object types. But I guess it makes sense, especially when looking at the available examples (even though doing this makes for a lot of duplicate code).
Thanks for the concern about our security, but the passwords are hashed and they are never saved in plain text.
Most helpful comment
It looks like our error messages for this could be much much better. The issue is that if an Object type claims to implement an Interface type, then it must define at least all of the fields that the Interface type requires.
In your code it's clear that the
RetailerAccounttype claims to implementAccount, however there are numerous fields required by the Interface typeAccountthatRetailerAccountdoes not define.