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

caliban.wrappers.ApolloCaching.scala Maven / Gradle / Ivy

The newest version!
package caliban.wrappers

import caliban.CalibanError.ExecutionError
import caliban.ResponseValue.{ ListValue, ObjectValue }
import caliban.Value.{ EnumValue, IntValue, StringValue }
import caliban.execution.FieldInfo
import caliban.parsing.adt.Directive
import caliban.schema.Annotations.GQLDirective
import caliban.wrappers.Wrapper.{ EffectfulWrapper, FieldWrapper, OverallWrapper }
import caliban._
import zio._
import zio.query.ZQuery

import java.util.concurrent.TimeUnit

/**
 * Returns a wrapper which applies apollo caching response extensions
 */
@deprecated("Use `caliban.wrappers.Caching` for a more flexible implementation", "2.4.0")
object ApolloCaching {

  private val directiveName = "cacheControl"

  @deprecated("Use `caliban.wrappers.Caching` for a more flexible implementation", "2.4.0")
  case class GQLCacheControl(maxAge: Option[Duration] = None, scope: Option[CacheScope] = None)
      extends GQLDirective(CacheControl(scope, maxAge))

  object CacheControl {

    def apply(scope: Option[CacheScope], maxAge: Option[Duration]): Directive =
      (scope, maxAge) match {
        case (Some(scope), Some(age)) => apply(age, scope)
        case (None, Some(age))        => apply(age)
        case (Some(scope), None)      => apply(scope)
        case _                        => Directive(directiveName, Map.empty)
      }

    def apply(scope: ApolloCaching.CacheScope): Directive =
      Directive(directiveName, Map("scope" -> EnumValue(scope.toString)))

    def apply(maxAge: Duration): Directive =
      Directive(directiveName, Map("maxAge" -> IntValue(maxAge.toMillis / 1000)))

    def apply(maxAge: Duration, scope: ApolloCaching.CacheScope): Directive =
      Directive(directiveName, Map("maxAge" -> IntValue(maxAge.toMillis / 1000), "scope" -> EnumValue(scope.toString)))

  }

  val apolloCaching: EffectfulWrapper[Any] =
    EffectfulWrapper(
      Ref.make(Caching()).map(ref => apolloCachingOverall(ref) |+| apolloCachingField(ref))
    )

  sealed trait CacheScope

  object CacheScope {

    case object Private extends CacheScope {
      override def toString: String = "PRIVATE"
    }

    case object Public extends CacheScope {
      override def toString: String = "PUBLIC"
    }

  }

  case class CacheHint(
    fieldName: String = "",
    path: List[PathValue] = Nil,
    maxAge: Duration,
    scope: CacheScope
  ) {

    def toResponseValue: ResponseValue =
      ObjectValue(
        List(
          "path"   -> ListValue((PathValue.Key(fieldName) :: path).reverse),
          "maxAge" -> IntValue(maxAge.toMillis / 1000),
          "scope"  -> StringValue(scope match {
            case CacheScope.Private => "PRIVATE"
            case CacheScope.Public  => "PUBLIC"
          })
        )
      )

  }

  case class Caching(
    version: Int = 1,
    hints: List[CacheHint] = List.empty
  ) {

    def toResponseValue: ResponseValue =
      ObjectValue(List("version" -> IntValue(version), "hints" -> ListValue(hints.map(_.toResponseValue))))
  }

  case class CacheDirective(scope: Option[CacheScope] = None, maxAge: Option[Duration] = None)

  private def extractCacheDirective(directives: List[Directive]): Option[CacheDirective] =
    directives.collectFirst {
      case d if d.name == directiveName =>
        val scope = d.arguments.get("scope").collectFirst {
          case StringValue("PRIVATE") | EnumValue("PRIVATE") => CacheScope.Private
          case StringValue("PUBLIC") | EnumValue("PUBLIC")   => CacheScope.Public
        }

        val maxAge = d.arguments.get("maxAge").collectFirst { case i: IntValue =>
          Duration(i.toLong, TimeUnit.SECONDS)
        }

        CacheDirective(scope, maxAge)
    }

  private def apolloCachingOverall(ref: Ref[Caching]): OverallWrapper[Any] =
    new OverallWrapper[Any] {
      def wrap[R1 <: Any](
        process: GraphQLRequest => ZIO[R1, Nothing, GraphQLResponse[CalibanError]]
      ): GraphQLRequest => ZIO[R1, Nothing, GraphQLResponse[CalibanError]] =
        (request: GraphQLRequest) =>
          for {
            result <- process(request)
            cache  <- ref.get
          } yield result.copy(
            extensions = Some(
              ObjectValue(
                ("cacheControl" -> cache.toResponseValue) :: result.extensions.fold(
                  List.empty[(String, ResponseValue)]
                )(_.fields)
              )
            )
          )
    }

  private def apolloCachingField(ref: Ref[Caching]): FieldWrapper[Any] =
    new FieldWrapper(true) {
      def wrap[R1 <: Any](
        query: ZQuery[R1, ExecutionError, ResponseValue],
        fieldInfo: FieldInfo
      ): ZQuery[R1, ExecutionError, ResponseValue] = {
        val cacheDirectives = extractCacheDirective(
          fieldInfo.directives ++ fieldInfo.details.fieldType.ofType.flatMap(_.directives).getOrElse(Nil)
        )

        cacheDirectives.foldLeft(query) { case (q, cacheDirective) =>
          q <* ZQuery.fromZIO(
            ref.update(state =>
              state.copy(
                hints = CacheHint(
                  path = fieldInfo.path,
                  fieldName = fieldInfo.name,
                  maxAge = cacheDirective.maxAge getOrElse Duration.Zero,
                  scope = cacheDirective.scope getOrElse CacheScope.Private
                ) :: state.hints
              )
            )
          )
        }
      }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy