0

I have an Apollo GraphQL service that delegates to an internal gRPC service. This service has an endpoint which returns a message that contains a oneof, which I'm mapping to a Union in GraphQL.

This is straightforward, but there's a fair degree of boilerplate involved when implementing the resolvers. Suppose I have the following protobuf message definition:

message MyUnionMessage {
  oneof value {
    UnionType1 type1 = 1;
    UnionType1 type2 = 3;
    UnionType1 type3 = 4;
  }
}
message UnionType1 {<type 1 props>}
message UnionType2 {<type 2 props>}
message UnionType3 {<type 3 props>}

My corresponding GraphQL schema looks something like this:

union MyUnionType = UnionType1 | UnionType2 | UnionType3
type UnionType1 {<type 1 props>}
type UnionType1 {<type 2 props>}
type UnionType1 {<type 3 props>}

In the javascript binding for gRPC, a MyUnionMessage object will have two properties: value which is a string indicating which type of value is contained, and a property named for the type. So, if I had a MyUnionMessage containing a UnionType2, for example, the object would look like this:

{
  value: 'type2',
  type2: {...}
}

This is nice for implementing __resolveType, since I can do a simple switch on the value in value, but I then have to write a resolver for all of the fields of all of the concrete types.

What I'm looking for is to be able to so something like this:

resolvers = {
  MyUnionType: {
    __resolveType(obj) {
      switch(obj.value) {
      case 'type1': return 'UnionType1';
      case 'type2': return 'UnionType2';
      case 'type3': return 'UnionType3';
      default: return null;
    },
    __resolveValue(obj) {
      return obj[obj.value];
    },
  },
};

Basically, I want to write a "resolver" at the level of the generic union (or interface) type that transforms the object before it's passed to the concrete resolver.

Is such a thing possible?

Kris Pruden
  • 3,280
  • 4
  • 25
  • 30

1 Answers1

0

I'd wager that this sort of scenario is typically solved by transforming the data before it hits the __resolveType logic. For example, say you had a Query field that returned a list of MyUnionType. Your resolver for that field might look something like:

function resolve (arr) {
  return arr.map(obj => {
    return {
      ...obj[obj.value]
      type: obj.value // or whatever field name that won't cause a collision
    }
  })
}

You then switch on type inside of __resolveType and you're good to go. Of course, that means if you have multiple fields that return a MyUnionType, you'll want to extract that logic into a utility function that can be used by each resolver.

I don't think there's not really a way to do what you're trying to do with the existing API. You could, of course, do something like this:

const getUnionType(obj) {
  switch(obj.value) {
    case 'type1': return 'UnionType1';
    case 'type2': return 'UnionType2';
    case 'type3': return 'UnionType3';
    default: {
      throw new Error(`Unrecognized type ${obj.value}`)
    }
  }
}

const resolvers = {
  MyUnionType: {
    __resolveType(obj) {
      const type = getUnionType(obj)
      Object.assign(obj, obj[obj.value])
      return type
    },
  },
};

This works, but keep in mind it is a bit fragile since it assumes resolveType will always get the same root value as the resolve function, which could hypothetically change in the future.

Daniel Rearden
  • 80,636
  • 11
  • 185
  • 183
  • I suspected as much. The `Object.assign` trick does work, so I'm going to go with that for now. If the apollo implementation changes at some point such that this no longer works, I'll deal with it then :) Thanks! – Kris Pruden Jan 11 '19 at 00:02