Skip to content

Commit

Permalink
hide fieldSet scalar from federation v2 introspection
Browse files Browse the repository at this point in the history
  • Loading branch information
paulpdaniels committed Jun 8, 2024
1 parent 67a34ac commit 7c7e940
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 10 deletions.
33 changes: 33 additions & 0 deletions core/src/main/scala/caliban/Value.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import caliban.interop.zio.{ IsZIOJsonDecoder, IsZIOJsonEncoder }
import caliban.rendering.ValueRenderer
import zio.stream.Stream

import scala.annotation.tailrec
import scala.util.control.NonFatal
import scala.util.hashing.MurmurHash3

Expand Down Expand Up @@ -67,6 +68,38 @@ sealed trait ResponseValue extends Serializable { self =>
}
}
object ResponseValue {

def at(path: List[PathValue])(value: ResponseValue): ResponseValue = {
def loop(path: List[PathValue], value: ResponseValue): ResponseValue = path match {
case Nil => value
case PathValue.Key(key) :: tail =>
value match {
case ObjectValue(fields) =>
fields.find(_._1 == key) match {
case Some((_, v)) => loop(tail, v)
case None => Value.NullValue
}
case ListValue(values) =>
ListValue(values.map(loop(path, _)))
case _ => Value.NullValue
}
case PathValue.Index(index) :: tail =>
value match {
case ListValue(values) =>
val idx = index
if (idx < values.size) {
loop(tail, values(idx))
} else {
Value.NullValue
}
case _ => Value.NullValue
}
case _ => Value.NullValue
}

loop(path, value)
}

case class ListValue(values: List[ResponseValue]) extends ResponseValue {
override def toString: String = ValueRenderer.responseListValueRenderer.renderCompact(this)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,29 @@ abstract class FederationSupport(
) {
import FederationHelpers._

// This is a bit of a hack to determine if we are using the v1 version of the federation spec
// All of the v2 directives come through schema directives while the v1 is through the supported directives field instead
private val isV1 = supportedDirectives.nonEmpty && schemaDirectives.isEmpty
private val extraTypes = if (isV1) List(fieldSetSchema.toType_()) else Nil

/**
* Accepts a GraphQL and returns a GraphQL with the minimum settings to support federation. This variant does not
* provide any stitching capabilities, it merely makes this schema consumable by a graphql federation gateway.
* @param original The original schema
* @return A new schema which has been augmented with federation types
*/
def federate[R](original: GraphQL[R]): GraphQL[R] = {
import caliban.schema.Schema.auto._

case class Query(
_service: _Service,
_fieldSet: FieldSet = FieldSet("")
_service: _Service
)

implicit val serviceSchema = Schema.gen[R, _Service]
implicit val querySchema = Schema.gen[R, Query]

graphQL(
RootResolver(Query(_service = _Service(original.withSchemaDirectives(schemaDirectives).render))),
supportedDirectives
) |+| original
).withAdditionalTypes(extraTypes) |+| original
}

def federated[R](resolver: EntityResolver[R], others: EntityResolver[R]*): GraphQLAspect[Nothing, R] =
Expand All @@ -57,7 +62,6 @@ abstract class FederationSupport(
val resolvers = resolver +: otherResolvers.toList

val genericSchema = new GenericSchema[R] {}
import genericSchema.auto._

implicit val entitySchema: Schema[R, _Entity] = new Schema[R, _Entity] {
override def nullable: Boolean = true
Expand All @@ -84,14 +88,17 @@ abstract class FederationSupport(

case class Query(
_entities: RepresentationsArgs => List[_Entity],
_service: ZQuery[Any, Nothing, _Service],
_fieldSet: FieldSet = FieldSet("")
_service: ZQuery[Any, Nothing, _Service]
)

val withSDL = original
.withAdditionalTypes(resolvers.map(_.toType).flatMap(Types.collectTypes(_)))
.withSchemaDirectives(schemaDirectives)

implicit val representationsArgsSchema: Schema[Any, RepresentationsArgs] = Schema.gen
implicit val serviceSchema: Schema[R, _Service] = genericSchema.gen[R, _Service]
implicit val querySchema: Schema[R, Query] = genericSchema.gen[R, Query]

graphQL[R, Query, Unit, Unit](
RootResolver(
Query(
Expand All @@ -100,7 +107,7 @@ abstract class FederationSupport(
)
),
supportedDirectives
) |+| original
).withAdditionalTypes(extraTypes) |+| original

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,30 @@ object FederationV1Spec extends ZIOSpecDefault {
)
)
}
},
test("introspection should include _Any and _FieldSet scalars") {
val interpreter = (graphQL(resolver) @@ federated(entityResolver)).interpreter

val query = gqldoc("""{ __schema { types { name } } }""")
interpreter
.flatMap(_.execute(query))
.map(d =>
ResponseValue.at(
PathValue.Key("__schema") :: PathValue.Key("types") :: PathValue.Key("name") :: Nil
)(d.data)
)
.map(responseValue =>
assertTrue(
responseValue
.is(_.subtype[ListValue])
.values
.contains(StringValue("_Any")),
responseValue
.is(_.subtype[ListValue])
.values
.contains(StringValue("_FieldSet"))
)
)
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package caliban.federation.v2x

import caliban.InputValue.{ ListValue, ObjectValue }
import caliban.Macros.gqldoc
import caliban.TestUtils.resolver
import caliban.Value.StringValue
import caliban.parsing.Parser
import caliban.parsing.adt.{ Definition, Directive }
Expand All @@ -11,7 +12,6 @@ import io.circe.Json
import io.circe.parser.decode
import zio.ZIO
import zio.test.Assertion.{ hasSameElements, isSome }
import zio.test.{ assertTrue, ZIOSpecDefault }
import zio.test._

object FederationV2Spec extends ZIOSpecDefault {
Expand Down Expand Up @@ -182,6 +182,30 @@ object FederationV2Spec extends ZIOSpecDefault {
)
)
}
},
test("introspection doesn't contain _FieldSet scalar") {
import caliban.federation.v2_3._
val interpreter = (graphQL(resolver) @@ federated).interpreter
val query = gqldoc("""{ __schema { types { name } } }""")
interpreter
.flatMap(_.execute(query))
.map(d =>
ResponseValue.at(
PathValue.Key("__schema") :: PathValue.Key("types") :: PathValue.Key("name") :: Nil
)(d.data)
)
.map(responseValue =>
assertTrue(
!responseValue
.is(_.subtype[ResponseValue.ListValue])
.values
.contains(StringValue("_Any")),
!responseValue
.is(_.subtype[ResponseValue.ListValue])
.values
.contains(StringValue("_FieldSet"))
)
)
}
)

Expand Down

0 comments on commit 7c7e940

Please sign in to comment.