All Downloads are FREE. Search and download functionalities are using the official Maven repository.

caliban.federation.FederationSupport.scala Maven / Gradle / Ivy

The newest version!
package caliban.federation

import caliban._
import caliban.introspection.adt._
import caliban.parsing.adt.Directive
import caliban.schema.Step.QueryStep
import caliban.schema._
import zio.query.ZQuery

abstract class FederationSupport(
  supportedDirectives: List[__Directive],
  schemaDirectives: List[Directive]
) {
  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] = {
    case class Query(
      _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
    ).withAdditionalTypes(extraTypes) |+| 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.
   * @param original The original schema
   * @param resolver A type which can resolve a single type by a key which is provided per type using the @key directive
   * @param otherResolvers Additional resolvers to supply
   */
  def federate[R](original: GraphQL[R], resolver: EntityResolver[R], otherResolvers: EntityResolver[R]*): GraphQL[R] = {

    val resolvers = resolver +: otherResolvers.toList

    val genericSchema = new GenericSchema[R] {}

    implicit val entitySchema: Schema[R, _Entity] = new Schema[R, _Entity] {
      override def nullable: Boolean                                         = true
      override def toType(isInput: Boolean, isSubscription: Boolean): __Type =
        __Type(
          __TypeKind.UNION,
          name = Some("_Entity"),
          possibleTypes = Some(resolvers.map(_.toType))
        )

      private lazy val _entityMap = resolvers.flatMap(r => r.toType.name.map(_ -> r)).toMap

      /**
       * Resolves `T` by turning a value of type `T` into an execution step that describes how to resolve the value.
       *
       * @param value a value of type `T`
       */
      override def resolve(value: _Entity): Step[R] =
        _entityMap
          .get(value.__typename)
          .fold[Step[R]](Step.NullStep)(resolver => QueryStep(resolver.resolve(value.value)))

    }

    case class Query(
      _entities: RepresentationsArgs => List[_Entity],
      _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(
          _entities = args => args.representations.map(rep => _Entity(rep.__typename, rep.fields)),
          _service = ZQuery.succeed(_Service(withSDL.render))
        )
      ),
      supportedDirectives
    ).withAdditionalTypes(extraTypes) |+| original

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy