简体   繁体   中英

GraphQL.js - use Interface as a default (fallback) type in resolveType function

I'm trying to return generic type in resolveType function if none of the provided types is matched. The example below shows this problem: API worked like a charm supporting UserType and MovieType until in database was added BookType (not supported by GraphQL schema).

const {
  graphql,
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLString,
  GraphQLNonNull,
  GraphQLList,
  GraphQLInterfaceType
} = require("graphql");

const DATA = [
  {
    // UserType
    name: "catherine woolf",
    nick: "catherine"
  },
  {
    // MovieType
    name: "cat woman",
    director: "Jack Wolfgang"
  },
  {
    // --- missing type --- (BookType)
    name: "cats secrets",
    author: "Nicky Glace"
  }
];

const resolveType = data => {
  if (data.nick) {
    return UserType;
  }
  if (data.director) {
    return MovieType;
  }
};

const SearchableType = new GraphQLInterfaceType({
  name: "Searchable",
  fields: {
    name: { type: GraphQLString }
  },
  resolveType: resolveType
});

const UserType = new GraphQLObjectType({
  name: "User",
  interfaces: [SearchableType],
  fields: {
    name: { type: GraphQLString },
    nick: { type: GraphQLString }
  }
});

const MovieType = new GraphQLObjectType({
  name: "Movie",
  interfaces: [SearchableType],
  fields: {
    name: { type: GraphQLString },
    director: { type: GraphQLString }
  }
});

const schema = new GraphQLSchema({
  types: [MovieType, UserType, SearchableType],
  query: new GraphQLObjectType({
    name: "RootQueryType",
    fields: {
      search: {
        type: new GraphQLList(SearchableType),
        args: {
          text: { type: new GraphQLNonNull(GraphQLString) }
        },
        resolve(_, { text }) {
          return DATA.filter(d => d.name.indexOf(text) !== -1);
        }
      }
    }
  })
});

const query = `
  {
    search(text: "cat") {
      name
      ... on User {
        nick
      }
      ... on Movie {
        director
      }
    }
  }
`;

graphql(schema, query).then(result => {
  console.log(JSON.stringify(result, null, 2));
});

So now this code ends with error:

"Abstract type Searchable must resolve to an Object type at runtime for field RootQueryType.search with value \\"[object Object]\\", received \\"undefined\\". Either the Searchable type should provide a \\"resolveType\\" function or each possible types should provide an \\"isTypeOf\\" function."

This is nothing surprising since currently resolveType may not return any type.

Workaround

Crate type containing the same field like interface SearchableType (1 to 1 implementation):

const _SearchableType = new GraphQLObjectType({
  name: '_Searchable',
  interfaces: [SearchableType],
  fields: {
    name: { type: GraphQLString },
  }
});

Use it as a fallback type:

const resolveType = data => {
  if (data.nick) {
    return UserType;
  }
  if (data.director) {
    return MovieType;
  }
  return _SearchableType;
};

And add it to types in schema definition:

types: [MovieType, UserType, SearchableType, _SearchableType],

But the problem with this solution is presence of this dummy _SearchableType in documentation like this:

文档中有问题的_SearchableType

Question

Is there any way to return interface SearchableType or equivalent of it in resolveType ? The key point for me is hiding of this "fallback type" in a documentation.

GraphQL is strongly typed and doesn't support generics or some kind of "fallback" mechanism when resolving unions and interfaces. At the end of the day, if your underlying data layer is returning some type that you have not yet implemented in your schema, the simplest solution is to simply add that type to your schema. Migrations to your database and changes to your schema should go hand-in-hand.

If you want to derive your schema from your storage layer, I would suggest looking into something like PostGraphile (formerly PostGraphQL).

That said, if you're bent on using a workaround, you could just fallback to one of the existing types:

const resolveType = data => {
  if (data.nick) {
    return UserType
  }
  return MovieType
}

Now a book's name will still be accessible, provided you query it on the interface and not one of the types. The only downside to this approach is that movie-specific fields will be returned for a book and will resolve to null, but that won't cause any issues unless they're specifically defined as non-null in your schema.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM