-
-
Notifications
You must be signed in to change notification settings - Fork 251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Caliban Client: Allow query to not specify every subtype for the Union Type #446
Comments
The related code is https://github.com/ghostdogpr/caliban/blob/master/codegen/src/main/scala/caliban/codegen/ClientWriter.scala#L240. I think it would be nice if we kept the existing method (no need to get an Option when you're willing to provide all cases) and add a new one with that behavior, where each parameter would be optional. |
Actually if I could get some help with this that would be great. The new reference to the above First to make sure we are on the same page this is how I envision this working. For (You can use this query with graphiQL to see the result)
This would result in the following GraphQL response:
Because the query only specified the
The Client Generated code for this query could look like this:
I don't have access to my computer that I was working this on to remember some of the challenges with this approach. I do remember that the overridden methods |
How about the following implementation: def roleOption[A](
onCaptain: Option[SelectionBuilder[Captain, A]] = None,
onEngineer: Option[SelectionBuilder[Engineer, A]] = None,
onMechanic: Option[SelectionBuilder[Mechanic, A]] = None,
onPilot: Option[SelectionBuilder[Pilot, A]] = None
): SelectionBuilder[Character, Option[Option[A]]] =
Field(
"role",
OptionOf(
ChoiceOf(
Map(
"Captain" -> onCaptain.fold[FieldBuilder[Option[A]]](NullField)(a => OptionOf(Obj(a))),
"Engineer" -> onEngineer.fold[FieldBuilder[Option[A]]](NullField)(a => OptionOf(Obj(a))),
"Mechanic" -> onMechanic.fold[FieldBuilder[Option[A]]](NullField)(a => OptionOf(Obj(a))),
"Pilot" -> onPilot.fold[FieldBuilder[Option[A]]](NullField)(a => OptionOf(Obj(a)))
)
)
)
) This introduces case object NullField extends FieldBuilder[Option[Nothing]] {
override def fromGraphQL(value: Value): Either[DecodingError, Option[Nothing]] = Right(None)
override def toSelectionSet: List[Selection] = Nil
} One more thing to do is in |
@ghostdogpr Yes that works. I filtered the case object NullField extends FieldBuilder[Option[Nothing]] {
override def fromGraphQL(value: Value): Either[DecodingError, Option[Nothing]] = Right(None)
override def toSelectionSet: List[Selection] = Nil
}
def isNullField(p : Any): Boolean = p match {
case NullField => true
case _ => false
}
case class ChoiceOf[A](builderMap: Map[String, FieldBuilder[A]]) extends FieldBuilder[A] {
override def fromGraphQL(value: Value): Either[DecodingError, A] =
value match {
case ObjectValue(fields) =>
for {
typeNameValue <- fields.find(_._1 == "__typename").map(_._2).toRight(DecodingError("__typename is missing"))
typeName <- typeNameValue match {
case StringValue(value) => Right(value)
case _ => Left(DecodingError("__typename is not a String"))
}
fieldType <- builderMap.get(typeName).toRight(DecodingError(s"type $typeName is unknown"))
result <- fieldType.fromGraphQL(value)
} yield result
case _ => Left(DecodingError(s"Field $value is not an object"))
}
override def toSelectionSet: List[Selection] = {
val filteredBuilderMap = builderMap.filter((f) => !isNullField(f._2) );
Selection.Field(None, "__typename", Nil, Nil, Nil, 0) ::
filteredBuilderMap.map { case (k, v) => { Selection.InlineFragment(k, v.toSelectionSet) }}.toList
}
} If this looks good to you I will try to update the code generator to generate the proper client code. For the ClientExampleApp I also updated the query to demonstrate named parameters for val character = {
import caliban.client.Client.Character._
(name ~
nicknames ~
origin ~
role(
onEngineer = Some(Engineer.shipName.map(Role.Engineer)),
onPilot = Some(Pilot.shipName.map(Role.Pilot))
)
).mapN(Character)
} |
@anotherhale that looks good! About the code generation, should it be an additional function or replace the existing one? I feel that the existing one will be nicer when you want to specify all cases because you don't need to wrap the selection in |
I agree it would be good to have a different function. I do feel like |
@anotherhale I think both functions can use |
Yes. I see what you mean now. The code would just generate both a
And then the query would could use either one like so:
Or the
|
Yep, that's what I was suggesting 👍 |
This is the change that I made to the
I did not see how to generate both without creating another |
Yeah I see the problem. Maybe add an intermediate function before |
Sorry been busy with work and life. I will give this a try this week. |
I have PR that I can push soon. I just need to get it approved for release by my company first. |
@anotherhale great! 😄 |
@anotherhale |
Sorry finally making some progress. The wheels of bureaucracy turn slowly... I will post the PR once approved. |
@anotherhale |
I will try to see if I can expedite it. |
I have it all completed with docs and examples too. Everyone is on vacation for the holidays - probably won't see anything until January. |
I just got approval on releasing this. I will open a PR as soon as I merge in latest changes and resolve any conflicts. |
Sorry this took so long. This request to contribute to open source was a pioneer effort in my organization and now they have put in place policies and procedures to make contributing back to the open source community much easier and quicker. 👍 |
Great to hear 👏 |
This is great news. Thank you! |
I would like to be able to write queries for a Union Type and not have to specify every subtype.
Currently when you query a Union Type you must provide all of the subtype implementations or you will get a compile error. If I remove the
Pilot.shipName.map(Role.Pilot)
from the role query:I get the following compile time error:
I discussed this with @ghostdogpr and we determined that this is doable by returning the
__typename
for the case classes that are not specified and returning an Option[A] instead of the A.The text was updated successfully, but these errors were encountered: