Unions
You define a union by defining a variant with payloads of types tagged with @gql.type
, and annotate that variant with @gql.union
:
@gql.type
type user = {
@gql.field name: string,
@gql.field age: int
}
@gql.type
type group = {
@gql.field displayName: string,
}
@gql.union
type entity = User(user) | Group(group)
type User {
name: String!
age: Int!
}
type Group {
displayName: String!
}
union Entity = User | Group
Each variant case can be called whatever you want it to (although it's good practice to follow the name of the GraphQL type it holds), but rememeber that the payload of each union variant case must be one of:
- Exactly 1 type that has a
@gql.type
annotation. - An inline record.
Don't worry, ResGraph will complain if you try anything else.
You can add comments on the union type definition itself, as well as each variant case, and they'll end up in the schema.
Inline records for defining union members
You can create ad hoc "synthetic" object types for your unions by using an inline record as the payload for a variant case in union. Let's look at an example:
@gql.union
type setPasswordPayload = Ok({affectedUser: user}) | Failed({reason: string})
This defines a variant called setPasswordPayload
with two cases - Ok
and Failed
. Each of those cases also has additional fields through an inline record. The above will generate the following GraphQL:
union SetPasswordPayload = SetPasswordPayloadOk | SetPasswordPayloadFailed
type SetPasswordPayloadFailed {
reason: String!
}
type SetPasswordPayloadOk {
affectedUser: User!
}
Notice a few things:
- Each inline record has been synthesized into an actual GraphQL type holding all fields that were defined in that inline record.
- The synthetiszed GraphQL types are called
<unionName><caseName>
.Ok
therefore becomesSetPasswordPayloadOk
.
This is intended to be a quick way to define one-off GraphQL types only intended to be used in a specific enum, like how you'd typically design a result from a mutation.
Using unions in the schema
Unions can be used as the type for fields on GraphQL objects or interfaces. A simple example:
@gql.type
type user = {
@gql.field name: string,
@gql.field age: int
}
@gql.type
type group = {
@gql.field displayName: string,
}
@gql.union
type entity = User(user) | Group(group)
@gql.field
let entity = async (_: query, ~entityId, ~ctx: ResGraphContext.context): option<entity> => {
switch decodeEntityId(entityId) {
| Some(#User, id) =>
switch await ctx.dataLoaders.userById(~userId=id) {
| None => None
| Some(user) => Some(User(user))
}
| Some(#Group, id) =>
switch await ctx.dataLoaders.groupById(~groupId=id) {
| None => None
| Some(group) => Some(Group(group))
}
| _ => None
}
}
type User {
name: String!
age: Int!
}
type Group {
displayName: String!
}
union Entity = User | Group
type Query {
entity(entityId: String!): Entity
}
Now that we've covered unions, we can move on to interfaces.