apollographql / apollo-server

🌍  Spec-compliant and production ready JavaScript GraphQL server that lets you develop in a schema-first way. Built for Express, Connect, Hapi, Koa, and more.
https://www.apollographql.com/docs/apollo-server/
MIT License
13.8k stars 2.03k forks source link

Error Abstract type Search must resolve to an Object type at runtime for field Query.search #3352

Closed ChrisMichaelPerezSantiago closed 4 years ago

ChrisMichaelPerezSantiago commented 5 years ago

helo friends,

Someone who can help me solve a problem in Apollo Graphql. I have implemented a search query that receives a parameter that is the title to search, the problem I have is that it shows me the following error:

"message": "Abstract type Search must resolve to an Object type at runtime for field Query.search with value { id: \"avatar\", title: \"Avatar\", poster: \"https://pedropolis.tv/wp-content/uploads/2009/12/Avatar-150x150.jpg\", sinopsis: \"Jake Sully, un ex-marine confinado a una silla de ruedas, es reclutado para viajar al planeta Pandora, donde un consorcio corporativo está extrayendo un mineral que será clave en la solución de ...\", type: \"pelicula\", extra: [[Object]] }, received \"undefined\". Either the Search type should provide a \"resolveType\" function or each possible type should provide an \"isTypeOf\" function.",

_The return type of the search is a union of the type Series | Movies_

❤️ Any help will be more than received, I've been trying to solve the problem for a couple of weeks.

Query

const resolvers = require('./resolvers.js');
const {gql} = require('apollo-server');

const typeDefs = gql `
  extend type Query{
    search(query: String!): [Search!]!
  }

  union Search = Series | Movies

  type Series{
    id: String!
    title: String!
    sinopsis: String!
    poster: String!
    rating: String!
    year: String!
    extra: [SerieExtra!]!
  }

  type Movies{
    id: String!
    title: String!
    sinopsis: String!
    poster: String!
    rating: String!
    quality: String!
    year: String!
    extra: [MovieExtra!]!
  }

  type Episodes{
    id: String!
    title: String!
    episode_name: String!
    poster: String!
    date: String!
    quality: String!
    sinopsis: String!
  }

  type VideoIframe{
    iframe: Iframe
  }

  type Iframe{
    option: Int 
    video_iframe: String
  }

  type SerieExtra {
    channel: String!
    first_air_date: String!
    last_air_date: String!
    total_seasons: String!
    total_episodes: String
    season_list: [SeasonList!] !
    cast_members: CastMembers!
    similar_series: [SimilarSeries!] !
  }

  type MovieExtra {
    air_date: String!
    country: String!
    runtime: String!
    rated: String!
    cast_members: CastMembers!
    similar_movies: [SimilarMovies!] !
  }

// The information of the type SeasonList, CastMembers, SimilarSeries, SimilarMovies
// It was omitted since it would be a lot of information in the template .. for this discussion.
`;

module.exports = {
  typeDefs,
  resolvers
}

resolvers

const resolvers = { 
 Search:{
    __resolveType(obj , context , info){
      // .....
      return null;
    }
  },
  Query:{ 
    search: async(_source , {query} , { dataSources}) =>{
    return dataSources.API.search(query)
        .then(doc => {
          console.log(doc.content) // It correctly shows me the query data.
          return doc.content;
        });
     },
  }
};

Playground

query{
    search(query: "avatar"){
      ... on Series{
        id
    }
      ... on Movies{
        id
    }
  }
}

Error

