Is there a technique that would let me declare ADT like this in GraphQL?
// TypeScript
type SomeAlgebraicDataType =
| { state: 'A', subState1: string }
| { state: 'B', subState2: string, subState3: number }
| { state: 'C', subState4: string, subState5: string, subState6: number }
Note how based on the state
discriminator, the rest of the structure can be inferred.
Here's some pseudo code that illustrates the idea:
union AlgebraicDataType = StateA | StateB | StateC
type StateA {state: StateDiscriminator.A, subState1: String }
type StateB {state: StateDiscriminator.B, subState2: String, subState3: Int }
type StateC {state: StateDiscriminator.C, subState4: String, subState5: String, subState6: Int}
enum StateDiscriminator { A B C }
According to the spec:
GraphQL Unions represent an object that could be one of a list of GraphQL Object types, but provides for no guaranteed fields between those types. They also differ from interfaces in that Object types declare what interfaces they implement, but are not aware of what unions contain them.
Unions (and interfaces) are referred to in the spec as abstract types because the concrete (i.e. actual) type of a field that returns a Union is not known until runtime. However, Unions also fit the general definition of an algebraic type because they are effectively the sum of two or more object types.
The pseudocode in the question is not far from a working example:
union AlgebraicDataType = StateA | StateB | StateC
type StateA {state: StateDiscriminator, subState1: String }
type StateB {state: StateDiscriminator, subState2: String, subState3: Int }
type StateC {state: StateDiscriminator, subState4: String, subState5: String, subState6: Int}
enum StateDiscriminator { A B C }
The main difference is that when we use SDL to define a schema, we have to specify how the Union is resolved separately from our type definitions. If you're using apollo-server
or makeExecutableSchema
from graphql-tools
, we specify this logic as part of our resolver map:
const resolvers = {
AlgebraicDataType: {
__resolveType: (obj) => {
switch (obj.state) {
case 'A': return 'StateA'
case 'B': return 'StateB'
case 'C': return 'StateC'
default: {
throw new TypeError(
`Unknown state for AlgebraicDataType: ${obj.state}`
)
}
}
}
}
}
If you're using vanilla GraphQL.js, the same function is provided to the Union's constructor as the resolveType
parameter.
It's also worth noting that if the resolveType
function is not provided, by default GraphQL will look for a property named __typename
on the provided object and use that to resolve the type. So it's possible to omit the function altogether as long as you return an object with that property inside your resolver.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With