A. L
A. L

Reputation: 12639

graphqlj-js union type, access args

I'm trying to figure out how to access the args field of retrieveRowsWithoutLocations. However, I'm not even sure what goes into the value parameter in the resolveType function of UnionType.

I'm looking at the documentation https://graphql.org/graphql-js/type/#graphqluniontype but it's quite brief and doesn't go into detail where that information is picked up from. I've tried looking at other sources but they aren't graphql-js.

What I'm trying to do is access args.type and check its value, which then allows the union to decide which type it should return.

let rows_union =
    new graphql.GraphQLUnionType({
        name: 
            "rows_union",
        types: 
        [
            many_item,
            many_list,
            many_display_list, 
        ],
        resolveType(value) 
        {
            switch(value)
            {
                case "item":
                    return many_item
                case "list":
                    return many_list
                case "display_list":
                    return many_display_list
            }
        }
    })

// Define the Query type
var query =
    new graphql.GraphQLObjectType({
        name: "Query",
        fields:
        {
            retrieveRowsWithoutLocations:
            {
                type:
                    rows_union,
                args:
                {
                    _id:
                    {
                        type:
                            nonNullId()
                    },
                    page:
                    {
                        type:
                            nonNullInt()
                    },
                    page_length:
                    {
                        type:
                            nonNullInt()
                    },
                    type:
                    {
                        type:
                            nonNullString()
                    },
                },
                async resolve ()
                {

                }
            },
        }
    })


let many_type =
    new graphql.GraphQLObjectType({
        name: "many_"+name,
        fields: {
            results: {
                type: graphql.GraphQLList(type)
            },
            total: {
                type: graphql.GraphQLNonNull(graphql.GraphQLInt)
            },
        }
    })

type is another ObjectType

Upvotes: 1

Views: 424

Answers (1)

Daniel Rearden
Daniel Rearden

Reputation: 84687

You cannot directly access any resolver parameters inside resolveType (or isTypeOf). When a field is resolved, the resolver returns some value, or a Promise that will resolve to that value. In the case of a field that returns an output type, interface or union, this value should be a JavaScript object. It's this value that is then passed to resolveType, which is used to determine what type is actually being returned in the response at runtime.

Given a schema like

union Animal = Bird | Fish

type Bird {
  numberOfWings: Int
}

type Fish {
  numberOfFins: Int
}

type Query {
  animal: Animal
}

you can image that the resolver for the animal field returns JavaScript objects like { numberOfWings: 2 } { numberOfFins: 4 }. Here, we could utilize a simple heuristic to determine the type:

resolveType: (value) => {
  if (value.numberOfWings !== undefined) {
    return 'Bird'
  } else if (value.numberOfFins !== undefined) {
    return 'Fish'
  }
  throw new TypeError(`Unable to resolve type for Animal with value: ${value}`)
}

If instead of returning simple objects, we return instances of specific classes, we can do even better:

resolveType: (value) => {
  if (value instanceof BirdModel) {
    return 'Bird'
  } else if (value instanceof FishModel) {
    return 'Fish'
  }
  throw new TypeError(`Unable to resolve type for Animal with value: ${value}`)
}

Whatever our conditional logic looks like, just remember that we're always just testing the value being returned by the resolver, whatever that happens to be.

Things get a bit trickier if you're not using classes and two or more of the types share the same structure. Or, as in your case, when the distinguishing property (results) is an array, since inspecting one of the elements is a no-go. Imagine our union instead looks like this:

union Animal = Cat | Dog

type Cat {
  numberOfPaws: Int
}

type Dog {
  numberOfPaws: Int
}

Here, unfortunately, we have to rely on our resolver to provide some additional information. For example, we could return some arbitrary field to identify the type:

// Resolver for animal field
resolve: () => {
  return {
    numberOfPaws: 4,
    kind: 'Dog',
  }
}

// Union
resolveType: (value) => {
  return value.kind
}

But we can actually do one better by relying on the default implementations of resolveType and isTypeOf:

resolve: () => {
  return {
    numberOfPaws: 4,
    __typename: 'Dog',
  }
}

By explicitly returning the __typename like this, we can actually omit defining resolveType altogether. However, keep in mind that, again, this creates a dependency on the resolver. Where possible, you should probably favor using resolveType with instanceof checks instead to decouple resolveType from your resolver logic.

Upvotes: 1

Related Questions