{
  "errors": [
    {
      "message": "Abstract type Search must resolve to an Object type at runtime for field Query.search with value { id: \"avatar\", title: \"Avatar\", poster: \"https://pedropolis.tv/wp-content/uploads/2009/12/Avatar-150x150.jpg\", sinopsis: \"Jake Sully, un ex-marine confinado a una silla de ruedas, es reclutado para viajar al planeta Pandora, donde un consorcio corporativo está extrayendo un mineral que será clave en la solución de ...\", type: \"pelicula\", extra: [[Object]] }, received \"undefined\". Either the Search type should provide a \"resolveType\" function or each possible type should provide an \"isTypeOf\" function.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "search",
        0
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "message": "Abstract type Search must resolve to an Object type at runtime for field Query.search with value { id: \"avatar\", title: \"Avatar\", poster: \"https://pedropolis.tv/wp-content/uploads/2009/12/Avatar-150x150.jpg\", sinopsis: \"Jake Sully, un ex-marine confinado a una silla de ruedas, es reclutado para viajar al planeta Pandora, donde un consorcio corporativo está extrayendo un mineral que será clave en la solución de ...\", type: \"pelicula\", extra: [[Object]] }, received \"undefined\". Either the Search type should provide a \"resolveType\" function or each possible type should provide an \"isTypeOf\" function.",
          "locations": [
            {
              "line": 2,
              "column": 3
            }
          ],
          "stacktrace": [
            "GraphQLError: Abstract type Search must resolve to an Object type at runtime for field Query.search with value { id: \"avatar\", title: \"Avatar\", poster: \"https://pedropolis.tv/wp-content/uploads/2009/12/Avatar-150x150.jpg\", sinopsis: \"Jake Sully, un ex-marine confinado a una silla de ruedas, es reclutado para viajar al planeta Pandora, donde un consorcio corporativo está extrayendo un mineral que será clave en la solución de ...\", type: \"pelicula\", extra: [[Object]] }, received \"undefined\". Either the Search type should provide a \"resolveType\" function or each possible type should provide an \"isTypeOf\" function.",
            "    at ensureValidRuntimeType (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:667:11)",
            "    at completeAbstractValue (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:660:42)",
            "    at completeValue (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:585:12)",
            "    at completeValue (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:557:21)",
            "    at completeValueCatchingError (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:495:19)",
            "    at C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:618:25",
            "    at Array.forEach (<anonymous>)",
            "    at forEach (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\iterall\\index.js:83:25)",
            "    at completeListValue (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:614:24)",
            "    at completeValue (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:573:12)"
          ]
        }
      }
    }
  ],
  "data": null
}
luna215 commented 5 years ago

I'm getting a similar error. My guess is it has to do with the way I am stitching my schema. I only say this because I made a smaller version of my union example in a separate file where I keep my resolvers and type defs in the same file and it seemed to work.

Have you tried putting your type definitions and resolvers in the same file? I'm surprised more people haven't encountered this error.

baldmountain commented 5 years ago

I don't think you can just return null from your __resolveType method. I think you need to actually figure out if it is a Series or Movie and return that instead of null. See this page: https://www.apollographql.com/docs/apollo-server/schema/unions-interfaces/#union-type

luna215 commented 5 years ago

I think I might have solved this issue. Inside your __resolveType make sure you're returning a string value that matches your union type.

For example, if your resolveType decides a Series type then make sure your resolveType returns 'Series'. Does that make sense?

ChrisMichaelPerezSantiago commented 5 years ago

Hello @luna215 and friends,

I still can't figure out the problem at all. Here I leave the changes I have made and the new error that shows me. Apparently I'm close to getting the results ..

Playground

query{
  search(query: "elite"){
    ... on SMovies{
      id
      title
      poster
      sinopsis
      type
      extra{
        air_date
      }
    }
    ... on SSeries{
      id
      title
      poster
      sinopsis
      type
      extra{
        channel
      }
    }
  }
}

resolvers.js

const resolvers = {
  Search:{
    __solverType(obj , context , info){
      if(obj.type === 'pelicula') return SMovies
      if(obj.type === 'serie') return SSeries    
    }
  },

  SMovies:{
    __isTypeOf(obj , context , info){
      return Object.prototype.hasOwnProperty.call(obj, 'id');
    }
  },

  SSeries:{
    __isTypeOf(obj , context , info){
      return Object.prototype.hasOwnProperty.call(obj, 'id');
    }
  },

  MovieExtra:{
    __isTypeOf(obj , context , info){
      return Object.prototype.hasOwnProperty.call(obj , 'air_date')
    }
  },

  SerieExtra:{
    __isTypeOf(obj , context , info){
      return Object.prototype.hasOwnProperty.call(obj , 'channel') 
    }
  },

  Query: {
    search: async(_source , {query} , { dataSources}) =>{
      return dataSources.API.search(query)
        .then(doc => {
          return doc.content;
        })
    },
  }
};

module.exports = resolvers;

error

