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

Allow excluding fields from interfaces #1975

Merged
merged 6 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 5 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import com.typesafe.tools.mima.core.{ IncompatibleMethTypeProblem, IncompatibleResultTypeProblem, ProblemFilters }
import com.typesafe.tools.mima.core.*
import org.scalajs.linker.interface.ModuleSplitStyle
import sbtcrossproject.CrossPlugin.autoImport.{ crossProject, CrossType }

Expand Down Expand Up @@ -676,7 +676,10 @@ lazy val enableMimaSettingsJVM =
mimaPreviousArtifacts := previousStableVersion.value.map(organization.value %% moduleName.value % _).toSet,
mimaBinaryIssueFilters ++= Seq(
ProblemFilters.exclude[IncompatibleMethTypeProblem]("caliban.schema.Step#ObjectStep*"),
ProblemFilters.exclude[IncompatibleResultTypeProblem]("caliban.schema.Step#ObjectStep*")
ProblemFilters.exclude[IncompatibleResultTypeProblem]("caliban.schema.Step#ObjectStep*"),
ProblemFilters.exclude[DirectMissingMethodProblem]("caliban.schema.Annotations*"),
ProblemFilters.exclude[MissingTypesProblem]("caliban.schema.Annotations*"),
ProblemFilters.exclude[IncompatibleResultTypeProblem]("caliban.schema.Annotations*")
)
)

Expand Down
19 changes: 10 additions & 9 deletions core/src/main/scala-2/caliban/schema/SchemaDerivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ trait CommonSchemaDerivation[R] {
case _ => false
}
val isInterface = ctx.annotations.exists {
case GQLInterface() => true
case _ => false
case _: GQLInterface => true
case _ => false
}
val isUnion = ctx.annotations.exists {
case GQLUnion() => true
Expand Down Expand Up @@ -158,18 +158,19 @@ trait CommonSchemaDerivation[R] {
Some(getDirectives(ctx.annotations))
)
else {
val excl = ctx.annotations.collectFirst { case i: GQLInterface => i.excludedFields.toSet }.getOrElse(Set.empty)
val impl = subtypes.map(_._1.copy(interfaces = () => Some(List(toType(isInput, isSubscription)))))
val commonFields = () =>
impl
.flatMap(_.allFields)
.groupBy(_.name)
.filter { case (_, list) => list.lengthCompare(impl.size) == 0 }
.collect { case (_, list) =>
Types
.unify(list)
.flatMap(t =>
list.headOption.map(_.copy(description = Types.extractCommonDescription(list), `type` = () => t))
)
.collect {
case (name, list) if list.lengthCompare(impl.size) == 0 && !excl.contains(name) =>
Types
.unify(list)
.flatMap(t =>
list.headOption.map(_.copy(description = Types.extractCommonDescription(list), `type` = () => t))
)
}
.flatten
.toList
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/scala-3/caliban/schema/DerivationUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,13 @@ private object DerivationUtils {
info: TypeInfo,
impl: List[__Type]
): __Type = {
val excl = annotations.collectFirst { case i: GQLInterface => i.excludedFields.toSet }.getOrElse(Set.empty)
val commonFields = () =>
impl
.flatMap(_.allFields)
.groupBy(_.name)
.collect {
case (_, list) if list.lengthCompare(impl.size) == 0 =>
case (name, list) if list.lengthCompare(impl.size) == 0 && !excl.contains(name) =>
Types
.unify(list)
.flatMap(t =>
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala-3/caliban/schema/SumSchema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final private class SumSchema[R, A](
}

private lazy val isEnum = subTypes.forall((_, t, _) => t.allFields.isEmpty && t.allInputFields.isEmpty)
private val isInterface = annotations.contains(GQLInterface())
private val isInterface = annotations.exists(_.isInstanceOf[GQLInterface])
private val isUnion = annotations.contains(GQLUnion())

def toType(isInput: Boolean, isSubscription: Boolean): __Type =
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/scala/caliban/schema/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ object Annotations {

/**
* Annotation to make a sealed trait an interface instead of a union type or an enum
*
* @param excludeFields Optionally provide a list of field names that should be excluded from the interface
*/
case class GQLInterface() extends StaticAnnotation
case class GQLInterface(excludedFields: String*) extends StaticAnnotation

/**
* Annotation to make a sealed trait a union instead of an enum
Expand Down
16 changes: 14 additions & 2 deletions core/src/test/scala/caliban/schema/SchemaSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ object SchemaSpec extends ZIOSpecDefault {
test("interface only take fields that return the same type") {
assertTrue(introspect[MyInterface].fields(__DeprecatedArgs()).toList.flatten.map(_.name) == List("c1", "c2"))
},
test("excluding fields from interfaces") {
assertTrue(
introspect[InterfaceWithExclusions].fields(__DeprecatedArgs()).toList.flatten.map(_.name) == List("c1")
)
},
test("enum-like sealed traits annotated with GQLUnion") {
assert(introspect[EnumLikeUnion])(
hasField[__Type, __TypeKind]("kind", _.kind, equalTo(__TypeKind.UNION))
Expand Down Expand Up @@ -381,14 +386,21 @@ object SchemaSpec extends ZIOSpecDefault {

@GQLInterface
sealed trait MyInterface
object MyInterface {
object MyInterface {
case class A(c1: Int, c2: Int => Int, d1: Int, d2: Int => Int, d3: Int => Int) extends MyInterface
case class B(c1: Int, c2: Int => Int, d1: Boolean, d2: Int, d3: Option[Int] => Int) extends MyInterface
}

@GQLInterface(excludedFields = "c2", "c3")
sealed trait InterfaceWithExclusions
object InterfaceWithExclusions {
case class A(c1: Int, c2: String, c3: Int, c5: Int) extends InterfaceWithExclusions
case class B(c1: Int, c2: String, c3: Int, c4: Int) extends InterfaceWithExclusions
}

@GQLUnion
sealed trait EnumLikeUnion
object EnumLikeUnion {
object EnumLikeUnion {
case object A extends EnumLikeUnion
case object B extends EnumLikeUnion
}
Expand Down