generator: Couldn't share Union type when includes self-reference type
masa-mfw opened this issue · 5 comments
Issue
Cannot share a union type when a self-reference field is included in the union type definition.
Environment
version: 7.0.2
dependencies: spring boot, gradle
Reproduce
1. Create type classes including self-reference field
data class SimpleTaskType(
override val id: ID,
override val name: String,
)
data class TaskGroupType(
override val id: ID,
override val name: String,
val tasks: List<Any>, // Should be list of SimpleTaskType or TaskGroupType
)
2. Configure UnionType
data class TaskGroupType(
override val id: ID,
override val name: String,
@GraphQLUnion(
name = "Task",
possibleTypes = [
SimpleTaskType::class,
TaskGroupType::class
]
)
val tasks: List<Any>,
)
3. Create another type referring to the union type
data class ScheduleType(
val id: ID,
@GraphQLUnion(
name = "Task",
possibleTypes = [
SimpleTaskType::class,
TaskGroupType::class
]
)
val task: Any,
)
4. Generate SDL and error occurs
error message:
> There was a failure while executing work items
> A failure occurred while executing com.expediagroup.graphql.plugin.gradle.actions.GenerateSDLAction
> type Any not found in schema
Work around
Avoid using the same type name.
data class ScheduleType(
val id: ID,
@GraphQLUnion(
name = "UnitedTask", // When "Task" is set, error occurs
possibleTypes = [
SimpleTaskType::class,
TaskGroupType::class
]
)
val task: Any,
)
However, it generates schema like as:
union Task = SimpleTaskType | TaskGroupType
union UnitedTask = SimpleTaskType | TaskGroupType
type ScheduleType {
id: ID!
task: UnitedTask! # Having different union types for the same entities is not preferable
}
type TaskGroupType implements TaskType {
id: ID!
name: String!
tasks: [Task!]!
}
Question
- Is this behavior intentional?
- Are there any solutions to share a union type?
Appendix
Union types can be shared as a single definition when they don't have a self-reference field.
Such as, the following code is perfectly valid.
data class ScheduleType(
val id: ID,
@GraphQLUnion(
name = "UnitedTask", // When "Task" is set, error occurs
possibleTypes = [
SimpleTaskType::class,
TaskGroupType::class
]
)
val task: Any,
)
data class TodoType(
val id: ID,
@GraphQLUnion(
name = "UnitedTask",
possibleTypes = [
SimpleTaskType::class,
TaskGroupType::class
]
)
val task: Any, // Share union type as single object
@GraphQLUnion(
name = "UnitedTask",
possibleTypes = [
SimpleTaskType::class,
TaskGroupType::class
]
)
val tasks: List<Any>, // Share union type as listed objects
val isDone: Boolean,
)
Sample code
This practice is located at:
https://github.com/masa-mfw/spring-graphql-practice
Hello 👋
Instead of using the @GraphQLUnion
which is limited in number of ways, have you tried using marker interfaces or @GraphQLType
approach?
@dariuszkuc
Thank you for your reply 😄
I successfully applied @GraphQLType in my sample project.
However, I'm facing issues when applying the same approach to our product code.
What I tried is like following.
data class TaskGroupType(
override val id: ID,
override val name: String,
@GraphQLUnion(
name = "Task",
possibleTypes = [
SimpleTaskType::class,
TaskGroupType::class
]
)
val tasks: List<Any>,
)
data class ScheduleType(
val id: ID,
@GraphQLType("Task")
val task: Any,
)
And error occurs in product code:
type Task not found in schema
I guess this unstable issue might be related to the order in which the schema generator discovers fields.
As for the marker interface, it doesn't seem to meet what I want to do.
From my understanding, this approach requires query arguments to determine the appropriate class response.
I am afraid that I may not fully understand these features.
Would you have any detailed examples for these approach?
Reason why schema generation fails is that @GraphQLType
is just a reference to an existing type - it does not generate that type definition. So your Task
has to be somehow created.
re: unions through marker interfaces - you simply need to define marker interface and update your types to implement it
interface Task
data class TaskGroupType(
val id: ID,
val name: String,
val tasks: List<Task>,
): Task
data class ScheduleType(
val id: ID,
val task: Task,
): Task
Given a simple query then
fun task(): Task {
TODO() // your logic goes here
}
Its up to you to determine to return appropriate Task
union member.
Thank you!
I agree that these should not be union types, as you pointed out.
This issue arose due to my limited knowledge of GraphQL.
I have learned about GraphQL thanks to this opportunity!
Oops, I misunderstood your suggestion.
Interestingly, when I defined the marker interface, it appeared as a union type in SDL, which was actually my intention.
Thank you for your support!