Skip to content

Commit

Permalink
Merge pull request #531 from typelevel/topic/supertype-subtype-fragments
Browse files Browse the repository at this point in the history
Fix for fragments with supertype refinements
  • Loading branch information
milessabin authored Dec 12, 2023
2 parents f9527b0 + 261f03a commit 0b12675
Show file tree
Hide file tree
Showing 3 changed files with 314 additions and 7 deletions.
167 changes: 167 additions & 0 deletions modules/core/src/test/scala/compiler/FragmentSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,173 @@ final class FragmentSuite extends CatsEffectSuite {

val res = FragmentMapping.compileAndRun(query)

assertIO(res, expected)
}

test("supertype fragment query (5)") {
val query = """
query withFragments {
user(id: 1) {
friends {
...profileFields
}
}
}
fragment profileFields on Profile {
id
name
}
"""

val expected = json"""
{
"errors" : [
{
"message" : "No field 'name' for type Profile"
}
]
}
"""

val res = FragmentMapping.compileAndRun(query)

assertIO(res, expected)
}

test("interface query with supertype fragment containing subtype refinement") {
val query = """
query withFragments {
user(id: 1) {
friends {
... ProfileFields
}
}
}
fragment ProfileFields on Profile {
id
... on User {
name
}
... on Page {
title
}
}
"""

val expected = json"""
{
"data" : {
"user" : {
"friends" : [
{
"id" : "2",
"name" : "Bob"
},
{
"id" : "3",
"name" : "Carol"
}
]
}
}
}
"""

val res = FragmentMapping.compileAndRun(query)

assertIO(res, expected)
}

test("interface query with supertype fragment containing nested fragment spreads") {
val query = """
query withFragments {
user(id: 1) {
friends {
... ProfileFields
}
}
}
fragment ProfileFields on Profile {
id
... UserFields
... PageFields
}
fragment UserFields on User {
name
}
fragment PageFields on Page {
title
}
"""

val expected = json"""
{
"data" : {
"user" : {
"friends" : [
{
"id" : "2",
"name" : "Bob"
},
{
"id" : "3",
"name" : "Carol"
}
]
}
}
}
"""

val res = FragmentMapping.compileAndRun(query)

assertIO(res, expected)
}

test("interface query with nested inline fragments") {
val query = """
query withFragments {
user(id: 1) {
friends {
... on Profile {
id
... on User {
name
}
... on Page {
title
}
}
}
}
}
"""

val expected = json"""
{
"data" : {
"user" : {
"friends" : [
{
"id" : "2",
"name" : "Bob"
},
{
"id" : "3",
"name" : "Carol"
}
]
}
}
}
"""

val res = FragmentMapping.compileAndRun(query)

assertIO(res, expected)
}
Expand Down
17 changes: 10 additions & 7 deletions modules/sql/shared/src/main/scala/SqlMapping.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2973,7 +2973,7 @@ trait SqlMappingLike[F[_]] extends CirceMappingLike[F] with SqlModule[F] { self
}
}

case TypeCase(default, narrows) =>
case TypeCase(default, narrows0) =>
def isSimple(query: Query): Boolean = {
def loop(query: Query): Boolean =
query match {
Expand All @@ -2985,9 +2985,11 @@ trait SqlMappingLike[F[_]] extends CirceMappingLike[F] with SqlModule[F] { self
loop(query)
}

val subtpes = narrows.map(_.subtpe)
val supertpe = context.tpe.underlying
assert(supertpe.underlying.isInterface || supertpe.underlying.isUnion)
val narrows = narrows0.filter(_.subtpe <:< supertpe)
val subtpes = narrows.map(_.subtpe)

assert(supertpe.underlying.isInterface || supertpe.underlying.isUnion || (subtpes.sizeCompare(1) == 0 && subtpes.head =:= supertpe))
subtpes.foreach(subtpe => assert(subtpe <:< supertpe))

val discriminator = discriminatorForType(context)
Expand Down Expand Up @@ -3472,10 +3474,11 @@ trait SqlMappingLike[F[_]] extends CirceMappingLike[F] with SqlModule[F] { self
if (ctpe =:= tpe) asTable.map(table => mapped.narrowsTo(context.asType(subtpe), table)).toOption.getOrElse(false)
else ctpe <:< subtpe

discriminatorForType(context) match {
case Some(disc) => disc.discriminator.discriminate(this).map(check).getOrElse(false)
case _ => check(tpe)
}
(subtpe <:< tpe) &&
(discriminatorForType(context) match {
case Some(disc) => disc.discriminator.discriminate(this).map(check).getOrElse(false)
case _ => check(tpe)
})
}

def narrow(subtpe: TypeRef): Result[Cursor] = {
Expand Down
137 changes: 137 additions & 0 deletions modules/sql/shared/src/test/scala/SqlInterfacesSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -634,4 +634,141 @@ trait SqlInterfacesSuite extends CatsEffectSuite {

assertWeaklyEqualIO(res, expected)
}

test("interface query with supertype fragment containing subtype refinement") {
val query = """
query {
films {
... EntityFields
}
}
fragment EntityFields on Entity {
id
... on Film {
rating
}
... on Series {
numberOfEpisodes
}
}
"""

val expected = json"""
{
"data" : {
"films" : [
{
"id" : "1",
"rating" : "PG"
},
{
"id" : "2",
"rating" : "U"
},
{
"id" : "3",
"rating" : "15"
}
]
}
}
"""

val res = mapping.compileAndRun(query)

assertWeaklyEqualIO(res, expected)
}

test("interface query with supertype fragment containing nested fragment spreads") {
val query = """
query {
films {
... EntityFields
}
}
fragment EntityFields on Entity {
id
... FilmFields
... SeriesFields
}
fragment FilmFields on Film {
rating
}
fragment SeriesFields on Series {
numberOfEpisodes
}
"""

val expected = json"""
{
"data" : {
"films" : [
{
"id" : "1",
"rating" : "PG"
},
{
"id" : "2",
"rating" : "U"
},
{
"id" : "3",
"rating" : "15"
}
]
}
}
"""

val res = mapping.compileAndRun(query)

assertWeaklyEqualIO(res, expected)
}

test("interface query with nested inline fragments") {
val query = """
query {
films {
... on Entity {
id
... on Film {
rating
}
... on Series {
numberOfEpisodes
}
}
}
}
"""

val expected = json"""
{
"data" : {
"films" : [
{
"id" : "1",
"rating" : "PG"
},
{
"id" : "2",
"rating" : "U"
},
{
"id" : "3",
"rating" : "15"
}
]
}
}
"""

val res = mapping.compileAndRun(query)

assertWeaklyEqualIO(res, expected)
}
}

0 comments on commit 0b12675

Please sign in to comment.