Skip to content

Commit

Permalink
Adding aspects and annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
paulpdaniels committed Nov 6, 2021
1 parent 338123c commit a793fda
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 26 deletions.
6 changes: 2 additions & 4 deletions core/src/main/scala/caliban/GraphQL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,8 @@ trait GraphQL[-R] { self =>
override val additionalDirectives: List[__Directive] = self.additionalDirectives
}

/**
* A symbolic alias for `withWrapper`.
*/
final def @@[R2 <: R](wrapper: Wrapper[R2]): GraphQL[R2] = withWrapper(wrapper)
final def @@[LowerR <: UpperR, UpperR <: R](aspect: GraphQLAspect[LowerR, UpperR]): GraphQL[UpperR] =
aspect(self)

/**
* Merges this GraphQL API with another GraphQL API.
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/scala/caliban/GraphQLAspect.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package caliban

trait GraphQLAspect[+LowerR, -UpperR] { self =>
def apply[R >: LowerR <: UpperR](gql: GraphQL[R]): GraphQL[R]

def @@[LowerR1 >: LowerR, UpperR1 <: UpperR](
other: GraphQLAspect[LowerR1, UpperR1]
): GraphQLAspect[LowerR1, UpperR1] =
new GraphQLAspect[LowerR1, UpperR1] {
def apply[R >: LowerR1 <: UpperR1](gql: GraphQL[R]): GraphQL[R] =
other(self(gql))
}
}
6 changes: 5 additions & 1 deletion core/src/main/scala/caliban/wrappers/Wrapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import caliban.wrappers.Wrapper.CombinedWrapper
import caliban.{ CalibanError, GraphQLRequest, GraphQLResponse, ResponseValue }
import zio.{ UIO, ZIO }
import zio.query.ZQuery
import caliban.{ GraphQL, GraphQLAspect }

/**
* A `Wrapper[-R]` represents an extra layer of computation that can be applied on top of Caliban's query handling.
Expand All @@ -21,8 +22,11 @@ import zio.query.ZQuery
*
* It is also possible to combine wrappers using `|+|` and to build a wrapper effectfully with `EffectfulWrapper`.
*/
sealed trait Wrapper[-R] { self =>
sealed trait Wrapper[-R] extends GraphQLAspect[Nothing, R] { self =>
def |+|[R1 <: R](that: Wrapper[R1]): Wrapper[R1] = CombinedWrapper(List(self, that))

def apply[R1 <: R](that: GraphQL[R1]): GraphQL[R1] =
that.withWrapper(self)
}

object Wrapper {
Expand Down
32 changes: 16 additions & 16 deletions examples/src/main/scala/example/federation/FederatedApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import example.federation.CharacterService.CharacterService
import example.federation.EpisodeService.EpisodeService

import caliban.GraphQL.graphQL
import caliban.federation.{EntityResolver, federate}
import caliban.federation.{EntityResolver, federated}
import caliban.federation.tracing.ApolloFederatedTracing
import caliban.schema.Annotations.{GQLDeprecated, GQLDescription}
import caliban.schema.{ArgBuilder, GenericSchema, Schema}
Expand Down Expand Up @@ -48,19 +48,9 @@ object FederatedApi {
implicit val episodeArgs = gen[EpisodeArgs]
implicit val episodeArgBuilder: ArgBuilder[EpisodeArgs] = ArgBuilder.gen[EpisodeArgs]

val api: GraphQL[Console with Clock with CharacterService] =
federate(
graphQL(
RootResolver(
Queries(
args => CharacterService.getCharacters(args.origin),
args => CharacterService.findCharacter(args.name)
),
Mutations(args => CharacterService.deleteCharacter(args.name))
)
) @@ standardWrappers,
EntityResolver.from[CharacterArgs](args => ZQuery.fromEffect(CharacterService.findCharacter(args.name))),
EntityResolver.from[EpisodeArgs](args =>
val withFederation = federated(
EntityResolver.from[CharacterArgs](args => ZQuery.fromEffect(CharacterService.findCharacter(args.name))),
EntityResolver.from[EpisodeArgs](args =>
ZQuery
.fromEffect(CharacterService.getCharactersByEpisode(args.season, args.episode))
.map(characters =>
Expand All @@ -72,8 +62,18 @@ object FederatedApi {
)
)
)
)
)
))

val api: GraphQL[Console with Clock with CharacterService] =
graphQL(
RootResolver(
Queries(
args => CharacterService.getCharacters(args.origin),
args => CharacterService.findCharacter(args.name)
),
Mutations(args => CharacterService.deleteCharacter(args.name))
)
) @@ standardWrappers @@ withFederation
}

object Episodes extends GenericSchema[EpisodeService] {
Expand Down
14 changes: 13 additions & 1 deletion federation/src/main/scala/caliban/federation/Federation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import caliban.introspection.adt._
import caliban.parsing.adt.Directive
import caliban.schema.Step.QueryStep
import caliban.schema._
import caliban.{ CalibanError, GraphQL, InputValue, RootResolver }
import caliban.{ CalibanError, GraphQL, GraphQLAspect, InputValue, RootResolver }
import zio.query.ZQuery

trait Federation {
Expand Down Expand Up @@ -48,6 +48,18 @@ trait Federation {
GraphQL.graphQL(RootResolver(Query(_service = _Service(original.render))), federationDirectives) |+| original
}

def federated[R](resolver: EntityResolver[R], others: EntityResolver[R]*): GraphQLAspect[Nothing, R] =
new GraphQLAspect[Nothing, R] {
def apply[R1 <: R](original: GraphQL[R1]): GraphQL[R1] =
federate(original, resolver, others: _*)
}

lazy val federated: GraphQLAspect[Nothing, Any] =
new GraphQLAspect[Nothing, Any] {
def apply[R1](original: GraphQL[R1]): GraphQL[R1] =
federate(original)
}

/**
* Accepts a GraphQL as well as entity resolvers in order to support more advanced federation use cases. This variant
* will allow the gateway to query for entities by resolver.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ object FederationSpec extends DefaultRunnableSpec {

override def spec = suite("FederationSpec")(
testM("should resolve federated types") {
val interpreter = federate(graphQL(resolver), entityResolver).interpreter
val interpreter = (graphQL(resolver) @@ federated(entityResolver)).interpreter

val query = gqldoc("""
query test {
Expand All @@ -69,7 +69,7 @@ object FederationSpec extends DefaultRunnableSpec {
)
},
testM("should not include _entities if not resolvers provided") {
val interpreter = federate(graphQL(resolver)).interpreter
val interpreter = (graphQL(resolver) @@ federated).interpreter

val query = gqldoc("""
query test {
Expand All @@ -93,7 +93,7 @@ object FederationSpec extends DefaultRunnableSpec {
)
},
testM("should include orphan entities in sdl") {
val interpreter = federate(graphQL(resolver), orphanResolver).interpreter
val interpreter = (graphQL(resolver) @@ federated(orphanResolver)).interpreter

val query = gqldoc("""{ _service { sdl } }""")
assertM(interpreter.flatMap(_.execute(query)).map(d => d.data.toString))(
Expand All @@ -106,7 +106,7 @@ object FederationSpec extends DefaultRunnableSpec {
)
},
testM("should include field metadata") {
val interpreter = federate(graphQL(resolver), functionEntityResolver).interpreter
val interpreter = (graphQL(resolver) @@ federated(functionEntityResolver)).interpreter
val query = gqldoc("""
query Entities($withNicknames: Boolean = false) {
_entities(representations: [{__typename: "Character", name: "Amos Burton"}]) {
Expand Down

0 comments on commit a793fda

Please sign in to comment.