From c7887b16e8ecbd14450c8e876f9b6f35015ec092 Mon Sep 17 00:00:00 2001 From: Yann Simon Date: Fri, 31 Mar 2023 11:12:03 +0200 Subject: [PATCH] prepare for custom resolver move current Resolver into FutureResolver make more private methods to mroe easily adapt the behavior --- build.sbt | 6 +- .../sangria/execution/ExecutionScheme.scala | 12 +- .../scala/sangria/execution/Executor.scala | 2 +- .../sangria/execution/FutureResolver.scala | 1959 +++++++++++++++++ .../scala/sangria/execution/Resolver.scala | 1854 +--------------- .../sangria/execution/IOExecutionScheme.scala | 2 +- 6 files changed, 2011 insertions(+), 1824 deletions(-) create mode 100644 modules/core/src/main/scala/sangria/execution/FutureResolver.scala diff --git a/build.sbt b/build.sbt index fb680eb7..691fae87 100644 --- a/build.sbt +++ b/build.sbt @@ -192,7 +192,11 @@ lazy val core = project ProblemFilters.exclude[DirectMissingMethodProblem]( "sangria.introspection.IntrospectionInterfaceType.curried"), ProblemFilters.exclude[DirectMissingMethodProblem]( - "sangria.introspection.IntrospectionInterfaceType.copy") + "sangria.introspection.IntrospectionInterfaceType.copy"), + ProblemFilters.exclude[ReversedMissingMethodProblem]( + "sangria.execution.ExecutionScheme.resolverBuilder"), + ProblemFilters.exclude[IncompatibleTemplateDefProblem]("sangria.execution.Resolver"), + ProblemFilters.exclude[MissingClassProblem]("sangria.execution.Resolver$*") ), Test / testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-oF"), libraryDependencies ++= Seq( diff --git a/modules/core/src/main/scala/sangria/execution/ExecutionScheme.scala b/modules/core/src/main/scala/sangria/execution/ExecutionScheme.scala index 93abf699..7360c05c 100644 --- a/modules/core/src/main/scala/sangria/execution/ExecutionScheme.scala +++ b/modules/core/src/main/scala/sangria/execution/ExecutionScheme.scala @@ -14,6 +14,7 @@ sealed trait ExecutionScheme { def flatMapFuture[Ctx, Res, T](future: Future[T])(resultFn: T => Result[Ctx, Res])(implicit ec: ExecutionContext): Result[Ctx, Res] def extended: Boolean + val resolverBuilder: ResolverBuilder } object ExecutionScheme extends AlternativeExecutionScheme { @@ -34,6 +35,8 @@ object ExecutionScheme extends AlternativeExecutionScheme { future.flatMap(resultFn) def extended = false + + override val resolverBuilder: ResolverBuilder = FutureResolverBuilder } } @@ -46,7 +49,8 @@ trait EffectOps[F[_]] { @ApiMayChange class EffectBasedExecutionScheme[F[_]]( - ops: EffectOps[F] + val ops: EffectOps[F], + val resolverBuilder: ResolverBuilder ) extends ExecutionScheme { override type Result[Ctx, Res] = F[Res] override def failed[Ctx, Res](error: Throwable): Result[Ctx, Res] = @@ -85,6 +89,8 @@ trait AlternativeExecutionScheme { future.flatMap(resultFn) def extended = true + + override val resolverBuilder: ResolverBuilder = FutureResolverBuilder } implicit def Stream[S[_]](implicit @@ -107,6 +113,8 @@ trait AlternativeExecutionScheme { def flatMapFuture[Ctx, Res, T](future: Future[T])(resultFn: T => Result[Ctx, Res])(implicit ec: ExecutionContext): Result[Ctx, Res] = stream.flatMapFuture(future)(resultFn) + + override val resolverBuilder: ResolverBuilder = FutureResolverBuilder } implicit def StreamExtended[S[_]](implicit @@ -129,6 +137,8 @@ trait AlternativeExecutionScheme { def flatMapFuture[Ctx, Res, T](future: Future[T])(resultFn: T => Result[Ctx, Res])(implicit ec: ExecutionContext): Result[Ctx, Res] = stream.flatMapFuture(future)(resultFn) + + override val resolverBuilder: ResolverBuilder = FutureResolverBuilder } } diff --git a/modules/core/src/main/scala/sangria/execution/Executor.scala b/modules/core/src/main/scala/sangria/execution/Executor.scala index 87d0c804..e1bf053b 100644 --- a/modules/core/src/main/scala/sangria/execution/Executor.scala +++ b/modules/core/src/main/scala/sangria/execution/Executor.scala @@ -249,7 +249,7 @@ case class Executor[Ctx, Root]( val middlewareVal = middleware.map(m => m.beforeQuery(middlewareCtx) -> m) val deferredResolverState = deferredResolver.initialQueryState - val resolver = new Resolver[Ctx]( + val resolver = scheme.resolverBuilder.build[Ctx]( marshaller, middlewareCtx, schema, diff --git a/modules/core/src/main/scala/sangria/execution/FutureResolver.scala b/modules/core/src/main/scala/sangria/execution/FutureResolver.scala new file mode 100644 index 00000000..33fddd5f --- /dev/null +++ b/modules/core/src/main/scala/sangria/execution/FutureResolver.scala @@ -0,0 +1,1959 @@ +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.VectorBuilder +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: 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: 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] => + 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") + } + + private 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[_] => + 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: EffectBasedExecutionScheme[_] => + s.mapEffect(result.map(_.swap)) { case (_, in) => in._2 } + .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 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) + } + + 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.map(_.ctxFn(v)).getOrElse(uc) + + 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.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 _: MappedSequenceLeafAction[_, _, _] => + Future.failed( + new IllegalStateException("MappedSequenceLeafAction 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 + } + } + + 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")))) + } + + 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.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.map(_.ctxFn(v)).getOrElse(userCtx) + + 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 + } + + private 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 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] => + 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) + } + ) + } + + private 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) + 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) + } + + 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, _] => + 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) + 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 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 + ) + + if (allFields.exists(_.deprecationReason.isDefined)) + deprecationTracker.deprecatedFieldUsed(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 { + val res = + 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) + } + } + + def createResolution(result: Any): StandardFieldResolution = + result match { + // these specific cases are important for time measuring middleware and eager values + case resolved: Value[Ctx, Any @unchecked] => + StandardFieldResolution( + errors, + if (mAfter.nonEmpty) + Value(doAfterMiddleware(resolved.value)) + else + resolved, + None) + + case resolved: PartialValue[Ctx, 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, 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, _] => + StandardFieldResolution( + errors, + res, + Some( + MappedCtxUpdate( + _ => userCtx, + if (mAfter.nonEmpty) doAfterMiddleware else identity, + if (mError.nonEmpty) doErrorMiddleware else identity))) + + case res: MappedSequenceLeafAction[Ctx, 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, 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, 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, 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)) + ) + } + + res match { + case s: SubscriptionValue[Ctx, _, _] => + StreamFieldResolution(errors, s, createResolution) + case _ => createResolution(res) + } + } 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 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, _] => + fieldCollector.collectFields(path, objTpe, astFields) match { + case Success(ff) => + ff.fields.collect { + case CollectedField(_, _, Success(fields)) + if objTpe.getField(schema, fields.head.name).nonEmpty && !objTpe + .getField(schema, fields.head.name) + .head + .tags + .contains(ProjectionExclude) => + val astField = fields.head + val field = objTpe.getField(schema, astField.name).head + val projectionNames = field.tags.iterator.collect { case ProjectionName(name) => + name + }.toVector + + val projectedName = + if (projectionNames.nonEmpty) projectionNames + else Vector(field.name) + + projectedName.map(name => + ProjectedName( + name, + loop(path.add(astField, objTpe), field.fieldType, fields, currLevel + 1), + Args(field, astField, variables))) + }.flatten + case Failure(_) => Vector.empty + } + case abst: AbstractType => + schema.possibleTypes + .get(abst.name) + .map( + _.flatMap(loop(path, _, astFields, currLevel + 1)) + .groupBy(_.name) + .map(_._2.head) + .toVector) + .getOrElse(Vector.empty) + case _ => Vector.empty + } + + loop(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) + + private 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) +} diff --git a/modules/core/src/main/scala/sangria/execution/Resolver.scala b/modules/core/src/main/scala/sangria/execution/Resolver.scala index 263e7161..b6166a06 100644 --- a/modules/core/src/main/scala/sangria/execution/Resolver.scala +++ b/modules/core/src/main/scala/sangria/execution/Resolver.scala @@ -1,1834 +1,48 @@ package sangria.execution -import sangria.ast.AstLocation import sangria.ast +import sangria.ast.SourceMapper import sangria.execution.deferred.{Deferred, DeferredResolver} import sangria.marshalling.{ResultMarshaller, ScalarValueInfo} -import sangria.ast.SourceMapper import sangria.schema._ -import sangria.streaming.SubscriptionStream - -import scala.annotation.tailrec -import scala.collection.immutable.VectorBuilder -import scala.concurrent.{ExecutionContext, Future, Promise} -import scala.util.control.NonFatal -import scala.util.{Failure, Success} - -private[execution] class Resolver[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: DeprecationTracker, - middleware: List[(Any, Middleware[Ctx])], - maxQueryDepth: Option[Int], - deferredResolverState: Any, - preserveOriginalErrors: Boolean, - validationTiming: TimeMeasurement, - queryReducerTiming: TimeMeasurement, - queryAst: ast.Document -)(implicit executionContext: ExecutionContext) { - val resultResolver = new ResultResolver(marshaller, exceptionHandler, preserveOriginalErrors) - private val toScalarMiddleware = - Middleware.composeToScalarMiddleware(middleware.map(_._2), userContext) - - import resultResolver._ - import Resolver._ - - private[execution] 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) - } - - private[execution] 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) - } - - private[execution] 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] => - 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") - } - - private 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[_] => - 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: EffectBasedExecutionScheme[_] => - s.mapEffect(result.map(_.swap)) { case (_, in) => in._2 } - .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 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) - } - - 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.map(_.ctxFn(v)).getOrElse(uc) - - 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.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 _: MappedSequenceLeafAction[_, _, _] => - Future.failed( - new IllegalStateException("MappedSequenceLeafAction 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 - } - } - - 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")))) - } - - private def resolveActionsPar( - path: ExecutionPath, - tpe: ObjectType[Ctx, _], - actions: Actions, - userCtx: Ctx, - fieldsNamesOrdered: Vector[String]): Resolve = { - val (errors, res) = actions - - def resolveUc(newUc: Option[MappedCtxUpdate[Ctx, Any, Any]], v: Any) = - newUc.map(_.ctxFn(v)).getOrElse(userCtx) - - def resolveError(newUc: Option[MappedCtxUpdate[Ctx, Any, Any]], e: Throwable) = { - try newUc.foreach(_.onError(e)) - catch { - case NonFatal(ee) => ee.printStackTrace() - } - - e - } - - def resolveVal(newUc: Option[MappedCtxUpdate[Ctx, Any, Any]], v: Any) = newUc match { - case Some(MappedCtxUpdate(_, mapFn, _)) => mapFn(v) - case None => v - } - - res match { - case None => Result(errors, None) - case Some(results) => - val resolvedValues = results.map { - case (astFields, None) => astFields.head -> Result(ErrorRegistry.empty, None) - case (astFields, Some((field, updateCtx, Value(v)))) => - val fieldsPath = path.add(astFields.head, tpe) - - try - astFields.head -> resolveValue( - fieldsPath, - astFields, - field.fieldType, - field, - resolveVal(updateCtx, v), - resolveUc(updateCtx, v)) - catch { - case NonFatal(e) => - astFields.head -> Result( - ErrorRegistry(fieldsPath, resolveError(updateCtx, e), astFields.head.location), - None) - } - - case (astFields, Some((field, updateCtx, SequenceLeafAction(actions)))) => - 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)) - .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) +import scala.concurrent.ExecutionContext - case (astFields, Some((field, updateCtx, PartialValue(v, es)))) => - 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)) - .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) - } - case (astFields, Some((field, updateCtx, TryValue(v)))) => - 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)) - 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) - } - case (astFields, Some((field, updateCtx, DeferredValue(deferred)))) => - 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) - - 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) - } - ) - case (astFields, Some((field, updateCtx, FutureValue(future)))) => - val fieldsPath = path.add(astFields.head, tpe) - - val resolved = future - .map(v => - resolveValue( - fieldsPath, - astFields, - field.fieldType, - field, - resolveVal(updateCtx, v), - resolveUc(updateCtx, v))) - .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() - } - - case (astFields, Some((field, updateCtx, PartialFutureValue(future)))) => - 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)) - .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) - - case (astFields, Some((field, updateCtx, DeferredFutureValue(deferredValue)))) => - 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) - - 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) - } - ) - case (astFields, Some((_, updateCtx, SubscriptionValue(_, _)))) => - val fieldsPath = path.add(astFields.head, tpe) - val error = new IllegalStateException( - "Subscription values are not supported for normal operations") - - astFields.head -> Result( - ErrorRegistry(fieldsPath, resolveError(updateCtx, error), astFields.head.location), - None) - - case (astFields, Some((_, updateCtx, _: MappedSequenceLeafAction[_, _, _]))) => - val fieldsPath = path.add(astFields.head, tpe) - val error = - new IllegalStateException("MappedSequenceLeafAction is not supposed to appear here") - - astFields.head -> Result( - ErrorRegistry(fieldsPath, resolveError(updateCtx, error), astFields.head.location), - None) - - } - - 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.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 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) - 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) - } - - 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, _] => - 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) - 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 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 - ) - - if (allFields.exists(_.deprecationReason.isDefined)) - deprecationTracker.deprecatedFieldUsed(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 { - val res = - 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) - } - } - - def createResolution(result: Any): StandardFieldResolution = - result match { - // these specific cases are important for time measuring middleware and eager values - case resolved: Value[Ctx, Any @unchecked] => - StandardFieldResolution( - errors, - if (mAfter.nonEmpty) - Value(doAfterMiddleware(resolved.value)) - else - resolved, - None) - - case resolved: PartialValue[Ctx, 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, 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, _] => - StandardFieldResolution( - errors, - res, - Some( - MappedCtxUpdate( - _ => userCtx, - if (mAfter.nonEmpty) doAfterMiddleware else identity, - if (mError.nonEmpty) doErrorMiddleware else identity))) - - case res: MappedSequenceLeafAction[Ctx, 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, 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, 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, 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)) - ) - } - - res match { - case s: SubscriptionValue[Ctx, _, _] => - StreamFieldResolution(errors, s, createResolution) - case _ => createResolution(res) - } - } 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 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, _] => - fieldCollector.collectFields(path, objTpe, astFields) match { - case Success(ff) => - ff.fields.collect { - case CollectedField(_, _, Success(fields)) - if objTpe.getField(schema, fields.head.name).nonEmpty && !objTpe - .getField(schema, fields.head.name) - .head - .tags - .contains(ProjectionExclude) => - val astField = fields.head - val field = objTpe.getField(schema, astField.name).head - val projectionNames = field.tags.iterator.collect { case ProjectionName(name) => - name - }.toVector - - val projectedName = - if (projectionNames.nonEmpty) projectionNames - else Vector(field.name) - - projectedName.map(name => - ProjectedName( - name, - loop(path.add(astField, objTpe), field.fieldType, fields, currLevel + 1), - Args(field, astField, variables))) - }.flatten - case Failure(_) => Vector.empty - } - case abst: AbstractType => - schema.possibleTypes - .get(abst.name) - .map( - _.flatMap(loop(path, _, astFields, currLevel + 1)) - .groupBy(_.name) - .map(_._2.head) - .toVector) - .getOrElse(Vector.empty) - case _ => Vector.empty - } - - loop(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) - - private 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[execution] trait ResolverBuilder { + 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: DeprecationTracker, + middleware: List[(Any, Middleware[Ctx])], + maxQueryDepth: Option[Int], + deferredResolverState: Any, + preserveOriginalErrors: Boolean, + validationTiming: TimeMeasurement, + queryReducerTiming: TimeMeasurement, + queryAst: ast.Document + )(implicit + executionContext: ExecutionContext + ): Resolver[Ctx] +} - 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) +private[execution] trait Resolver[Ctx] { + val marshaller: ResultMarshaller + def resolveFieldsPar(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)( + scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] - 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) - } + def resolveFieldsSeq(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)( + scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] - private case class SeqFutRes( - value: Any = null, - errors: Vector[Throwable] = Vector.empty, - dctx: ChildDeferredContext = null) + def resolveFieldsSubs(tpe: ObjectType[Ctx, _], value: Any, fields: CollectedFields)( + scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] } case class MappedCtxUpdate[Ctx, Val, NewVal]( diff --git a/modules/test-cats-effect/src/test/scala/sangria/execution/IOExecutionScheme.scala b/modules/test-cats-effect/src/test/scala/sangria/execution/IOExecutionScheme.scala index 569fd92c..90171ff6 100644 --- a/modules/test-cats-effect/src/test/scala/sangria/execution/IOExecutionScheme.scala +++ b/modules/test-cats-effect/src/test/scala/sangria/execution/IOExecutionScheme.scala @@ -22,7 +22,7 @@ class IOExecutionScheme extends AnyWordSpec with Matchers { override def map[T, Out](in: Future[T])(f: T => Out): IO[Out] = IO.fromFuture(IO(in)).map(f) } private implicit val ioExecutionScheme: EffectBasedExecutionScheme[IO] = - new EffectBasedExecutionScheme[IO](ioEffectOps) + new EffectBasedExecutionScheme[IO](ioEffectOps, FutureResolverBuilder) import IOExecutionScheme._ "IOExecutionScheme" must {