ghostdogpr / caliban

Functional GraphQL library for Scala

Home Page:https://ghostdogpr.github.io/caliban/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Duplicate keys when querying with a fragment compared to inlining

guymers opened this issue · comments

Had an issue beginning with 2.5 where nodes in a connection where not being returned. I can't seem to write a reproducer that does exactly that but given this schema:

sealed trait Widget
object Widget  {
  implicit val schema: Schema[Any, Widget] = Schema.gen

  case class Args(limit: Option[Int])
  object Args {
    implicit val schema: Schema[Any, Args]    = Schema.gen
    implicit val argBuilder: ArgBuilder[Args] = ArgBuilder.gen
  }

  @GQLName("WidgetA")
  case class A(
    name: String,
    children: Args => ZIO[Any, Throwable, A.Connection]
  ) extends Widget
  object A    {
    implicit val schema: Schema[Any, A] = Schema.gen

    @GQLName("WidgetAConnection")
    case class Connection(total: Int, nodes: Chunk[Child])
    object Connection {
      implicit val schema: Schema[Any, Connection] = Schema.gen
    }

    @GQLName("WidgetAChild")
    case class Child(name: String)
    object Child      {
      implicit val schema: Schema[Any, Child] = Schema.gen
    }
  }

  @GQLName("WidgetB")
  case class B(
    name: String,
    children: Args => ZIO[Any, Throwable, B.Connection]
  ) extends Widget
  object B    {
    implicit val schema: Schema[Any, B] = Schema.gen

    @GQLName("WidgetBConnection")
    case class Connection(total: Int, nodes: Chunk[Child])
    object Connection {
      implicit val schema: Schema[Any, Connection] = Schema.gen
    }

    @GQLName("WidgetBChild")
    case class Child(name: String)
    object Child      {
      implicit val schema: Schema[Any, Child] = Schema.gen
    }
  }
}

case class Queries(
  widget: Option[Widget]
)
object Queries {
  implicit val schema: Schema[Any, Queries] = Schema.gen
}

Querying with a fragment:

query {
  widget {
    __typename
    ...Widgets
  }
}

fragment Widgets on Widget {
  ... on WidgetA {
    name
    children {
      total
      nodes { name }
    }
  }
  ... on WidgetB {
    name
    children {
      total
      nodes { name }
    }
  }
}

returns children with duplicate keys

{
  "widget": {
    "__typename": "WidgetA",
    "name": "a",
    "children": {
      "total": 1,
      "nodes": [
        {"name": "1"}
      ],
      "total": 1,
      "nodes": [
        {"name": "1"}
      ]
    }
  }
}

Querying inline:

query {
  widget {
    __typename
    ... on WidgetA {
      name
      children {
        total
        nodes { name }
      }
    }
    ... on WidgetB {
      name
      children {
        total
        nodes { name }
      }
    }
  }
}

gives

{
  "widget": {
    "__typename": "WidgetA",
    "name": "a",
    "children": {
      "total": 1,
      "nodes": [
        {"name": "1"}
      ]
    }
  }
}

both queries produced no duplicates in 2.4.3

@guymers thanks for reporting this issue. Unfortunately this edge case fell managed to creep in during a series of optimizations to the query executor. I opened a PR fixing the issue and we'll try and get a release out to patch it as soon as possible.

On that note, I've noticed based on the previous issues you created that you heavily rely on deeply nested intersection / union types. If / when you have some time (and of course, if you're interested!), it would be great if you could add a few test cases to our test suite with some of your more complex usecases. That could potentially help us catch these issues early :)

Thanks for looking at the issues I raise so promptly. Sorry for raising this one without doing more testing, 2.5.1 has fixed the fields not being returned, but there are fields coming back from other fragments:

diff --git a/core/src/test/scala/caliban/schema/SchemaDerivationIssuesSpec.scala b/core/src/test/scala/caliban/schema/SchemaDerivationIssuesSpec.scala
index ad7827cc..a246f7cb 100644
--- a/core/src/test/scala/caliban/schema/SchemaDerivationIssuesSpec.scala
+++ b/core/src/test/scala/caliban/schema/SchemaDerivationIssuesSpec.scala
@@ -227,7 +227,7 @@ object SchemaDerivationIssuesSpec extends ZIOSpecDefault {
                 name
                 children {
                   total
-                  nodes { name }
+                  nodes { name bar }
                 }
               }
               ... on WidgetB {
@@ -248,7 +248,7 @@ object SchemaDerivationIssuesSpec extends ZIOSpecDefault {
                 name
                 children {
                   total
-                  nodes { name }
+                  nodes { name bar }
                 }
               }
               ... on WidgetB {
@@ -574,7 +574,7 @@ object i2076 {
       }
 
       @GQLName("WidgetAChild")
-      case class Child(name: String, foo: String)
+      case class Child(name: String, foo: String, bar: String)
       object Child      {
         implicit val schema: Schema[Any, Child] = Schema.gen
       }

the resulting json contains "bar":null.

I though everyone made good use of unions in their GraphQL schemas ;) I think most of our structure is already covered now haha.

@guymers this seems like a separate issue. I've gone back as far as version 2.3.0 and it's still there. I'll see if I can fix it

fyi 2.5.1 release failed because of Sonatype timing out, will retry later