Handling `extend type` directive
GraphQL spec: 3.6.3 Object Extensions
Currently Caliban can parse it, but cannot generate.
Sometimes it leads to issues when a GraphQL schema containing extend type is provided "as is" and cannot be changed, but has to be complied with.
Therefore it could come in really handy to have a possibility to generate extend type too.
Note that a GraphQL schema can have multiple extend type directives for the same type.
Although the feature could be implemented in many different ways, here's one of the ways I could think of personally.
For example, consider the following schema:
type TheQuery {
one: String
}
# first extension
extend type TheQuery {
two: Int
three: Float
}
# second extension
extend type TheQuery {
four: Boolean
}
One of the way to handle it could be a new annotation, e.g. @GQLExtendType, which would allow to parse/generate the following model:
case class TheQuery(
one: String,
@GQLExtendType // opens "first extension"
two: Int,
three: Float,
@GQLExtendType // opens "second extension"
four: Boolean
)
In other words, @GQLExtendType is applied to a field that opens a new extension group. Each group corresponds to a separate extend type directive in GraphQL. The group continues until the end of the case class or until the next @GQLExtendType is encountered.
I like the simplicity of the proposed approach, but I think it might box us in a bit on this bit:
Object type extensions may choose not to add additional fields, instead only adding interfaces or directives.
If we were to go this route of augmenting the existing schema I'd suggest doing it through a nested component
case class User(
id: ID,
name: String
@GQLExtend extension1: UserExt
@GQLExtend extension2: NodeExt
)
case class UserExt(
age: Int,
)
@GQLDirective(Foo(42))
object NodeExt extends Node
The idea would be that each extend type is flattened into the main type for execution but we can preserve the extension state for rendering the schema, and can write this format when generating a schema.
There are some other variations of this approach that we could take too depending on how we the merging would work at a schema level.
We could do an inverted form of extension by wrapping the type.
@GQLExtendWith
case class Wrapper(
main: Foo,
ext1: UserExt,
ext2: NodeExt
Where the convention would be for the first item to be the base type and all subsequent ones would be merged in.
Both these would be declarative and they allow some benefit beyond the stated addition of allowing schemas to be DRYer.
Alternatively,
We could do extension at the GraphQL level, we already have a merge operator, we could add a new extend method that treats the second api as adding extensions.
case class User(
id: ID,
name: String
@GQLExtend extension1: UserExt
@GQLExtend extension2: NodeExt
)
I thought about the nesting approach too, but my concern here is – if we are to generate model classes from GraphQL schema (via caliban codegen), how would the code generator choose appropriate names for those nesting fields?
We can just pick some heuristic, structurally it wouldn't make a difference since it wouldn't be part of the schema