{
  "errors": [
    {
      "message": "Expected value of type \"MovieExtra\" but got: { channel: \"Netflix\", first_air_date: \" First air date Oct. 05, 2018\", last_air_date: \" Última transmisión Oct. 05, 2018\", total_seasons: \"1\", total_episodes: \"8\", season_list: [[Object], [Object]], cast_members: { creator: [Object], members_list: [Array] }, similar_series: [[Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], ... 2 more items] }.",
      "locations": [
        {
          "line": 9,
          "column": 7
        }
      ],
      "path": [
        "search",
        0,
        "extra",
        0
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "message": "Expected value of type \"MovieExtra\" but got: { channel: \"Netflix\", first_air_date: \" First air date Oct. 05, 2018\", last_air_date: \" Última transmisión Oct. 05, 2018\", total_seasons: \"1\", total_episodes: \"8\", season_list: [[Object], [Object]], cast_members: { creator: [Object], members_list: [Array] }, similar_series: [[Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], ... 2 more items] }.",
          "locations": [
            {
              "line": 9,
              "column": 7
            }
          ],
          "stacktrace": [
            "GraphQLError: Expected value of type \"MovieExtra\" but got: { channel: \"Netflix\", first_air_date: \" First air date Oct. 05, 2018\", last_air_date: \" Última transmisión Oct. 05, 2018\", total_seasons: \"1\", total_episodes: \"8\", season_list: [[Object], [Object]], cast_members: { creator: [Object], members_list: [Array] }, similar_series: [[Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], ... 2 more items] }.",
            "    at invalidReturnTypeError (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:707:10)",
            "    at completeObjectValue (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:699:13)",
            "    at completeValue (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:591:12)",
            "    at completeValue (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:557:21)",
            "    at completeValueCatchingError (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:495:19)",
            "    at C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:618:25",
            "    at Array.forEach (<anonymous>)",
            "    at forEach (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\iterall\\index.js:83:25)",
            "    at completeListValue (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:614:24)",
            "    at completeValue (C:\\Users\\c\\Desktop\\cinemanight-graphql\\node_modules\\graphql\\execution\\execute.js:573:12)"
          ]
        }
      }
    }
  ],
  "data": null
}
luna215 commented 5 years ago

Is __solverType similar to __resolveType?

Try changing your code for __solverType to:

__solverType(obj , context , info){ if(obj.type === 'pelicula') return 'SMovies' if(obj.type === 'serie') return 'SSeries' }

See if that works.

ChrisMichaelPerezSantiago commented 5 years ago

Hello @luna215,

Sorry, I made the change from __solverType to __resolveType, it was a syntax error. But even so, it shows me the same error made in the previous dialogue.

But something that I noticed

 console.log ('obj:', obj)

It doesn't show me anything, it's as if resolver Search didn't do anything.

Resolvers

  Search:{
    __resolveType(obj , context , info){
      console.log('obj: ' , obj)
      if(obj.type === 'pelicula') return 'SMovies'
      if(obj.type === 'serie') return 'SSeries'    
    }
  },
seyedasfar commented 4 years ago

When using unions, you will have to return a __typename so apollo-server knows which type the result has and which resolver map must be used for resolving further field values of the resolved type.

search query must be something like below

Query: {
  search: async (_source, { query }, { dataSources }) => {
    const data = dataSources.API.search(query)

    if (data.name === 'Series') return {
      __typename: 'Series',
      ...data
    }

    if (data.name === 'Movies') {
      __typename: 'Movies',
      ...data
    }
  }
}
MarshHawk commented 4 years ago

Simplest (fewest extra lines of code) solution follows:

const resolvers = {
  Search:{
    __resolveType(parent) {
    return parent.constructor.name
  }
},

  SMovies:{
    ...
  },

  SSeries:{
    ...
  },

  MovieExtra:{
    ...
  },

  SerieExtra:{
    ...
  },

  Query: {
    search: async(_source , {query} , { dataSources}) =>{
      return dataSources.API.search(query)
        .then(doc => {
          return doc.content;
        })
    },
  }
};

module.exports = resolvers;

No Need for __typename: or _isTypeOf:

m-lautenbach commented 4 years ago

For us somehow only __typename worked, but __resolveType didn't (even when returning a static string; using graphql-tools)

m-lautenbach commented 4 years ago

works:

        Boq: () => ({
          elements: () => [
            {
              priceEstimate: (): BoqPositionPriceEstimate => ({
                min: 2000,
                max: 7000,
              }),
            },
          ],
        }),
        // force line items to be always be positions,
        //  otherwise we randomly might not get any and the test fails
        BoqLineItem: () => ({
          __typename: 'BoqPosition',
        }),

doesn't work

        Boq: () => ({
          elements: () => [
            {
              priceEstimate: (): BoqPositionPriceEstimate => ({
                min: 2000,
                max: 7000,
              }),
            },
          ],
        }),
        // force line items to be always be positions,
        //  otherwise we randomly might not get any and the test fails
        BoqLineItem: () => ({
          __resolveType: () => 'BoqPosition',
        }),

(we wanted to force one type on an array of union typed elements)