sangria.execution.FutureResolver.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sangria-core_2.13 Show documentation
Show all versions of sangria-core_2.13 Show documentation
Scala GraphQL implementation
The newest version!
package sangria.execution
import sangria.ast
import sangria.ast.{AstLocation, Document, SourceMapper}
import sangria.execution.deferred.{Deferred, DeferredResolver}
import sangria.marshalling.ResultMarshaller
import sangria.schema._
import sangria.streaming.SubscriptionStream
import scala.annotation.tailrec
import scala.collection.immutable.{ListMap, VectorBuilder}
import scala.collection.mutable
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.control.NonFatal
import scala.util.{Failure, Success}
private[execution] object FutureResolverBuilder extends ResolverBuilder {
override def build[Ctx](
marshaller: ResultMarshaller,
middlewareCtx: MiddlewareQueryContext[Ctx, _, _],
schema: Schema[Ctx, _],
valueCollector: ValueCollector[Ctx, _],
variables: Map[String, VariableValue],
fieldCollector: FieldCollector[Ctx, _],
userContext: Ctx,
exceptionHandler: ExceptionHandler,
deferredResolver: DeferredResolver[Ctx],
sourceMapper: Option[SourceMapper],
deprecationTracker: Option[DeprecationTracker],
middleware: List[(Any, Middleware[Ctx])],
maxQueryDepth: Option[Int],
deferredResolverState: Any,
preserveOriginalErrors: Boolean,
validationTiming: TimeMeasurement,
queryReducerTiming: TimeMeasurement,
queryAst: Document)(implicit executionContext: ExecutionContext): Resolver[Ctx] =
new FutureResolver[Ctx](
marshaller,
middlewareCtx,
schema,
valueCollector,
variables,
fieldCollector,
userContext,
exceptionHandler,
deferredResolver,
sourceMapper,
deprecationTracker,
middleware,
maxQueryDepth,
deferredResolverState,
preserveOriginalErrors,
validationTiming,
queryReducerTiming,
queryAst
)
}
/** [[Resolver]] using [[scala.concurrent.Future]] and [[scala.concurrent.Promise]] as base
* asynchronous primitives
*/
private[execution] class FutureResolver[Ctx](
val marshaller: ResultMarshaller,
middlewareCtx: MiddlewareQueryContext[Ctx, _, _],
schema: Schema[Ctx, _],
valueCollector: ValueCollector[Ctx, _],
variables: Map[String, VariableValue],
fieldCollector: FieldCollector[Ctx, _],
userContext: Ctx,
exceptionHandler: ExceptionHandler,
deferredResolver: DeferredResolver[Ctx],
sourceMapper: Option[SourceMapper],
deprecationTracker: Option[DeprecationTracker],
middleware: List[(Any, Middleware[Ctx])],
maxQueryDepth: Option[Int],
deferredResolverState: Any,
preserveOriginalErrors: Boolean,
validationTiming: TimeMeasurement,
queryReducerTiming: TimeMeasurement,
queryAst: ast.Document
)(implicit executionContext: ExecutionContext)
extends Resolver[Ctx] {
private val resultResolver =
new ResultResolver(marshaller, exceptionHandler, preserveOriginalErrors)
private val toScalarMiddleware =
Middleware.composeToScalarMiddleware(middleware.map(_._2), userContext)
import Resolver._
import resultResolver._
override def resolveFieldsPar(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)(
scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] = {
val actions =
collectActionsPar(ExecutionPath.empty, tpe, value, fields, ErrorRegistry.empty, userContext)
handleScheme(
processFinalResolve(
resolveActionsPar(ExecutionPath.empty, tpe, actions, userContext, fields.namesOrdered))
.map(_ -> userContext),
scheme)
}
override def resolveFieldsSeq(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)(
scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] = {
val result = resolveSeq(ExecutionPath.empty, tpe, value, fields)
handleScheme(result.flatMap(res => processFinalResolve(res._1).map(_ -> res._2)), scheme)
}
override def resolveFieldsSubs(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)(
scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] =
scheme match {
case ExecutionScheme.Default =>
val (s, res) = resolveSubs[({ type X[_] })#X](
ExecutionPath.empty,
tpe,
value,
fields,
ErrorRegistry.empty,
None)
s.first(res).map(_._2).asInstanceOf[scheme.Result[Ctx, marshaller.Node]]
case ExecutionScheme.Extended =>
val (s, res) = resolveSubs[({ type X[_] })#X](
ExecutionPath.empty,
tpe,
value,
fields,
ErrorRegistry.empty,
None)
s.first(res)
.map { case (errors, res) =>
ExecutionResult(
userContext,
res,
errors,
middleware,
validationTiming,
queryReducerTiming)
}
.asInstanceOf[scheme.Result[Ctx, marshaller.Node]]
case es: ExecutionScheme.StreamBasedExecutionScheme[
({ type X[_] })#X @unchecked] @unchecked =>
val (_, res) = resolveSubs(
ExecutionPath.empty,
tpe,
value,
fields,
ErrorRegistry.empty,
Some(es.subscriptionStream))
es.subscriptionStream
.map(res) {
case (errors, r) if es.extended =>
ExecutionResult(
userContext,
r,
errors,
middleware,
validationTiming,
queryReducerTiming)
case (_, r) => r
}
.asInstanceOf[scheme.Result[Ctx, marshaller.Node]]
case s =>
throw new IllegalStateException(s"Unsupported execution scheme: $s")
}
protected def handleScheme(
result: Future[((Vector[RegisteredError], marshaller.Node), Ctx)],
scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] = scheme match {
case ExecutionScheme.Default =>
result.map { case ((_, res), _) => res }.asInstanceOf[scheme.Result[Ctx, marshaller.Node]]
case ExecutionScheme.Extended =>
result
.map { case ((errors, res), uc) =>
ExecutionResult(uc, res, errors, middleware, validationTiming, queryReducerTiming)
}
.asInstanceOf[scheme.Result[Ctx, marshaller.Node]]
case s: ExecutionScheme.StreamBasedExecutionScheme[_] @unchecked =>
s.subscriptionStream
.singleFuture(result.map {
case ((errors, res), uc) if s.extended =>
ExecutionResult(uc, res, errors, middleware, validationTiming, queryReducerTiming)
case ((_, res), _) => res
})
.asInstanceOf[scheme.Result[Ctx, marshaller.Node]]
case s =>
throw new IllegalStateException(s"Unsupported execution scheme: $s")
}
private def processFinalResolve(resolve: Resolve) = resolve match {
case Result(errors, data, _) =>
Future.successful(
errors.originalErrors ->
marshalResult(
data.asInstanceOf[Option[resultResolver.marshaller.Node]],
marshalErrors(errors),
marshallExtensions.asInstanceOf[Option[resultResolver.marshaller.Node]],
beforeExecution = false
).asInstanceOf[marshaller.Node])
case dr: DeferredResult =>
immediatelyResolveDeferred(
userContext,
dr,
_.map { case Result(errors, data, _) =>
errors.originalErrors ->
marshalResult(
data.asInstanceOf[Option[resultResolver.marshaller.Node]],
marshalErrors(errors),
marshallExtensions.asInstanceOf[Option[resultResolver.marshaller.Node]],
beforeExecution = false
).asInstanceOf[marshaller.Node]
}
)
}
private def marshallExtensions: Option[marshaller.Node] = {
val extensions =
middleware.flatMap {
case (v, m: MiddlewareExtension[Ctx]) =>
m.afterQueryExtensions(v.asInstanceOf[m.QueryVal], middlewareCtx)
case _ => Nil
}
if (extensions.nonEmpty) ResultResolver.marshalExtensions(marshaller, extensions)
else None
}
private def immediatelyResolveDeferred[T](
uc: Ctx,
dr: DeferredResult,
fn: Future[Result] => Future[T]): Future[T] = {
val res = fn(dr.futureValue)
resolveDeferredWithGrouping(dr.deferred).foreach(groups =>
groups.foreach(group => resolveDeferred(uc, group)))
res
}
private def resolveDeferredWithGrouping(deferred: Vector[Future[Vector[Defer]]]) =
Future.sequence(deferred).map(listOfDef => deferredResolver.groupDeferred(listOfDef.flatten))
private type Actions = (
ErrorRegistry,
Option[Vector[(
Vector[ast.Field],
Option[(Field[Ctx, _], Option[MappedCtxUpdate[Ctx, Any, Any]], LeafAction[Ctx, _])])]])
private def resolveSubs[S[_]](
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
value: Any,
fields: CollectedFields,
errorReg: ErrorRegistry,
requestedStream: Option[SubscriptionStream[S]])
: (SubscriptionStream[S], S[(Vector[RegisteredError], marshaller.Node)]) = {
val firstStream = tpe.uniqueFields.head.tags
.collectFirst { case SubscriptionField(s) => s }
.get
.asInstanceOf[SubscriptionStream[S]]
val stream = requestedStream.fold(firstStream) { s =>
if (s.supported(firstStream)) s
else
throw new IllegalStateException(
"Subscription type field stream implementation is incompatible with requested stream implementation")
}
def marshallResult(result: Result): Any =
stream.single(result)
val fieldStreams = fields.fields.flatMap {
case CollectedField(_, origField, _) if tpe.getField(schema, origField.name).isEmpty =>
None
case CollectedField(_, origField, Failure(error)) =>
val resMap = marshaller.emptyMapNode(Seq(origField.outputName))
Some(
marshallResult(Result(
errorReg.add(path.add(origField, tpe), error),
if (isOptional(tpe, origField.name))
Some(marshaller
.addMapNodeElem(resMap, origField.outputName, marshaller.nullNode, optional = true))
else None
)))
case CollectedField(_, origField, Success(fields)) =>
resolveField(
userContext,
tpe,
path.add(origField, tpe),
value,
ErrorRegistry.empty,
fields) match {
case _: StandardFieldResolution =>
throw new IllegalStateException(
"StandardFieldResolution is not supposed to appear here")
case ErrorFieldResolution(updatedErrors) if isOptional(tpe, origField.name) =>
val resMap = marshaller.emptyMapNode(Seq(origField.outputName))
Some(
marshallResult(
Result(
updatedErrors,
Some(
marshaller.addMapNodeElem(
resMap,
fields.head.outputName,
marshaller.nullNode,
optional = isOptional(tpe, origField.name)))
)))
case ErrorFieldResolution(updatedErrors) =>
Some(marshallResult(Result(updatedErrors, None)))
case StreamFieldResolution(updatedErrors, svalue, standardFn) =>
val s = svalue.stream.mapFuture[Any, Result](svalue.source) { action =>
val res =
Result(updatedErrors, Some(marshaller.emptyMapNode(Seq(origField.outputName))))
val standardAction = standardFn(action)
resolveStandardFieldResolutionSeq(
path,
userContext,
tpe,
origField,
fields,
res,
standardAction)
.map(_._1)
}
val recovered = svalue.stream.recover(s) { e =>
val resMap = marshaller.emptyMapNode(Seq(origField.outputName))
Result(
updatedErrors.add(path.add(origField, tpe), e),
if (isOptional(tpe, origField.name))
Some(
marshaller.addMapNodeElem(
resMap,
origField.outputName,
marshaller.nullNode,
optional = true))
else None
)
}
Some(recovered)
}
}
stream -> stream.mapFuture(stream.merge(fieldStreams.asInstanceOf[Vector[S[Result]]]))(r =>
processFinalResolve(r.buildValue))
}
private def resolveSeq(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
value: Any,
fields: CollectedFields): Future[(Result, Ctx)] =
fields.fields
.foldLeft(
Future.successful(
(
Result(ErrorRegistry.empty, Some(marshaller.emptyMapNode(fields.namesOrdered))),
userContext))) { case (future, elem) =>
future.flatMap { resAndCtx =>
(resAndCtx, elem) match {
case (acc @ (Result(_, None, _), _), _) => Future.successful(acc)
case (acc, CollectedField(_, origField, _))
if tpe.getField(schema, origField.name).isEmpty =>
Future.successful(acc)
case (
(Result(errors, Some(acc), _), uc),
CollectedField(_, origField, Failure(error))) =>
Future.successful(Result(
errors.add(path.add(origField, tpe), error),
if (isOptional(tpe, origField.name))
Some(
marshaller.addMapNodeElem(
acc.asInstanceOf[marshaller.MapBuilder],
origField.outputName,
marshaller.nullNode,
optional = true))
else None
) -> uc)
case (
(accRes @ Result(errors, Some(acc), _), uc),
CollectedField(_, origField, Success(fields))) =>
resolveSingleFieldSeq(path, uc, tpe, value, errors, origField, fields, accRes, acc)
}
}
}
.map { case (res, ctx) =>
res.buildValue -> ctx
}
private def resolveSingleFieldSeq(
path: ExecutionPath,
uc: Ctx,
tpe: ObjectType[Ctx, _],
value: Any,
errors: ErrorRegistry,
origField: ast.Field,
fields: Vector[ast.Field],
accRes: Result,
acc: Any // from `accRes`
): Future[(Result, Ctx)] =
resolveField(uc, tpe, path.add(origField, tpe), value, errors, fields) match {
case ErrorFieldResolution(updatedErrors) if isOptional(tpe, origField.name) =>
Future.successful(
Result(
updatedErrors,
Some(
marshaller.addMapNodeElem(
acc.asInstanceOf[marshaller.MapBuilder],
fields.head.outputName,
marshaller.nullNode,
optional = isOptional(tpe, origField.name)))
) -> uc)
case ErrorFieldResolution(updatedErrors) =>
Future.successful(Result(updatedErrors, None) -> uc)
case resolution: StandardFieldResolution =>
resolveStandardFieldResolutionSeq(path, uc, tpe, origField, fields, accRes, resolution)
case _: StreamFieldResolution[_, _] =>
Future.failed(
new IllegalStateException("StreamFieldResolution is not supposed to happen here"))
}
private def resolveStandardFieldResolutionSeq(
path: ExecutionPath,
uc: Ctx,
tpe: ObjectType[Ctx, _],
origField: ast.Field,
fields: Vector[ast.Field],
accRes: Result,
resolution: StandardFieldResolution
): Future[(Result, Ctx)] = {
val StandardFieldResolution(updatedErrors, result, newUc) = resolution
val sfield = tpe.getField(schema, origField.name).head
val fieldPath = path.add(fields.head, tpe)
def resolveUc(v: Any) = newUc.fold(uc)(_.ctxFn(v))
def resolveError(e: Throwable) = {
try newUc.foreach(_.onError(e))
catch {
case NonFatal(ee) => ee.printStackTrace()
}
e
}
def resolveVal(v: Any) = newUc match {
case Some(MappedCtxUpdate(_, mapFn, _)) => mapFn(v)
case None => v
}
val resolve =
try {
result match {
case Value(v) =>
val updatedUc = resolveUc(v)
Future.successful(
resolveValue(
fieldPath,
fields,
sfield.fieldType,
sfield,
resolveVal(v),
updatedUc) -> updatedUc)
case SequenceLeafAction(actions) =>
val values = resolveActionSequenceValues(fieldPath, fields, sfield, actions)
val future = Future.sequence(values.map(_.value))
val resolved = future
.flatMap { vs =>
val errors = vs.iterator.flatMap(_.errors).toVector
val successfulValues = vs.collect { case SeqFutRes(v, _, _) if v != null => v }
val dctx = vs.collect { case SeqFutRes(_, _, d) if d != null => d }
def resolveDctx(resolve: Resolve) = {
val last = dctx.lastOption
val init = if (dctx.isEmpty) dctx else dctx.init
resolve match {
case res: Result =>
dctx.foreach(_.promise.success(Vector.empty))
Future.successful(res)
case res: DeferredResult =>
init.foreach(_.promise.success(Vector.empty))
last.foreach(_.promise.success(res.deferred))
res.futureValue
}
}
errors.foreach(resolveError)
if (successfulValues.size == vs.size)
resolveDctx(
resolveValue(
fieldPath,
fields,
sfield.fieldType,
sfield,
resolveVal(successfulValues),
resolveUc(successfulValues))
.appendErrors(fieldPath, errors, fields.head.location))
else
resolveDctx(
Result(
ErrorRegistry.empty.append(fieldPath, errors, fields.head.location),
None))
}
.recover { case e =>
Result(ErrorRegistry(fieldPath, resolveError(e), fields.head.location), None)
}
val deferred = values.iterator.collect {
case SeqRes(_, d, _) if d != null => d
}.toVector
val deferredFut = values.iterator.collect {
case SeqRes(_, _, d) if d != null => d
}.toVector
immediatelyResolveDeferred(
uc,
DeferredResult(Future.successful(deferred) +: deferredFut, resolved),
_.map(r => r -> r.userContext.getOrElse(uc)))
case PartialValue(v, es) =>
val updatedUc = resolveUc(v)
es.foreach(resolveError)
Future.successful(
resolveValue(fieldPath, fields, sfield.fieldType, sfield, resolveVal(v), updatedUc)
.appendErrors(fieldPath, es, fields.head.location) -> updatedUc)
case TryValue(v) =>
Future.successful(v match {
case Success(success) =>
val updatedUc = resolveUc(success)
resolveValue(
fieldPath,
fields,
sfield.fieldType,
sfield,
resolveVal(success),
updatedUc) -> updatedUc
case Failure(e) =>
Result(ErrorRegistry(fieldPath, resolveError(e), fields.head.location), None) -> uc
})
case DeferredValue(d) =>
val p = Promise[(ChildDeferredContext, Any, Vector[Throwable])]()
val (args, complexity) = calcComplexity(fieldPath, origField, sfield, userContext)
val defer = Defer(p, d, complexity, sfield, fields, args)
immediatelyResolveDeferred(
uc,
DeferredResult(
Vector(Future.successful(Vector(defer))),
p.future
.flatMap { case (dctx, v, es) =>
val updatedUc = resolveUc(v)
es.foreach(resolveError)
resolveValue(
fieldPath,
fields,
sfield.fieldType,
sfield,
resolveVal(v),
updatedUc).appendErrors(fieldPath, es, fields.head.location) match {
case r: Result => dctx.resolveResult(r.copy(userContext = Some(updatedUc)))
case er: DeferredResult =>
dctx
.resolveDeferredResult(er)
.map(_.copy(userContext = Some(updatedUc)))
}
}
.recover { case e =>
Result(ErrorRegistry(fieldPath, resolveError(e), fields.head.location), None)
}
),
_.map(r => r -> r.userContext.getOrElse(uc))
)
case FutureValue(f) =>
f.map { v =>
val updatedUc = resolveUc(v)
resolveValue(
fieldPath,
fields,
sfield.fieldType,
sfield,
resolveVal(v),
updatedUc) -> updatedUc
}.recover { case e =>
Result(
ErrorRegistry(path.add(origField, tpe), resolveError(e), fields.head.location),
None) -> uc
}
case PartialFutureValue(f) =>
f.map { case PartialValue(v, es) =>
val updatedUc = resolveUc(v)
es.foreach(resolveError)
resolveValue(fieldPath, fields, sfield.fieldType, sfield, resolveVal(v), updatedUc)
.appendErrors(fieldPath, es, fields.head.location) -> updatedUc
}.recover { case e =>
Result(
ErrorRegistry(path.add(origField, tpe), resolveError(e), fields.head.location),
None) -> uc
}
case DeferredFutureValue(df) =>
val p = Promise[(ChildDeferredContext, Any, Vector[Throwable])]()
def defer(d: Deferred[Any]) = {
val (args, complexity) = calcComplexity(fieldPath, origField, sfield, userContext)
Defer(p, d, complexity, sfield, fields, args)
}
val actualDeferred = df
.map(d => Vector(defer(d)))
.recover { case NonFatal(e) =>
p.failure(e)
Vector.empty
}
immediatelyResolveDeferred(
uc,
DeferredResult(
Vector(actualDeferred),
p.future
.flatMap { case (dctx, v, es) =>
val updatedUc = resolveUc(v)
es.foreach(resolveError)
resolveValue(
fieldPath,
fields,
sfield.fieldType,
sfield,
resolveVal(v),
updatedUc).appendErrors(fieldPath, es, fields.head.location) match {
case r: Result => dctx.resolveResult(r.copy(userContext = Some(updatedUc)))
case er: DeferredResult =>
dctx
.resolveDeferredResult(er)
.map(_.copy(userContext = Some(updatedUc)))
}
}
.recover { case e =>
Result(ErrorRegistry(fieldPath, resolveError(e), fields.head.location), None)
}
),
_.map(r => r -> r.userContext.getOrElse(uc))
)
case SubscriptionValue(_, _) =>
Future.failed(
new IllegalStateException(
"Subscription values are not supported for normal operations"))
case e =>
Future.failed(
new IllegalStateException(s"${e.getClass.toString} is not supposed to appear here"))
}
} catch {
case NonFatal(e) =>
Future.successful(
Result(ErrorRegistry(fieldPath, resolveError(e), fields.head.location), None) -> uc)
}
resolve.flatMap {
case (r: Result, newUc) =>
Future.successful(
accRes.addToMap(
r,
fields.head.outputName,
isOptional(tpe, fields.head.name),
fieldPath,
fields.head.location,
updatedErrors) -> newUc)
case (dr: DeferredResult, newUc) =>
immediatelyResolveDeferred(
newUc,
dr,
_.map(
accRes.addToMap(
_,
fields.head.outputName,
isOptional(tpe, fields.head.name),
fieldPath,
fields.head.location,
updatedErrors) -> newUc))
}
}
private def calcComplexity(
path: ExecutionPath,
astField: ast.Field,
field: Field[Ctx, _],
uc: Ctx) = {
val args = valueCollector.getFieldArgumentValues(
path,
Some(astField),
field.arguments,
astField.arguments,
variables)
args match {
case Success(a) => a -> field.complexity.fold(DefaultComplexity)(_(uc, a, DefaultComplexity))
case _ => Args.empty -> DefaultComplexity
}
}
private def collectActionsPar(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
value: Any,
fields: CollectedFields,
errorReg: ErrorRegistry,
userCtx: Ctx): Actions =
fields.fields.foldLeft((errorReg, Some(Vector.empty)): Actions) {
case (acc @ (_, None), _) => acc
case (acc, CollectedField(_, origField, _)) if tpe.getField(schema, origField.name).isEmpty =>
acc
case ((errors, Some(acc)), CollectedField(_, origField, Failure(error))) =>
errors.add(path.add(origField, tpe), error) -> (if (isOptional(tpe, origField.name))
Some(acc :+ (Vector(origField) -> None))
else None)
case ((errors, Some(acc)), CollectedField(_, origField, Success(fields))) =>
resolveField(userCtx, tpe, path.add(origField, tpe), value, errors, fields) match {
case StandardFieldResolution(updatedErrors, result, updateCtx) =>
updatedErrors -> Some(
acc :+ (fields -> Some(
(tpe.getField(schema, origField.name).head, updateCtx, result))))
case ErrorFieldResolution(updatedErrors) if isOptional(tpe, origField.name) =>
updatedErrors -> Some(acc :+ (Vector(origField) -> None))
case ErrorFieldResolution(updatedErrors) => updatedErrors -> None
case _: StreamFieldResolution[_, _] =>
throw new IllegalStateException("IllegalStateException is not supposed to happen here")
}
}
private def resolveActionSequenceValues(
fieldsPath: ExecutionPath,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
actions: Seq[LeafAction[Any, Any]]): Seq[SeqRes] =
actions.map {
case Value(v) => SeqRes(SeqFutRes(v))
case TryValue(Success(v)) => SeqRes(SeqFutRes(v))
case TryValue(Failure(e)) => SeqRes(SeqFutRes(errors = Vector(e)))
case PartialValue(v, es) => SeqRes(SeqFutRes(v, es))
case FutureValue(future) =>
SeqRes(future.map(v => SeqFutRes(v)).recover { case e => SeqFutRes(errors = Vector(e)) })
case PartialFutureValue(future) =>
SeqRes(future.map { case PartialValue(v, es) => SeqFutRes(v, es) }.recover { case e =>
SeqFutRes(errors = Vector(e))
})
case DeferredValue(deferred) =>
val promise = Promise[(ChildDeferredContext, Any, Vector[Throwable])]()
val (args, complexity) = calcComplexity(fieldsPath, astFields.head, field, userContext)
val defer = Defer(promise, deferred, complexity, field, astFields, args)
SeqRes(
promise.future.map { case (dctx, v, es) => SeqFutRes(v, es, dctx) }.recover { case e =>
SeqFutRes(errors = Vector(e))
},
defer)
case DeferredFutureValue(deferredValue) =>
val promise = Promise[(ChildDeferredContext, Any, Vector[Throwable])]()
def defer(d: Deferred[Any]) = {
val (args, complexity) = calcComplexity(fieldsPath, astFields.head, field, userContext)
Defer(promise, d, complexity, field, astFields, args)
}
val actualDeferred = deferredValue
.map(d => Vector(defer(d)))
.recover { case NonFatal(e) =>
promise.failure(e)
Vector.empty
}
SeqRes(
promise.future.map { case (dctx, v, es) => SeqFutRes(v, es, dctx) }.recover { case e =>
SeqFutRes(errors = Vector(e))
},
actualDeferred)
case SequenceLeafAction(_) | _: MappedSequenceLeafAction[_, _, _] =>
SeqRes(SeqFutRes(errors = Vector(new IllegalStateException(
"Nested `SequenceLeafAction` is not yet supported inside of another `SequenceLeafAction`"))))
case SubscriptionValue(_, _) =>
SeqRes(
SeqFutRes(errors = Vector(new IllegalStateException(
"Subscription values are not supported for normal operations"))))
case e =>
SeqRes(
SeqFutRes(errors = Vector(
new IllegalStateException(s"${e.getClass.toString} is not supposed to appear here"))))
}
private def resolveActionsPar(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
actions: Actions,
userCtx: Ctx,
fieldsNamesOrdered: Vector[String]): Resolve = {
val (errors, res) = actions
res match {
case None => Result(errors, None)
case Some(results) =>
val resolvedValues: Vector[(ast.Field, Resolve)] = results.map { case (astFields, rest) =>
rest match {
case None => astFields.head -> Result(ErrorRegistry.empty, None)
case Some((field, updateCtx, v)) =>
resolveLeafAction(path, tpe, userCtx, astFields, field, updateCtx)(v)
}
}
val resSoFar =
resolvedValues.iterator
.collect { case (af, r: Result) => af -> r }
.foldLeft(Result(errors, Some(marshaller.emptyMapNode(fieldsNamesOrdered)))) {
case (res, (astField, other)) =>
res.addToMap(
other,
astField.outputName,
isOptional(tpe, astField.name),
path.add(astField, tpe),
astField.location,
res.errors)
}
val complexRes = resolvedValues.collect { case (af, r: DeferredResult) => af -> r }
if (complexRes.isEmpty) resSoFar.buildValue
else {
val allDeferred = complexRes.flatMap(_._2.deferred)
val finalValue = Future
.sequence(complexRes.iterator.map { case (astField, DeferredResult(_, future)) =>
future.map(astField -> _)
})
.map { results =>
results
.foldLeft(resSoFar) { case (res, (astField, other)) =>
res.addToMap(
other,
astField.outputName,
isOptional(tpe, astField.name),
path.add(astField, tpe),
astField.location,
res.errors)
}
.buildValue
}
DeferredResult(allDeferred, finalValue)
}
}
}
private def resolveUc(newUc: Option[MappedCtxUpdate[Ctx, Any, Any]], v: Any, userCtx: Ctx) =
newUc.fold(userCtx)(_.ctxFn(v))
private def resolveVal(newUc: Option[MappedCtxUpdate[Ctx, Any, Any]], v: Any) = newUc match {
case Some(MappedCtxUpdate(_, mapFn, _)) => mapFn(v)
case None => v
}
private def resolveError(newUc: Option[MappedCtxUpdate[Ctx, Any, Any]], e: Throwable) = {
try newUc.foreach(_.onError(e))
catch {
case NonFatal(ee) => ee.printStackTrace()
}
e
}
protected def resolveLeafAction(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
userCtx: Ctx,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
action: LeafAction[Ctx, Any]): (ast.Field, Resolve) =
action match {
case a: StandardLeafAction[Ctx, Any] =>
resolveStandardLeafAction(path, tpe, userCtx, astFields, field, updateCtx)(a)
case other => unresolvableLeafAction(path, tpe, astFields, updateCtx)(other)
}
protected def unresolvableLeafAction(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
astFields: Vector[ast.Field],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
action: LeafAction[Ctx, Any]): (ast.Field, Resolve) =
illegalActionException(path, tpe, astFields, updateCtx)(
s"Action of type '${action.getClass.toString}' is not supported by this resolver")
protected def resolveStandardLeafAction(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
userCtx: Ctx,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
action: StandardLeafAction[Ctx, Any]): (ast.Field, Resolve) =
action match {
case v: Value[Ctx, Any] => resolveValue(path, tpe, userCtx, astFields, field, updateCtx)(v)
case t: TryValue[Ctx, Any] =>
resolveTryValue(path, tpe, userCtx, astFields, field, updateCtx)(t)
case p: PartialValue[Ctx, Any] =>
resolvePartialValue(path, tpe, userCtx, astFields, field, updateCtx)(p)
case f: FutureValue[Ctx, Any] =>
resolveFutureValue(path, tpe, userCtx, astFields, field, updateCtx)(f)
case p: PartialFutureValue[Ctx, Any] =>
resolvePartialFutureValue(path, tpe, userCtx, astFields, field, updateCtx)(p)
case d: DeferredValue[Ctx, Any] =>
resolveDeferredValue(path, tpe, userCtx, astFields, field, updateCtx)(d)
case d: DeferredFutureValue[Ctx, Any] =>
resolveDeferredFutureValue(path, tpe, userCtx, astFields, field, updateCtx)(d)
case s: SequenceLeafAction[Ctx, Any] @unchecked =>
resolveSequenceLeafAction(path, tpe, userCtx, astFields, field, updateCtx)(s)
case _: MappedSequenceLeafAction[_, _, _] =>
illegalActionException(path, tpe, astFields, updateCtx)(
"MappedSequenceLeafAction is not supposed to appear here")
case _: SubscriptionValue[_, _, _] =>
illegalActionException(path, tpe, astFields, updateCtx)(
"Subscription values are not supported for normal operations")
}
private def resolveValue(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
userCtx: Ctx,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
value: Value[Ctx, Any]): (ast.Field, Resolve) = {
val v = value.value
val fieldsPath = path.add(astFields.head, tpe)
try
astFields.head -> resolveValue(
fieldsPath,
astFields,
field.fieldType,
field,
resolveVal(updateCtx, v),
resolveUc(updateCtx, v, userCtx))
catch {
case NonFatal(e) =>
astFields.head -> Result(
ErrorRegistry(fieldsPath, resolveError(updateCtx, e), astFields.head.location),
None)
}
}
private def resolveSequenceLeafAction(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
userCtx: Ctx,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
action: SequenceLeafAction[Ctx, Any]): (ast.Field, Resolve) = {
val actions = action.value
val fieldsPath = path.add(astFields.head, tpe)
val values = resolveActionSequenceValues(fieldsPath, astFields, field, actions)
val future = Future.sequence(values.map(_.value))
val resolved = future
.flatMap { vs =>
val errors = vs.iterator.flatMap(_.errors).toVector
val successfulValues = vs.collect { case SeqFutRes(v, _, _) if v != null => v }
val dctx = vs.collect { case SeqFutRes(_, _, d) if d != null => d }
def resolveDctx(resolve: Resolve) = {
val last = dctx.lastOption
val init = if (dctx.isEmpty) dctx else dctx.init
resolve match {
case res: Result =>
dctx.foreach(_.promise.success(Vector.empty))
Future.successful(res)
case res: DeferredResult =>
init.foreach(_.promise.success(Vector.empty))
last.foreach(_.promise.success(res.deferred))
res.futureValue
}
}
errors.foreach(resolveError(updateCtx, _))
if (successfulValues.size == vs.size)
resolveDctx(
resolveValue(
fieldsPath,
astFields,
field.fieldType,
field,
resolveVal(updateCtx, successfulValues),
resolveUc(updateCtx, successfulValues, userCtx))
.appendErrors(fieldsPath, errors, astFields.head.location))
else
resolveDctx(
Result(ErrorRegistry.empty.append(fieldsPath, errors, astFields.head.location), None))
}
.recover { case e =>
Result(ErrorRegistry(fieldsPath, resolveError(updateCtx, e), astFields.head.location), None)
}
val deferred = values.iterator.collect {
case SeqRes(_, d, _) if d != null => d
}.toVector
val deferredFut = values.iterator.collect {
case SeqRes(_, _, d) if d != null => d
}.toVector
astFields.head -> DeferredResult(Future.successful(deferred) +: deferredFut, resolved)
}
private def resolvePartialValue(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
userCtx: Ctx,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
p: PartialValue[Ctx, Any]): (ast.Field, Resolve) = {
val v = p.value
val es = p.errors
val fieldsPath = path.add(astFields.head, tpe)
es.foreach(resolveError(updateCtx, _))
try
astFields.head ->
resolveValue(
fieldsPath,
astFields,
field.fieldType,
field,
resolveVal(updateCtx, v),
resolveUc(updateCtx, v, userCtx))
.appendErrors(fieldsPath, es, astFields.head.location)
catch {
case NonFatal(e) =>
astFields.head -> Result(
ErrorRegistry(fieldsPath, resolveError(updateCtx, e), astFields.head.location)
.append(fieldsPath, es, astFields.head.location),
None)
}
}
private def resolveTryValue(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
userCtx: Ctx,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
t: TryValue[Ctx, Any]): (ast.Field, Resolve) = {
val v = t.value
val fieldsPath = path.add(astFields.head, tpe)
v match {
case Success(success) =>
try
astFields.head -> resolveValue(
fieldsPath,
astFields,
field.fieldType,
field,
resolveVal(updateCtx, success),
resolveUc(updateCtx, success, userCtx))
catch {
case NonFatal(e) =>
astFields.head -> Result(
ErrorRegistry(fieldsPath, resolveError(updateCtx, e), astFields.head.location),
None)
}
case Failure(e) =>
astFields.head -> Result(
ErrorRegistry(fieldsPath, resolveError(updateCtx, e), astFields.head.location),
None)
}
}
private def resolveDeferredValue(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
userCtx: Ctx,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
d: DeferredValue[Ctx, Any]): (ast.Field, Resolve) = {
val deferred = d.value
val fieldsPath = path.add(astFields.head, tpe)
val promise = Promise[(ChildDeferredContext, Any, Vector[Throwable])]()
val (args, complexity) = calcComplexity(fieldsPath, astFields.head, field, userContext)
val defer = Defer(promise, deferred, complexity, field, astFields, args)
astFields.head -> DeferredResult(
Vector(Future.successful(Vector(defer))),
promise.future
.flatMap { case (dctx, v, es) =>
val uc = resolveUc(updateCtx, v, userCtx)
es.foreach(resolveError(updateCtx, _))
resolveValue(fieldsPath, astFields, field.fieldType, field, resolveVal(updateCtx, v), uc)
.appendErrors(fieldsPath, es, astFields.head.location) match {
case r: Result => dctx.resolveResult(r)
case er: DeferredResult => dctx.resolveDeferredResult(er)
}
}
.recover { case e =>
Result(
ErrorRegistry(fieldsPath, resolveError(updateCtx, e), astFields.head.location),
None)
}
)
}
protected def resolveFutureValue(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
userCtx: Ctx,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
f: FutureValue[Ctx, Any]): (ast.Field, Resolve) = {
val future = f.value
val fieldsPath = path.add(astFields.head, tpe)
val resolved = future
.map(v =>
resolveValue(
fieldsPath,
astFields,
field.fieldType,
field,
resolveVal(updateCtx, v),
resolveUc(updateCtx, v, userCtx)))
.recover { case e =>
Result(ErrorRegistry(fieldsPath, resolveError(updateCtx, e), astFields.head.location), None)
}
def process() = {
val deferred = resolved.flatMap {
case _: Result => Future.successful(Vector.empty)
case r: DeferredResult => Future.sequence(r.deferred).map(_.flatten)
}
val value = resolved.flatMap {
case r: Result => Future.successful(r)
case dr: DeferredResult => dr.futureValue
}
astFields.head -> DeferredResult(Vector(deferred), value)
}
def processAndResolveDeferred() = {
val value = resolved.flatMap {
case r: Result => Future.successful(r)
case dr: DeferredResult => immediatelyResolveDeferred(userContext, dr, identity)
}
astFields.head -> DeferredResult(Vector.empty, value)
}
deferredResolver.includeDeferredFromField match {
case Some(fn) =>
val (args, complexity) =
calcComplexity(fieldsPath, astFields.head, field, userContext)
if (fn(field, astFields, args, complexity))
process()
else
processAndResolveDeferred()
case None =>
process()
}
}
private def resolvePartialFutureValue(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
userCtx: Ctx,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
p: PartialFutureValue[Ctx, Any]): (ast.Field, Resolve) = {
val future = p.value
val fieldsPath = path.add(astFields.head, tpe)
val resolved = future
.map { case PartialValue(v, es) =>
es.foreach(resolveError(updateCtx, _))
resolveValue(
fieldsPath,
astFields,
field.fieldType,
field,
resolveVal(updateCtx, v),
resolveUc(updateCtx, v, userCtx))
.appendErrors(fieldsPath, es, astFields.head.location)
}
.recover { case e =>
Result(ErrorRegistry(fieldsPath, resolveError(updateCtx, e), astFields.head.location), None)
}
val deferred = resolved.flatMap {
case _: Result => Future.successful(Vector.empty)
case r: DeferredResult => Future.sequence(r.deferred).map(_.flatten)
}
val value = resolved.flatMap {
case r: Result => Future.successful(r)
case dr: DeferredResult => dr.futureValue
}
astFields.head -> DeferredResult(Vector(deferred), value)
}
private def resolveDeferredFutureValue(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
userCtx: Ctx,
astFields: Vector[ast.Field],
field: Field[Ctx, _],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(
d: DeferredFutureValue[Ctx, Any]): (ast.Field, Resolve) = {
val deferredValue = d.value
val fieldsPath = path.add(astFields.head, tpe)
val promise = Promise[(ChildDeferredContext, Any, Vector[Throwable])]()
def defer(d: Deferred[Any]) = {
val (args, complexity) =
calcComplexity(fieldsPath, astFields.head, field, userContext)
Defer(promise, d, complexity, field, astFields, args)
}
val actualDeferred = deferredValue
.map(d => Vector(defer(d)))
.recover { case NonFatal(e) =>
promise.failure(e)
Vector.empty
}
astFields.head -> DeferredResult(
Vector(actualDeferred),
promise.future
.flatMap { case (dctx, v, es) =>
val uc = resolveUc(updateCtx, v, userCtx)
es.foreach(resolveError(updateCtx, _))
resolveValue(fieldsPath, astFields, field.fieldType, field, resolveVal(updateCtx, v), uc)
.appendErrors(fieldsPath, es, astFields.head.location) match {
case r: Result => dctx.resolveResult(r)
case er: DeferredResult => dctx.resolveDeferredResult(er)
}
}
.recover { case e =>
Result(
ErrorRegistry(fieldsPath, resolveError(updateCtx, e), astFields.head.location),
None)
}
)
}
private def illegalActionException(
path: ExecutionPath,
tpe: ObjectType[Ctx, _],
astFields: Vector[ast.Field],
updateCtx: Option[MappedCtxUpdate[Ctx, Any, Any]])(msg: String): (ast.Field, Resolve) = {
val fieldsPath = path.add(astFields.head, tpe)
val error = new IllegalStateException(msg)
astFields.head -> Result(
ErrorRegistry(fieldsPath, resolveError(updateCtx, error), astFields.head.location),
None)
}
private def resolveDeferred(uc: Ctx, toResolve: Vector[Defer]): Unit =
if (toResolve.nonEmpty) {
@tailrec
def findActualDeferred(deferred: Deferred[_]): Deferred[_] = deferred match {
case MappingDeferred(d, _) => findActualDeferred(d)
case d => d
}
def mapAllDeferred(
deferred: Deferred[_],
value: Future[Any]): Future[(Any, Vector[Throwable])] = deferred match {
case MappingDeferred(d, fn) =>
mapAllDeferred(d, value).map { case (v, errors) =>
val (mappedV, newErrors) = fn.asInstanceOf[Any => (Any, Seq[Throwable])](v)
mappedV -> (errors ++ newErrors)
}
case _ => value.map(_ -> Vector.empty)
}
try {
val resolved = deferredResolver.resolve(
toResolve.map(d => findActualDeferred(d.deferred)),
uc,
deferredResolverState)
if (toResolve.size == resolved.size) {
val dctx = ParentDeferredContext(uc, toResolve.size)
for (i <- toResolve.indices) {
val toRes = toResolve(i)
toRes.promise.completeWith(
mapAllDeferred(toRes.deferred, resolved(i))
.map(v => (dctx.children(i), v._1, v._2))
.recover { case NonFatal(e) =>
dctx.children(i).resolveError(e)
throw e
})
}
dctx.init()
} else {
toResolve.foreach(_.promise.failure(new IllegalStateException(
s"Deferred resolver returned ${resolved.size} elements, but it got ${toResolve.size} deferred values. This violates the contract. You can find more information in the documentation: https://sangria-graphql.github.io/learn/#deferred-values-and-resolver")))
}
} catch {
case NonFatal(error) => toResolve.foreach(_.promise.failure(error))
}
}
private def resolveValue(
path: ExecutionPath,
astFields: Vector[ast.Field],
tpe: OutputType[_],
field: Field[Ctx, _],
value: Any,
userCtx: Ctx,
actualType: Option[InputType[_]] = None): Resolve =
tpe match {
case OptionType(optTpe) =>
val actualValue = value match {
case v: Option[_] => v
case v => Option(v)
}
actualValue match {
case Some(someValue) =>
resolveValue(path, astFields, optTpe, field, someValue, userCtx, None)
case None => Result(ErrorRegistry.empty, None)
}
case ListType(listTpe) =>
if (isUndefinedValue(value))
Result(ErrorRegistry.empty, None)
else {
val actualValue = value match {
case seq: Iterable[_] => seq
case other => Seq(other)
}
val res = actualValue.zipWithIndex.map { case (v, idx) =>
resolveValue(path.withIndex(idx), astFields, listTpe, field, v, userCtx, None)
}
val simpleRes = res.collect { case r: Result => r }
val optional = isOptional(listTpe)
if (simpleRes.size == res.size)
resolveSimpleListValue(simpleRes, path, optional, astFields.head.location)
else {
// this is very hot place, so resorting to mutability to minimize the footprint
val deferredBuilder = new VectorBuilder[Future[Vector[Defer]]]
val resultFutures = new VectorBuilder[Future[Result]]
val resIt = res.iterator
while (resIt.hasNext)
resIt.next() match {
case r: Result =>
resultFutures += Future.successful(r)
case dr: DeferredResult =>
resultFutures += dr.futureValue
deferredBuilder ++= dr.deferred
}
DeferredResult(
deferred = deferredBuilder.result(),
futureValue = Future
.sequence(resultFutures.result())
.map(resolveSimpleListValue(_, path, optional, astFields.head.location))
)
}
}
case scalar: ScalarType[Any @unchecked] =>
try
Result(
ErrorRegistry.empty,
if (isUndefinedValue(value))
None
else {
val coerced = scalar.coerceOutput(value, marshaller.capabilities)
if (isUndefinedValue(coerced)) {
None
} else {
val coercedWithMiddleware =
toScalarMiddleware match {
case Some(fn) => fn(coerced, actualType.getOrElse(scalar)).getOrElse(coerced)
case None => coerced
}
Some(
marshalScalarValue(
coercedWithMiddleware,
marshaller,
scalar.name,
scalar.scalarInfo))
}
}
)
catch {
case NonFatal(e) => Result(ErrorRegistry(path, e), None)
}
case scalar: ScalarAlias[Any @unchecked, Any @unchecked] =>
resolveValue(
path,
astFields,
scalar.aliasFor,
field,
scalar.toScalar(value),
userCtx,
Some(scalar))
case enumT: EnumType[Any @unchecked] =>
try
Result(
ErrorRegistry.empty,
if (isUndefinedValue(value))
None
else {
val coerced = enumT.coerceOutput(value)
if (isUndefinedValue(coerced))
None
else
Some(marshalEnumValue(coerced, marshaller, enumT.name))
}
)
catch {
case NonFatal(e) => Result(ErrorRegistry(path, e), None)
}
case obj: ObjectType[Ctx @unchecked, _] =>
if (isUndefinedValue(value))
Result(ErrorRegistry.empty, None)
else
fieldCollector.collectFields(path, obj, astFields) match {
case Success(fields) =>
val actions =
collectActionsPar(path, obj, value, fields, ErrorRegistry.empty, userCtx)
resolveActionsPar(path, obj, actions, userCtx, fields.namesOrdered)
case Failure(error) => Result(ErrorRegistry(path, error), None)
}
case abst: AbstractType =>
if (isUndefinedValue(value))
Result(ErrorRegistry.empty, None)
else {
val actualValue =
abst match {
case abst: MappedAbstractType[Any @unchecked] => abst.contraMap(value)
case _ => value
}
abst.typeOf(actualValue, schema) match {
case Some(obj) =>
resolveValue(path, astFields, obj, field, actualValue, userCtx, None)
case None =>
Result(
ErrorRegistry(
path,
UndefinedConcreteTypeError(
path,
abst,
schema.possibleTypes.getOrElse(abst.name, Vector.empty),
actualValue,
exceptionHandler,
sourceMapper,
astFields.head.location.toList)
),
None
)
}
}
}
private def isUndefinedValue(value: Any) =
value == null || value == None
private def resolveSimpleListValue(
simpleRes: Iterable[Result],
path: ExecutionPath,
optional: Boolean,
astPosition: Option[AstLocation]): Result = {
// this is very hot place, so resorting to mutability to minimize the footprint
var errorReg = ErrorRegistry.empty
val listBuilder = new VectorBuilder[marshaller.Node]
var canceled = false
val resIt = simpleRes.iterator
while (resIt.hasNext && !canceled) {
val res = resIt.next()
if (!optional && res.value.isEmpty && res.errors.isEmpty)
errorReg = errorReg.add(path, nullForNotNullTypeError(astPosition))
else if (res.errors.nonEmpty)
errorReg = errorReg.add(res.errors)
res.nodeValue match {
case node if optional =>
listBuilder += marshaller.optionalArrayNodeValue(node)
case Some(other) =>
listBuilder += other
case None =>
canceled = true
}
}
Result(errorReg, if (canceled) None else Some(marshaller.arrayNode(listBuilder.result())))
}
private def trackDeprecation(
deprecationTracker: DeprecationTracker,
ctx: Context[Ctx, _]): Unit = {
val fieldArgs = ctx.args
val visitedDirectives = mutable.Set[String]()
def getArgValue(name: String, args: Args): Option[_] =
if (args.argDefinedInQuery(name)) {
if (args.optionalArgs.contains(name)) {
args.argOpt(name)
} else {
Some(args.arg(name))
}
} else {
None
}
def deprecatedArgsUsed(argDefs: List[Argument[_]], argValues: Args): List[Argument[_]] =
argDefs.filter { argDef =>
val argValue = getArgValue(argDef.name, argValues)
argDef.deprecationReason.isDefined && argValue.isDefined
}
def trackDeprecatedDirectiveArgs(astDirective: ast.Directive): Unit = {
// prevent infinite loop from directiveA -> arg -> directiveA -> arg ...
if (visitedDirectives.contains(astDirective.name)) {
return
}
visitedDirectives.add(astDirective.name)
ctx.schema.directives.find(_.name == astDirective.name) match {
case Some(directive) =>
val directiveArgs = valueCollector
.getArgumentValues(
Some(astDirective),
directive.arguments,
astDirective.arguments,
variables)
directiveArgs match {
case Success(directiveArgs) =>
deprecatedArgsUsed(directive.arguments, directiveArgs).foreach { arg =>
deprecationTracker.deprecatedDirectiveArgUsed(directive, arg, ctx)
}
case _ => // if we fail to get args, the query should fail elsewhere
}
// nested argument directives
directive.arguments.foreach { nestedArg =>
nestedArg.astDirectives.foreach(trackDeprecatedDirectiveArgs)
}
case _ => // do nothing
}
}
def trackDeprecatedInputObjectFields(inputType: InputType[_], ioArg: Any): Unit =
inputType match {
case ioDef: InputObjectType[_] =>
ioDef.fields.foreach { field =>
// field deprecation
val fieldVal: Option[_] = (ioArg match {
case lm: ListMap[String, _] @unchecked => lm.get(field.name)
case _ => None
}) match {
case Some(Some(nested)) => Some(nested)
case value => value
}
if (field.deprecationReason.isDefined && fieldVal.isDefined) {
deprecationTracker.deprecatedInputObjectFieldUsed(ioDef, field, ctx)
}
// for nested input objects
if (fieldVal.isDefined) trackDeprecatedInputObjectFields(field.fieldType, fieldVal.get)
// field directive args deprecation
field.astDirectives.foreach(trackDeprecatedDirectiveArgs)
}
case OptionInputType(ofType) =>
ioArg match {
case Some(ioArg) => trackDeprecatedInputObjectFields(ofType, ioArg)
case _ => trackDeprecatedInputObjectFields(ofType, ioArg)
}
case ListInputType(ofType) =>
ioArg match {
case seq: Seq[_] => seq.foreach(trackDeprecatedInputObjectFields(ofType, _))
case _ => // do nothing
}
case _ => // do nothing
}
val field = ctx.field
val astField = ctx.astFields.head
// field deprecation
val allFields =
ctx.parentType.getField(ctx.schema, astField.name).asInstanceOf[Vector[Field[Ctx, Any]]]
if (allFields.exists(_.deprecationReason.isDefined))
deprecationTracker.deprecatedFieldUsed(ctx)
// directive argument deprecation
field.astDirectives.foreach(trackDeprecatedDirectiveArgs)
// field argument deprecation
deprecatedArgsUsed(field.arguments, fieldArgs).foreach { arg =>
deprecationTracker.deprecatedFieldArgUsed(arg, ctx)
}
field.arguments.foreach { argDef =>
// argument directives args deprecation
argDef.astDirectives.foreach(trackDeprecatedDirectiveArgs)
// input object field deprecation
getArgValue(argDef.name, fieldArgs) match {
case Some(ioArg) => trackDeprecatedInputObjectFields(argDef.argumentType, ioArg)
case _ => // do nothing
}
}
}
private def resolveField(
userCtx: Ctx,
tpe: ObjectType[Ctx, _],
path: ExecutionPath,
value: Any,
errors: ErrorRegistry,
astFields: Vector[ast.Field]): FieldResolution = {
val astField = astFields.head
val allFields = tpe.getField(schema, astField.name).asInstanceOf[Vector[Field[Ctx, Any]]]
val field = allFields.head
maxQueryDepth match {
case Some(max) if path.size > max =>
ErrorFieldResolution(errors.add(path, MaxQueryDepthReachedError(max), astField.location))
case _ =>
valueCollector.getFieldArgumentValues(
path,
Some(astField),
field.arguments,
astField.arguments,
variables) match {
case Success(args) =>
val ctx = Context[Ctx, Any](
value = value,
ctx = userCtx,
args = args,
schema = schema.asInstanceOf[Schema[Ctx, Any]],
field = field,
parentType = tpe.asInstanceOf[ObjectType[Ctx, Any]],
marshaller = marshaller,
query = queryAst,
sourceMapper = sourceMapper,
deprecationTracker = deprecationTracker,
astFields = astFields,
path = path,
deferredResolverState = deferredResolverState
)
deprecationTracker.foreach(trackDeprecation(_, ctx))
try {
val mBefore = middleware.collect { case (mv, m: MiddlewareBeforeField[Ctx]) =>
(m.beforeField(mv.asInstanceOf[m.QueryVal], middlewareCtx, ctx), mv, m)
}
val beforeAction = mBefore.collect {
case (BeforeFieldResult(_, Some(action), _), _, _) => action
}.lastOption
val beforeAttachments = mBefore.iterator.collect {
case (BeforeFieldResult(_, _, Some(att)), _, _) => att
}.toVector
val updatedCtx =
if (beforeAttachments.nonEmpty) ctx.copy(middlewareAttachments = beforeAttachments)
else ctx
val mAfter = mBefore.filter(_._3.isInstanceOf[MiddlewareAfterField[Ctx]]).reverse
val mError = mBefore.filter(_._3.isInstanceOf[MiddlewareErrorField[Ctx]])
def doAfterMiddleware[Val](v: Val): Val =
mAfter.foldLeft(v) {
case (acc, (BeforeFieldResult(cv, _, _), mv, m: MiddlewareAfterField[Ctx])) =>
m.afterField(
mv.asInstanceOf[m.QueryVal],
cv.asInstanceOf[m.FieldVal],
acc,
middlewareCtx,
updatedCtx)
.asInstanceOf[Option[Val]]
.getOrElse(acc)
case (acc, _) => acc
}
def doErrorMiddleware(error: Throwable): Unit =
mError.foreach {
case (BeforeFieldResult(cv, _, _), mv, m: MiddlewareErrorField[Ctx]) =>
m.fieldError(
mv.asInstanceOf[m.QueryVal],
cv.asInstanceOf[m.FieldVal],
error,
middlewareCtx,
updatedCtx)
case _ => ()
}
def doAfterMiddlewareWithMap[Val, NewVal](fn: Val => NewVal)(v: Val): NewVal =
mAfter.foldLeft(fn(v)) {
case (acc, (BeforeFieldResult(cv, _, _), mv, m: MiddlewareAfterField[Ctx])) =>
m.afterField(
mv.asInstanceOf[m.QueryVal],
cv.asInstanceOf[m.FieldVal],
acc,
middlewareCtx,
updatedCtx)
.asInstanceOf[Option[NewVal]]
.getOrElse(acc)
case (acc, _) => acc
}
try {
def createResolution(result: Any): StandardFieldResolution =
result match {
// these specific cases are important for time measuring middleware and eager values
case resolved: Value[Ctx @unchecked, Any @unchecked] =>
StandardFieldResolution(
errors,
if (mAfter.nonEmpty)
Value(doAfterMiddleware(resolved.value))
else
resolved,
None)
case resolved: PartialValue[Ctx @unchecked, Any @unchecked] =>
StandardFieldResolution(
errors,
if (mAfter.nonEmpty)
PartialValue(doAfterMiddleware(resolved.value), resolved.errors)
else
resolved,
if (mError.nonEmpty)
Some(MappedCtxUpdate(_ => userCtx, identity, doErrorMiddleware))
else None
)
case resolved: TryValue[Ctx @unchecked, Any @unchecked] =>
StandardFieldResolution(
errors,
if (mAfter.nonEmpty && resolved.value.isSuccess)
Value(doAfterMiddleware(resolved.value.get))
else
resolved,
if (mError.nonEmpty)
Some(MappedCtxUpdate(_ => userCtx, identity, doErrorMiddleware))
else None
)
case res: SequenceLeafAction[Ctx @unchecked, _] =>
StandardFieldResolution(
errors,
res,
Some(
MappedCtxUpdate(
_ => userCtx,
if (mAfter.nonEmpty) doAfterMiddleware else identity,
if (mError.nonEmpty) doErrorMiddleware else identity)))
case res: MappedSequenceLeafAction[
Ctx @unchecked,
Any @unchecked,
Any @unchecked] =>
val mapFn = res.mapFn.asInstanceOf[Any => Any]
StandardFieldResolution(
errors,
res.action,
Some(
MappedCtxUpdate(
_ => userCtx,
if (mAfter.nonEmpty) doAfterMiddlewareWithMap(mapFn) else mapFn,
if (mError.nonEmpty) doErrorMiddleware else identity))
)
case resolved: LeafAction[Ctx @unchecked, Any @unchecked] =>
StandardFieldResolution(
errors,
resolved,
if (mAfter.nonEmpty || mError.nonEmpty)
Some(
MappedCtxUpdate(
_ => userCtx,
if (mAfter.nonEmpty) doAfterMiddleware else identity,
if (mError.nonEmpty) doErrorMiddleware else identity))
else None
)
case res: UpdateCtx[Ctx @unchecked, Any @unchecked] =>
StandardFieldResolution(
errors,
res.action,
Some(
MappedCtxUpdate(
res.nextCtx,
if (mAfter.nonEmpty) doAfterMiddleware else identity,
if (mError.nonEmpty) doErrorMiddleware else identity))
)
case res: MappedUpdateCtx[Ctx @unchecked, Any @unchecked, Any @unchecked] =>
StandardFieldResolution(
errors,
res.action,
Some(
MappedCtxUpdate(
res.nextCtx,
if (mAfter.nonEmpty) doAfterMiddlewareWithMap(res.mapFn) else res.mapFn,
if (mError.nonEmpty) doErrorMiddleware else identity))
)
case e => throw new IllegalStateException(s"Unsupported action: $e")
}
val result =
beforeAction match {
case Some(action) => action
case None =>
field.resolve match {
case pfn: Projector[Ctx, Any, _] =>
pfn(updatedCtx, collectProjections(path, field, astFields, pfn.maxLevel))
case fn =>
fn(updatedCtx)
}
}
result match {
case s: SubscriptionValue[Ctx @unchecked, _, _] =>
StreamFieldResolution(errors, s, createResolution)
case _ => createResolution(result)
}
} catch {
case NonFatal(e) =>
try {
if (mError.nonEmpty) doErrorMiddleware(e)
ErrorFieldResolution(errors.add(path, e, astField.location))
} catch {
case NonFatal(me) =>
ErrorFieldResolution(
errors.add(path, e, astField.location).add(path, me, astField.location))
}
}
} catch {
case NonFatal(e) => ErrorFieldResolution(errors.add(path, e, astField.location))
}
case Failure(error) => ErrorFieldResolution(errors.add(path, error))
}
}
}
private def collectProjections(
path: ExecutionPath,
field: Field[Ctx, _],
astFields: Vector[ast.Field],
maxLevel: Int): Vector[ProjectedName] = {
def collectProjectionsInternal(
path: ExecutionPath,
tpe: OutputType[_],
astFields: Vector[ast.Field],
currLevel: Int): Vector[ProjectedName] =
loop(path, tpe, astFields, currLevel)
@tailrec
def loop(
path: ExecutionPath,
tpe: OutputType[_],
astFields: Vector[ast.Field],
currLevel: Int): Vector[ProjectedName] =
if (currLevel > maxLevel) Vector.empty
else
tpe match {
case OptionType(ofType) => loop(path, ofType, astFields, currLevel)
case ListType(ofType) => loop(path, ofType, astFields, currLevel)
case objTpe: ObjectType[Ctx @unchecked, _] =>
fieldCollector.collectFields(path, objTpe, astFields) match {
case Success(ff) =>
ff.fields.collect { case CollectedField(_, _, Success(astFields2)) =>
val astField = astFields2.head
val fields = objTpe.getField(schema, astField.name)
if (fields.isEmpty) Vector.empty
else {
val field = fields.head
if (field.tags.contains(ProjectionExclude)) Vector.empty
else {
val projectionNames = field.tags.iterator.collect {
case ProjectionName(name) => name
}
val projectedName =
if (projectionNames.nonEmpty) projectionNames
else Iterator.single(field.name)
projectedName.map { name =>
val children =
collectProjectionsInternal(
path.add(astField, objTpe),
field.fieldType,
astFields2,
currLevel + 1)
ProjectedName(name, children, Args(field, astField, variables))
}
}
}
}.flatten
case Failure(_) => Vector.empty
}
case abst: AbstractType =>
schema.possibleTypes
.get(abst.name)
.map(
_.flatMap(collectProjectionsInternal(path, _, astFields, currLevel + 1))
.groupBy(_.name)
.iterator
.map(_._2.head)
.toVector)
.getOrElse(Vector.empty)
case _ => Vector.empty
}
collectProjectionsInternal(path, field.fieldType, astFields, 1)
}
private def isOptional(tpe: ObjectType[_, _], fieldName: String): Boolean =
isOptional(tpe.getField(schema, fieldName).head.fieldType)
private def isOptional(tpe: OutputType[_]): Boolean =
tpe.isInstanceOf[OptionType[_]]
private def nullForNotNullTypeError(position: Option[AstLocation]) =
new ExecutionError(
"Cannot return null for non-nullable type",
exceptionHandler,
sourceMapper,
position.toList)
protected sealed trait Resolve {
def appendErrors(
path: ExecutionPath,
errors: Vector[Throwable],
position: Option[AstLocation]): Resolve
}
private case class DeferredResult(
deferred: Vector[Future[Vector[Defer]]],
futureValue: Future[Result])
extends Resolve {
def appendErrors(
path: ExecutionPath,
errors: Vector[Throwable],
position: Option[AstLocation]): DeferredResult =
if (errors.nonEmpty)
copy(futureValue = futureValue.map(_.appendErrors(path, errors, position)))
else this
}
private case class Defer(
promise: Promise[(ChildDeferredContext, Any, Vector[Throwable])],
deferred: Deferred[Any],
complexity: Double,
field: Field[_, _],
astFields: Vector[ast.Field],
args: Args)
extends DeferredWithInfo
private case class Result(
errors: ErrorRegistry,
value: Option[Any /* Either marshaller.Node or marshaller.MapBuilder */ ],
userContext: Option[Ctx] = None)
extends Resolve {
def addToMap(
other: Result,
key: String,
optional: Boolean,
path: ExecutionPath,
position: Option[AstLocation],
updatedErrors: ErrorRegistry): Result =
copy(
errors =
if (!optional && other.value.isEmpty && other.errors.isEmpty)
updatedErrors.add(other.errors).add(path, nullForNotNullTypeError(position))
else
updatedErrors.add(other.errors),
value =
if (optional && other.value.isEmpty)
value.map(v =>
marshaller.addMapNodeElem(
v.asInstanceOf[marshaller.MapBuilder],
key,
marshaller.nullNode,
optional = false))
else
for {
myVal <- value
otherVal <- other.value
} yield marshaller.addMapNodeElem(
myVal.asInstanceOf[marshaller.MapBuilder],
key,
otherVal.asInstanceOf[marshaller.Node],
optional = false)
)
def nodeValue: Option[marshaller.Node] = value.asInstanceOf[Option[marshaller.Node]]
private def builderValue = value.asInstanceOf[Option[marshaller.MapBuilder]]
def buildValue: Result = copy(value = builderValue.map(marshaller.mapNode))
def appendErrors(
path: ExecutionPath,
e: Vector[Throwable],
position: Option[AstLocation]): Result =
if (e.nonEmpty) copy(errors = errors.append(path, e, position))
else this
}
private case class ParentDeferredContext(uc: Ctx, expectedBranches: Int) {
val children: Vector[ChildDeferredContext] =
Vector.fill(expectedBranches)(ChildDeferredContext(Promise[Vector[Future[Vector[Defer]]]]()))
def init(): Unit =
Future.sequence(children.map(_.promise.future)).onComplete { res =>
val allDeferred = res.get.flatten
if (allDeferred.nonEmpty)
resolveDeferredWithGrouping(allDeferred).foreach(groups =>
groups.foreach(group => resolveDeferred(uc, group)))
}
}
private case class ChildDeferredContext(promise: Promise[Vector[Future[Vector[Defer]]]]) {
def resolveDeferredResult(res: DeferredResult): Future[Result] = {
promise.success(res.deferred)
res.futureValue
}
def resolveResult(res: Result): Future[Result] = {
promise.success(Vector.empty)
Future.successful(res)
}
def resolveError(e: Throwable): Unit =
promise.success(Vector.empty)
}
private sealed trait FieldResolution
private case class ErrorFieldResolution(errors: ErrorRegistry) extends FieldResolution
private case class StandardFieldResolution(
errors: ErrorRegistry,
action: LeafAction[Ctx, Any],
ctxUpdate: Option[MappedCtxUpdate[Ctx, Any, Any]])
extends FieldResolution
private case class StreamFieldResolution[Val, S[_]](
errors: ErrorRegistry,
value: SubscriptionValue[Ctx, Val, S],
standardResolution: Any => StandardFieldResolution)
extends FieldResolution
private case class SeqRes(value: Future[SeqFutRes], defer: Defer, deferFut: Future[Vector[Defer]])
private object SeqRes {
def apply(value: SeqFutRes): SeqRes = SeqRes(Future.successful(value), null, null)
def apply(value: SeqFutRes, defer: Defer): SeqRes =
SeqRes(Future.successful(value), defer, null)
def apply(value: SeqFutRes, deferFut: Future[Vector[Defer]]): SeqRes =
SeqRes(Future.successful(value), null, deferFut)
def apply(value: Future[SeqFutRes]): SeqRes = SeqRes(value, null, null)
def apply(value: Future[SeqFutRes], defer: Defer): SeqRes = SeqRes(value, defer, null)
def apply(value: Future[SeqFutRes], deferFut: Future[Vector[Defer]]): SeqRes =
SeqRes(value, null, deferFut)
}
private case class SeqFutRes(
value: Any = null,
errors: Vector[Throwable] = Vector.empty,
dctx: ChildDeferredContext = null)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy