Skip to content
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

validate that different instances of InputObjectType with the same name are the same #611

Merged
merged 2 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions modules/core/src/main/scala/sangria/schema/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,8 @@ case class Schema[Ctx, Val](
(t1, t2) match {
case (ot1: ObjectType[_, _], ot2: ObjectType[_, _]) =>
sameSangriaType && (ot1.valClass == ot2.valClass)
case (ot1: InputObjectType[_], ot2: InputObjectType[_]) =>
sameSangriaType && (ot1.fieldsByName.keySet == ot2.fieldsByName.keySet)
case _ => sameSangriaType
}
}
Expand All @@ -1198,6 +1200,10 @@ case class Schema[Ctx, Val](
throw SchemaValidationException(
Vector(ConflictingObjectTypeCaseClassViolation(name, parentInfo)))

case (ot1: InputObjectType[_], ot2: InputObjectType[_]) =>
throw SchemaValidationException(
Vector(ConflictingInputObjectTypeCaseClassViolation(name, parentInfo)))

case _ =>
val conflictingTypes = List(t1, t2).map(_.getClass.getSimpleName)

Expand Down Expand Up @@ -1232,12 +1238,10 @@ case class Schema[Ctx, Val](
"You can find more info in the docs: http://sangria-graphql.org/learn/#circular-references-and-recursive-types")
case t: Named if result contains t.name =>
result.get(t.name) match {
case Some(found)
if !sameType(found._2, t) && t.isInstanceOf[ScalarAlias[_, _]] && found._2
.isInstanceOf[ScalarType[_]] =>
result
case Some(found) if !sameType(found._2, t) =>
typeConflict(t.name, found._2, t, parentInfo)
if (t.isInstanceOf[ScalarAlias[_, _]] && found._2.isInstanceOf[ScalarType[_]])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've extracted the if to avoid calling sameType several times.

result
else typeConflict(t.name, found._2, t, parentInfo)
case _ => result
}
case OptionType(ofType) => collectTypes(parentInfo, priority, ofType, result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,13 @@ case class ConflictingObjectTypeCaseClassViolation(typeName: String, parentInfo:
s"""Type name '$typeName' is used for several conflicting GraphQL ObjectTypes based on different classes. Conflict found in $parentInfo. One possible fix is to use ObjectTypeName like this: deriveObjectType[Foo, Bar](ObjectTypeName("OtherBar")) to avoid that two ObjectTypes have the same name."""
}

case class ConflictingInputObjectTypeCaseClassViolation(typeName: String, parentInfo: String)
extends Violation {
// Ideally this error message should include the conflicting classes canonical names but due to https://issues.scala-lang.org/browse/SI-2034 that's not possible
lazy val errorMessage =
s"""Type name '$typeName' is used for several conflicting GraphQL InputObjectTypes based on different classes. Conflict found in $parentInfo. One possible fix is to use InputObjectTypeName like this: deriveInputObjectType[Foo, Bar](InputObjectTypeName("OtherBar")) to avoid that two InputObjectTypes have the same name."""
}

case class ReservedTypeNameViolation(
typeName: String,
sourceMapper: Option[SourceMapper],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import sangria.ast
import sangria.execution.WithViolations
import sangria.validation._
import sangria.macros._
import sangria.macros.derive.{ObjectTypeName, deriveObjectType}
import sangria.macros.derive.{
InputObjectTypeName,
ObjectTypeName,
deriveInputObjectType,
deriveObjectType
}
import sangria.util.Pos

import scala.util.{Failure, Success, Try}
Expand Down Expand Up @@ -177,8 +182,8 @@ class SchemaConstraintsSpec extends AnyWordSpec with Matchers {
}

"Not allow ObjectTypes with same name to be based on different case classes" in {
implicit val fooBazType = deriveObjectType[Unit, test.foo.Baz]()
implicit val barBazType = deriveObjectType[Unit, test.bar.Baz]()
val fooBazType = deriveObjectType[Unit, test.foo.Baz]()
val barBazType = deriveObjectType[Unit, test.bar.Baz]()

val queryType = ObjectType(
"Query",
Expand All @@ -194,6 +199,40 @@ class SchemaConstraintsSpec extends AnyWordSpec with Matchers {
"""Type name 'Baz' is used for several conflicting GraphQL ObjectTypes based on different classes. Conflict found in a field 'barBaz' of 'Query' type. One possible fix is to use ObjectTypeName like this: deriveObjectType[Foo, Bar](ObjectTypeName("OtherBar")) to avoid that two ObjectTypes have the same name.""")
}

"Not allow InputObjectTypes with same name to be based on different case classes" in {
val fooBazType: InputObjectType[test.foo.Baz] =
deriveInputObjectType(InputObjectTypeName("baz"))
val barBazType: InputObjectType[test.bar.Baz] =
deriveInputObjectType(InputObjectTypeName("baz"))

val draftType = InputObjectType(
name = "DraftType",
fields = List(
InputField("fooBaz", OptionInputType(fooBazType)),
InputField("barBaz", barBazType)
)
)

val draft = Argument("draft", draftType)

val mutationType = ObjectType(
"Mutation",
fields[Unit, Unit](
Field("nothing", StringType, arguments = draft :: Nil, resolve = _ => "hello")
)
)
val queryType = ObjectType(
"Query",
fields[Unit, Unit](
Field("nothing", StringType, resolve = _ => "hello")
))

val error = intercept[SchemaValidationException](Schema(queryType, Some(mutationType)))

error.getMessage should include(
"""Type name 'baz' is used for several conflicting GraphQL InputObjectTypes based on different classes. Conflict found in a field 'barBaz' of 'DraftType' input object type. One possible fix is to use InputObjectTypeName like this: deriveInputObjectType[Foo, Bar](InputObjectTypeName("OtherBar")) to avoid that two InputObjectTypes have the same name.""")
}

"Allow ObjectTypes based on different case classes but with different names" in {
implicit val fooBazType = deriveObjectType[Unit, test.foo.Baz]()
implicit val barBazType =
Expand Down