From a793fdad67dd5095dfed586530dae75fc8dadf6a Mon Sep 17 00:00:00 2001 From: Paul Daniels Date: Sun, 31 Oct 2021 23:12:03 +0800 Subject: [PATCH] Adding aspects and annotations --- core/src/main/scala/caliban/GraphQL.scala | 6 ++-- .../main/scala/caliban/GraphQLAspect.scala | 13 ++++++++ .../main/scala/caliban/wrappers/Wrapper.scala | 6 +++- .../example/federation/FederatedApi.scala | 32 +++++++++---------- .../scala/caliban/federation/Federation.scala | 14 +++++++- .../caliban/federation/FederationSpec.scala | 8 ++--- 6 files changed, 53 insertions(+), 26 deletions(-) create mode 100644 core/src/main/scala/caliban/GraphQLAspect.scala diff --git a/core/src/main/scala/caliban/GraphQL.scala b/core/src/main/scala/caliban/GraphQL.scala index 0736c9275..37658284f 100644 --- a/core/src/main/scala/caliban/GraphQL.scala +++ b/core/src/main/scala/caliban/GraphQL.scala @@ -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. diff --git a/core/src/main/scala/caliban/GraphQLAspect.scala b/core/src/main/scala/caliban/GraphQLAspect.scala new file mode 100644 index 000000000..c8c551425 --- /dev/null +++ b/core/src/main/scala/caliban/GraphQLAspect.scala @@ -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)) + } +} diff --git a/core/src/main/scala/caliban/wrappers/Wrapper.scala b/core/src/main/scala/caliban/wrappers/Wrapper.scala index 15ef81f83..aa1a59885 100644 --- a/core/src/main/scala/caliban/wrappers/Wrapper.scala +++ b/core/src/main/scala/caliban/wrappers/Wrapper.scala @@ -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. @@ -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 { diff --git a/examples/src/main/scala/example/federation/FederatedApi.scala b/examples/src/main/scala/example/federation/FederatedApi.scala index b33256374..e11d431f9 100644 --- a/examples/src/main/scala/example/federation/FederatedApi.scala +++ b/examples/src/main/scala/example/federation/FederatedApi.scala @@ -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} @@ -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 => @@ -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] { diff --git a/federation/src/main/scala/caliban/federation/Federation.scala b/federation/src/main/scala/caliban/federation/Federation.scala index 904d1bd65..7111a2485 100644 --- a/federation/src/main/scala/caliban/federation/Federation.scala +++ b/federation/src/main/scala/caliban/federation/Federation.scala @@ -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 { @@ -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. diff --git a/federation/src/test/scala/caliban/federation/FederationSpec.scala b/federation/src/test/scala/caliban/federation/FederationSpec.scala index 5d018f6f5..bb682d807 100644 --- a/federation/src/test/scala/caliban/federation/FederationSpec.scala +++ b/federation/src/test/scala/caliban/federation/FederationSpec.scala @@ -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 { @@ -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 { @@ -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))( @@ -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"}]) {