Apollo-server: Resolving a list of Unions

Created on 2 Nov 2016  路  6Comments  路  Source: apollographql/apollo-server

Hi @helfer @stubailo ,

I just found out how to implement a Resolver for a single Union (#172), but how about resolving a list of Unions?

In relation to the example provided in #172: how would I resolve a list of Characters? ([Character]!)

For me this is a practical issue. I am using this in a real-life case with an AvailableSearchFilters Union List, which can contain for example a GeoPointDistanceFilter and a PriceRangeFilter.

Of course I'm willing to update the docs if you can point me to the solution.

Kind regards,

Sander

Most helpful comment

Phew... I've found the issue.

Do not name array elements like this, as _named array elements seem not to be passed to the resolver_:

// WRONG!
[ offerCategoryFilter: { id: '6ca46120-8a42-11e6-8692-553a5a80fc35' },
  geoBoundingBoxFilter: { topLeft: { lat: 86.5, lon: 86.5 },
    bottomRight: { lat: 5.5, lon: 5.5 } },
  geoDistanceFilter: { location: { lat: 5.5, lon: 5.5 },
    distance: 15000,
    units: 'km' },
  priceRangeFilter: { min: 6, max: 44 } ]
// END-WRONG!

Instead, use a default array like this, you can pass the ofType to resolve the Union type:

[ { id: '6ca46120-8a42-11e6-8692-553a5a80fc35',
    ofType: 'offerCategoryFilter' },
  { topLeft: { lat: 86.5, lon: 86.5 },
    bottomRight: { lat: 5.5, lon: 5.5 },
    ofType: 'geoBoundingBoxFilter' },
  { location: { lat: 5.5, lon: 5.5 },
    distance: 15000,
    units: 'km',
    ofType: 'geoDistanceFilter' },
  { min: 6, max: 44, ofType: 'priceRangeFilter' } ]

Now you can resolve the union type like this:

SearchFilterUnion: {
    __resolveType(root:Object, context:Object, info:Object) {
      if( root.ofType === 'offerCategoryFilter') {
        return 'OfferCategorySearchFilter'
      }
      if( root.ofType === 'geoBoundingBoxFilter') {
        return 'GeoBoundingBoxSearchFilter'
      }
      if( root.ofType === 'geoDistanceFilter') {
        return 'GeoDistanceSearchFilter'
      }
      if( root.ofType === 'priceRangeFilter') {
        return 'PriceRangeSearchFilter'
      }
      return null
    }
  },

All 6 comments

Just return a list where each item implements one of the union types. Or return a list of promises that resolve to one of those types. I believe the resolveType method will still apply to each individual item, but if that doesn't work, you can try defining ofType on each of the implementing types.

Hi @helfer,

Thanks for pointing that out.

I got it to work with a list of interfaces using __resolveType, but not with the list of unions.

I cannot find any documentation on ofType. Can you give me an example, or point me to the documentation?

Kind regards,

Sander

Edit: using "graphql": "^0.7.2", "graphql-tools": "^0.7.2"

Addendum
So when resolving a list of NodeInterfaces, this works:

  NodeInterface: {
    __resolveType(root:Object, context:Object, info:Object) {
      if(root._graphQLType === 'Offer'){
        return 'Offer';
      }

      return null;
    },
  }, 

But when resolving a list of SearchFilterUnions, the specific resolver isn't called. The array is available on the parent resolver though.

SearchFilterUnion: {
    __resolveType(root:Object, context:Object, info:Object) {
    console.log('\n\nSEARCHFILTERUNION: ', root) // TODO remove
      if( root.offerCategoryFilter) {
        return 'OfferCategorySearchFilter'
      }
      if( root.geoBoundingBoxFilter) {
        return 'GeoBoundingBoxSearchFilter'
      }
      if( root.geoDistanceFilter) {
        return 'GeoDistanceSearchFilter'
      }
      if( root.priceRangeFilter) {
        return 'PriceRangeSearchFilter'
      }
      return null
    }
  },

Example array logged from parent:

[ offerCategoryFilter: { id: '6ca46120-8a42-11e6-8692-553a5a80fc35' },
  geoBoundingBoxFilter: { topLeft: { lat: 86.5, lon: 86.5 },
    bottomRight: { lat: 5.5, lon: 5.5 } },
  geoDistanceFilter: { location: { lat: 5.5, lon: 5.5 },
    distance: 15000,
    units: 'km' },
  priceRangeFilter: { min: 6, max: 44 } ]

Any clue?

Phew... I've found the issue.

Do not name array elements like this, as _named array elements seem not to be passed to the resolver_:

// WRONG!
[ offerCategoryFilter: { id: '6ca46120-8a42-11e6-8692-553a5a80fc35' },
  geoBoundingBoxFilter: { topLeft: { lat: 86.5, lon: 86.5 },
    bottomRight: { lat: 5.5, lon: 5.5 } },
  geoDistanceFilter: { location: { lat: 5.5, lon: 5.5 },
    distance: 15000,
    units: 'km' },
  priceRangeFilter: { min: 6, max: 44 } ]
// END-WRONG!

Instead, use a default array like this, you can pass the ofType to resolve the Union type:

[ { id: '6ca46120-8a42-11e6-8692-553a5a80fc35',
    ofType: 'offerCategoryFilter' },
  { topLeft: { lat: 86.5, lon: 86.5 },
    bottomRight: { lat: 5.5, lon: 5.5 },
    ofType: 'geoBoundingBoxFilter' },
  { location: { lat: 5.5, lon: 5.5 },
    distance: 15000,
    units: 'km',
    ofType: 'geoDistanceFilter' },
  { min: 6, max: 44, ofType: 'priceRangeFilter' } ]

Now you can resolve the union type like this:

SearchFilterUnion: {
    __resolveType(root:Object, context:Object, info:Object) {
      if( root.ofType === 'offerCategoryFilter') {
        return 'OfferCategorySearchFilter'
      }
      if( root.ofType === 'geoBoundingBoxFilter') {
        return 'GeoBoundingBoxSearchFilter'
      }
      if( root.ofType === 'geoDistanceFilter') {
        return 'GeoDistanceSearchFilter'
      }
      if( root.ofType === 'priceRangeFilter') {
        return 'PriceRangeSearchFilter'
      }
      return null
    }
  },

I was surprised the resolver skips named array elements. Is this intended behaviour? Took me a while to figure this out :)

@sandervanhooft that sounds like something that would need to be fixed in GraphQL.js - this package doesn't deal with execution at all. However, if you have some ideas for where documentation could be improved, please file an issue here: https://github.com/apollostack/tools-docs

Was this page helpful?
0 / 5 - 0 ratings