Skip to content

Commit 3f3a5d1

Browse files
authored
Merge pull request #989 from filosganga/fix-type-comparator
Fix TypeComparators
2 parents 3d72975 + 3daebdc commit 3f3a5d1

File tree

3 files changed

+140
-14
lines changed

3 files changed

+140
-14
lines changed

modules/core/src/main/scala/sangria/schema/Schema.scala

+35-13
Original file line numberDiff line numberDiff line change
@@ -1532,21 +1532,43 @@ case class Schema[Ctx, Val](
15321532
}
15331533
}
15341534

1535-
lazy val implementations: Map[String, Vector[ObjectType[_, _]]] =
1536-
allImplementations
1537-
.map { case (k, xs) =>
1538-
(
1539-
k,
1540-
xs.collect { case obj: ObjectType[_, _] =>
1541-
obj
1542-
})
1543-
}
1544-
.filter { case (_, v) =>
1545-
v.nonEmpty
1546-
}
1535+
def isPossibleImplementation(baseTypeName: String, tpe: ObjectLikeType[_, _]): Boolean =
1536+
tpe.name == baseTypeName || allImplementations
1537+
.get(baseTypeName)
1538+
.exists(_.exists(_.name == tpe.name))
1539+
1540+
@deprecated("Use concreteImplementations instead", "4.0.0")
1541+
lazy val implementations: Map[String, Vector[ObjectType[_, _]]] = concreteImplementations
1542+
1543+
lazy val concreteImplementations: Map[String, Vector[ObjectType[_, _]]] = allImplementations
1544+
.map { case (k, xs) =>
1545+
(
1546+
k,
1547+
xs.collect { case obj: ObjectType[_, _] =>
1548+
obj
1549+
})
1550+
}
1551+
.filter { case (_, v) =>
1552+
v.nonEmpty
1553+
}
15471554

1555+
/** This contains the map of all the concrete types by supertype.
1556+
*
1557+
* The supertype can be either a union or interface.
1558+
*
1559+
* According to the spec, even if an interface can implement another interface, this must only
1560+
* contains concrete types
1561+
*
1562+
* @see
1563+
* https://spec.graphql.org/June2018/#sec-Union
1564+
* @see
1565+
* https://spec.graphql.org/June2018/#sec-Interface
1566+
*
1567+
* @return
1568+
* Map of subtype by supertype name
1569+
*/
15481570
lazy val possibleTypes: Map[String, Vector[ObjectType[_, _]]] =
1549-
implementations ++ unionTypes.values.map(ut => ut.name -> ut.types.toVector)
1571+
concreteImplementations ++ unionTypes.values.map(ut => ut.name -> ut.types.toVector)
15501572

15511573
def isPossibleType(baseTypeName: String, tpe: ObjectType[_, _]): Boolean =
15521574
possibleTypes.get(baseTypeName).exists(_.exists(_.name == tpe.name))

modules/core/src/main/scala/sangria/validation/TypeComparators.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ object TypeComparators {
2222
case (sub, OptionInputType(ofType2)) => isSubType(schema, sub, ofType2)
2323
case (ListType(ofType1), ListType(ofType2)) => isSubType(schema, ofType1, ofType2)
2424
case (ListInputType(ofType1), ListInputType(ofType2)) => isSubType(schema, ofType1, ofType2)
25-
case (t1: ObjectType[_, _], t2: AbstractType) => schema.isPossibleType(t2.name, t1)
25+
case (t1: ObjectType[_, _], t2: AbstractType) =>
26+
schema.isPossibleType(t2.name, t1)
27+
case (t1: InterfaceType[_, _], t2: AbstractType) =>
28+
schema.isPossibleImplementation(t2.name, t1)
2629
case (t1: Named, t2: Named) => t1.name == t2.name
2730
case _ => false
2831
}

modules/derivation/src/test/scala/sangria/schema/SchemaConstraintsSpec.scala

+101
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,84 @@ class SchemaConstraintsSpec extends AnyWordSpec with Matchers {
971971
}
972972
""")
973973

974+
"rejects an Interface with a differently typed Interface field" in invalidSchema(
975+
graphql"""
976+
type Query {
977+
test: AnotherObject
978+
}
979+
980+
type A { foo: String }
981+
type B { foo: String }
982+
983+
interface AnotherInterface {
984+
field: A
985+
}
986+
987+
type AnotherObject implements AnotherInterface {
988+
field: B
989+
}
990+
""",
991+
"AnotherInterface.field expects type 'A', but AnotherObject.field provides type 'B'." -> Seq(
992+
Pos(14, 11),
993+
Pos(10, 11))
994+
)
995+
996+
"accepts an Interface with a subtyped Interface field (interface)" in validSchema(graphql"""
997+
type Query {
998+
test: Foo
999+
}
1000+
1001+
interface Node {
1002+
id: ID!
1003+
}
1004+
1005+
interface Edge {
1006+
cursor: String!
1007+
node: Node!
1008+
}
1009+
1010+
interface Foo implements Node {
1011+
id: ID!
1012+
}
1013+
1014+
type ConcreteFoo implements Foo & Node {
1015+
id: ID!
1016+
}
1017+
1018+
type FooEdge implements Edge {
1019+
cursor: String!
1020+
node: Foo!
1021+
}
1022+
1023+
type Test {
1024+
fooEdge: FooEdge
1025+
}
1026+
""")
1027+
1028+
"accepts an Interface with a subtyped Interface field (union)" in validSchema(graphql"""
1029+
type Query {
1030+
fooBar: FooBar
1031+
}
1032+
1033+
type SomeObject {
1034+
field: String
1035+
}
1036+
1037+
union SomeUnionType = SomeObject
1038+
1039+
interface Foo {
1040+
foo: SomeUnionType
1041+
}
1042+
1043+
interface Bar implements Foo {
1044+
foo: SomeUnionType
1045+
}
1046+
1047+
type FooBar implements Foo & Bar {
1048+
test: String
1049+
}
1050+
""")
1051+
9741052
"rejects an Interface missing an Interface argument" in invalidSchema(
9751053
graphql"""
9761054
type Query {
@@ -995,6 +1073,29 @@ class SchemaConstraintsSpec extends AnyWordSpec with Matchers {
9951073
)
9961074
)
9971075

1076+
"rejects an Interface with an incorrectly typed Interface argument" in invalidSchema(
1077+
graphql"""
1078+
type Query {
1079+
fooBar: FooBar
1080+
}
1081+
1082+
interface Foo {
1083+
foo(input: String): String
1084+
}
1085+
1086+
interface Bar implements Foo {
1087+
foo(input: Int): String
1088+
}
1089+
1090+
type FooBar implements Foo & Bar {
1091+
test: Int
1092+
}
1093+
""",
1094+
"Foo.foo(input) expects type 'String', but Bar.foo(input) provides type 'Int'." -> Seq(
1095+
Pos(7, 15),
1096+
Pos(11, 15))
1097+
)
1098+
9981099
"rejects an Interface with an incorrectly typed Interface field" in invalidSchema(
9991100
graphql"""
10001101
type Query {

0 commit comments

Comments
 (0)