From 76d87de986488bb68e9b0962a0b0cb2882f60f58 Mon Sep 17 00:00:00 2001 From: Sam Guymer Date: Mon, 10 Jun 2024 10:19:58 +1000 Subject: [PATCH] Fix Scala 3 enum and union member name sorting (#2275) Ensure Scala 3 derivation sorts enums and union members by type name to match Scala 2. --- .../caliban/schema/DerivationUtils.scala | 4 +- .../scala-3/caliban/schema/SumSchema.scala | 2 +- .../schema/SchemaDerivationIssuesSpec.scala | 115 ++++++++++++++++++ 3 files changed, 118 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala-3/caliban/schema/DerivationUtils.scala b/core/src/main/scala-3/caliban/schema/DerivationUtils.scala index b5204610e..090bc5a58 100644 --- a/core/src/main/scala-3/caliban/schema/DerivationUtils.scala +++ b/core/src/main/scala-3/caliban/schema/DerivationUtils.scala @@ -48,7 +48,7 @@ private object DerivationUtils { makeEnum( Some(getName(annotations, info)), getDescription(annotations), - subTypes.collect { case (name, __Type(_, _, description, _, _, _, _, _, _, _, _, _), annotations) => + subTypes.map { case (name, __Type(_, _, description, _, _, _, _, _, _, _, _, _), annotations) => __EnumValue( getName(annotations, name), description, @@ -56,7 +56,7 @@ private object DerivationUtils { getDeprecatedReason(annotations), Some(annotations.collect { case GQLDirective(dir) => dir }.toList).filter(_.nonEmpty) ) - }, + }.sortBy(_.name), Some(info.full), Some(getDirectives(annotations)) ) diff --git a/core/src/main/scala-3/caliban/schema/SumSchema.scala b/core/src/main/scala-3/caliban/schema/SumSchema.scala index 63b36ce36..7cd6dc305 100644 --- a/core/src/main/scala-3/caliban/schema/SumSchema.scala +++ b/core/src/main/scala-3/caliban/schema/SumSchema.scala @@ -39,7 +39,7 @@ final private class SumSchema[R, A]( makeUnion( Some(getName(annotations, info)), getDescription(annotations), - subTypes.map(_._2).distinctBy(_.name).map(SchemaUtils.fixEmptyUnionObject), + subTypes.map(_._2).distinctBy(_.name).map(SchemaUtils.fixEmptyUnionObject).sortBy(_.name), Some(info.full), Some(getDirectives(annotations)) ) diff --git a/core/src/test/scala/caliban/schema/SchemaDerivationIssuesSpec.scala b/core/src/test/scala/caliban/schema/SchemaDerivationIssuesSpec.scala index 53aba9b89..52462cf8c 100644 --- a/core/src/test/scala/caliban/schema/SchemaDerivationIssuesSpec.scala +++ b/core/src/test/scala/caliban/schema/SchemaDerivationIssuesSpec.scala @@ -268,6 +268,66 @@ object SchemaDerivationIssuesSpec extends ZIOSpecDefault { expected = """{"widget":{"__typename":"WidgetB","name":"a","children":{"total":1,"nodes":[{"name":"a","foo":"FOO"}]}}}""" } yield assertTrue(data1 == expected, data2 == expected) + }, + test("sum types are sorted by name") { + import sorting._ + val rendered = schema.render + assertTrue( + rendered == + """schema { + | query: Queries + |} + | + |union Union = A | B | C | ChildE | ChildY | D | Z + | + |enum Enum { + | B + | C + | ChildE + | ChildY + | D + | Z + |} + | + |type A { + | field: String! + |} + | + |type B { + | "Fake field because GraphQL does not support empty objects. Do not query, use __typename instead." + | _: Boolean + |} + | + |type C { + | "Fake field because GraphQL does not support empty objects. Do not query, use __typename instead." + | _: Boolean + |} + | + |type ChildE { + | "Fake field because GraphQL does not support empty objects. Do not query, use __typename instead." + | _: Boolean + |} + | + |type ChildY { + | "Fake field because GraphQL does not support empty objects. Do not query, use __typename instead." + | _: Boolean + |} + | + |type D { + | "Fake field because GraphQL does not support empty objects. Do not query, use __typename instead." + | _: Boolean + |} + | + |type Queries { + | e: Enum! + | u: Union! + |} + | + |type Z { + | "Fake field because GraphQL does not support empty objects. Do not query, use __typename instead." + | _: Boolean + |}""".stripMargin + ) } ) } @@ -616,3 +676,58 @@ object i2076 { graphQL(RootResolver(queries)) } } + +object sorting { + sealed trait Enum + object Enum { + implicit val schema: Schema[Any, Enum] = Schema.gen + } + + sealed trait Union + object Union { + implicit val schema: Schema[Any, Union] = Schema.gen + } + + case object Z extends Enum with Union { + implicit val schema: Schema[Any, Z.type] = Schema.gen + } + + @GQLName("ChildY") + case object Y extends Enum with Union { + implicit val schema: Schema[Any, Y.type] = Schema.gen + } + + @GQLName("B") + case object B extends Enum with Union { + implicit val schema: Schema[Any, B.type] = Schema.gen + } + + case class A(field: String) extends Union + object A { + implicit val schema: Schema[Any, A] = Schema.gen + } + + case object C extends Enum with Union { + implicit val schema: Schema[Any, C.type] = Schema.gen + } + + case object D extends Enum with Union { + implicit val schema: Schema[Any, D.type] = Schema.gen + } + + @GQLName("ChildE") + case object E extends Enum with Union { + implicit val schema: Schema[Any, E.type] = Schema.gen + } + + case class Queries(e: Enum, u: Union) + + object Queries { + implicit val schema: Schema[Any, Queries] = Schema.gen + } + + val schema = { + val queries = Queries(e = Z, u = Z) + caliban.graphQL(RootResolver(queries)) + } +}