Last active
February 10, 2022 22:05
-
-
Save OlegIlyenko/ce3c8c4b3ab7afdd51a0faea4e3e6dc0 to your computer and use it in GitHub Desktop.
Example of Sangria middleware for Apollo Cache Control (https://github.com/apollographql/apollo-cache-control)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sangria.ast._ | |
import sangria.execution._ | |
import sangria.schema.Context | |
import sangria.marshalling.queryAst._ | |
import java.util.concurrent.ConcurrentLinkedQueue | |
import scala.concurrent.duration.FiniteDuration | |
import scala.collection.JavaConverters._ | |
object ApolloCacheExtension extends Middleware[Any] with MiddlewareExtension[Any] with MiddlewareAfterField[Any] { | |
type QueryVal = ConcurrentLinkedQueue[Value] | |
type FieldVal = Option[CacheHint] | |
def beforeQuery(context: MiddlewareQueryContext[Any, _, _]) = | |
new ConcurrentLinkedQueue | |
def afterQuery(queryVal: QueryVal, context: MiddlewareQueryContext[Any, _, _]) = () | |
def beforeField(queryVal: QueryVal, mctx: MiddlewareQueryContext[Any, _, _], ctx: Context[Any, _]) = { | |
val cacheControl = ctx.field.tags.collectFirst {case cc: CacheControl ⇒ cc} | |
if (cacheControl.isDefined) { | |
val hint = Some(new CacheHint(cacheControl)) | |
BeforeFieldResult(hint, attachment = hint) | |
} else noCache | |
} | |
def afterField(queryVal: QueryVal, fieldVal: FieldVal, value: Any, mctx: MiddlewareQueryContext[Any, _, _], ctx: Context[Any, _]) = { | |
fieldVal | |
.flatMap(_.cc) | |
.flatMap {case CacheControl(maxAge, scope) ⇒ cacheElement(ctx.path, maxAge, scope)} | |
.foreach(queryVal.add) | |
None | |
} | |
def cacheElement(path: ExecutionPath, maxAge: Option[FiniteDuration], scope: CacheScope.Value) = { | |
val pathField = Vector("path" → ListValue(path.path.map(queryAstResultMarshaller.scalarNode(_, "Any", Set.empty)))) | |
val maxAgeField = maxAge.map(ma ⇒ "maxAge" → BigIntValue(ma.toSeconds)).toVector | |
val scopeField = scope match { | |
case CacheScope.Private ⇒ Vector("scope" → StringValue("PRIVATE")) | |
case CacheScope.Public ⇒ Vector.empty | |
} | |
if (maxAgeField.nonEmpty || scopeField.nonEmpty) | |
Some(ObjectValue(pathField ++ maxAgeField ++ scopeField: _*)) | |
else | |
None | |
} | |
def afterQueryExtensions(queryVal: QueryVal, context: MiddlewareQueryContext[Any, _, _]): Vector[Extension[_]] = | |
Vector(Extension(ObjectValue( | |
"cacheControl" → ObjectValue( | |
"version" → IntValue(1), | |
"hints" → ListValue(queryVal.asScala.toVector))): Value)) | |
val noCache = BeforeFieldResult[Any, Option[CacheHint]](None, None, None) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sangria.execution.{FieldTag, MiddlewareAttachment} | |
import sangria.schema.Context | |
import scala.concurrent.duration.FiniteDuration | |
case class CacheControl(maxAge: Option[FiniteDuration], scope: CacheScope.Value) extends FieldTag | |
object CacheControl { | |
val empty = CacheControl(None, CacheScope.Public) | |
def apply(maxAge: FiniteDuration, scope: CacheScope.Value = CacheScope.Public): CacheControl = | |
CacheControl(Some(maxAge), scope) | |
def apply(scope: CacheScope.Value): CacheControl = | |
CacheControl(None, scope) | |
} | |
object CacheScope extends Enumeration { | |
val Private, Public = Value | |
} | |
class CacheHint(@volatile var cc: Option[CacheControl] = None) extends MiddlewareAttachment { | |
def cacheControl(maxAge: Option[FiniteDuration] = None, scope: CacheScope.Value = CacheScope.Public): Unit = | |
cc = Some(CacheControl(maxAge, scope)) | |
} | |
object CacheHint { | |
def cacheControl(ctx: Context[_, _], maxAge: FiniteDuration, scope: CacheScope.Value = CacheScope.Public): Unit = | |
ctx.attachment[CacheHint].foreach(_.cacheControl(Some(maxAge), scope)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sangria.macros._ | |
import sangria.macros.derive._ | |
import sangria.schema._ | |
import sangria.execution._ | |
import scala.concurrent.duration._ | |
import scala.concurrent.ExecutionContext.Implicits.global | |
case class Post(title: String, votes: Int, readByCurrentUser: Boolean) | |
object Post { | |
implicit val graphqlType = deriveObjectType[Unit, Post]( | |
FieldTags("votes", CacheControl(30 seconds)), | |
FieldTags("readByCurrentUser", CacheControl(CacheScope.Private))) | |
} | |
val IdArg = Argument("id", IDType) | |
val schema = Schema(ObjectType("Query", fields[Unit, Unit]( | |
Field("post", OptionType(Post.graphqlType), | |
tags = CacheControl(2 minutes) :: Nil, | |
arguments = IdArg :: Nil, | |
resolve = c ⇒ { | |
CacheHint.cacheControl(c, 60 seconds) | |
Post("Test post", 20, true) | |
})))) | |
val query = | |
graphql""" | |
query MyPost { | |
post(id: 1) { | |
title | |
votes | |
readByCurrentUser | |
} | |
} | |
""" | |
Executor.execute(schema, query, middleware = ApolloCacheExtension :: Nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"data": { | |
"post": { | |
"title": "Test post", | |
"votes": 20, | |
"readByCurrentUser": true | |
} | |
}, | |
"extensions": { | |
"cacheControl": { | |
"version": 1, | |
"hints": [{ | |
"path": ["post"], | |
"maxAge": 60 | |
}, { | |
"path": ["post", "votes"], | |
"maxAge": 30 | |
}, { | |
"path": ["post", "readByCurrentUser"], | |
"scope": "PRIVATE" | |
}] | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment