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

sangria.execution.ValueCollector.scala Maven / Gradle / Ivy

package sangria.execution

import sangria.ast
import sangria.marshalling._
import sangria.parser.SourceMapper
import sangria.renderer.QueryRenderer
import sangria.schema._
import sangria.validation._

import scala.collection.concurrent.TrieMap
import scala.collection.immutable.VectorBuilder
import scala.util.{Success, Failure, Try}

class ValueCollector[Ctx, Input](schema: Schema[_, _], inputVars: Input, sourceMapper: Option[SourceMapper], deprecationTracker: DeprecationTracker, userContext: Ctx, exceptionHandler: Executor.ExceptionHandler)(implicit um: InputUnmarshaller[Input]) {
  val coercionHelper = new ValueCoercionHelper[Ctx](sourceMapper, deprecationTracker, Some(userContext))

  import coercionHelper._

  private val argumentCache = TrieMap[(ExecutionPath.PathCacheKey, List[ast.Argument]), Try[Args]]()

  def getVariableValues(definitions: List[ast.VariableDefinition]): Try[Map[String, VariableValue]] =
    if (!um.isMapNode(inputVars))
      Failure(new ExecutionError(s"Variables should be a map-like object, like JSON object. Got: ${um.render(inputVars)}", exceptionHandler))
    else {
      val res = definitions.foldLeft(Vector.empty[(String, Either[Vector[Violation], VariableValue])]) {
        case (acc, varDef) ⇒
          val value = schema.getInputType(varDef.tpe)
            .map(getVariableValue(varDef, _, um.getRootMapValue(inputVars, varDef.name)))
            .getOrElse(Left(Vector(UnknownVariableTypeViolation(varDef.name, QueryRenderer.render(varDef.tpe), sourceMapper, varDef.position.toList))))

          value match {
            case Right(Some(v)) ⇒ acc :+ (varDef.name → Right(v))
            case Right(None) ⇒ acc
            case Left(violations) ⇒ acc :+ (varDef.name → Left(violations))
          }
      }

      val (errors, values) = res.partition(_._2.isLeft)

      if (errors.nonEmpty) Failure(VariableCoercionError(errors.collect{case (name, Left(errors)) ⇒ errors}.flatten, exceptionHandler))
      else Success(Map(values.collect {case (name, Right(v)) ⇒ name → v}: _*))
    }

  def getVariableValue(definition: ast.VariableDefinition, tpe: InputType[_], input: Option[Input]): Either[Vector[Violation], Option[VariableValue]] = {
    val violations = isValidValue(tpe, input)

    if (violations.isEmpty) {
      val fieldPath = s"$$${definition.name}" :: Nil

      if (input.isEmpty || !um.isDefined(input.get)) {
        import sangria.marshalling.queryAst.queryAstInputUnmarshaller

        definition.defaultValue match {
          case Some(dv) ⇒ Right(Some(VariableValue((marshaller, firstKindMarshaller) ⇒ coerceInputValue(tpe, fieldPath, dv, None, marshaller, firstKindMarshaller))))
          case None ⇒ Right(None)
        }
      } else
        Right(Some(VariableValue((marshaller, firstKindMarshaller) ⇒ coerceInputValue(tpe, fieldPath, input.get, None, marshaller, firstKindMarshaller))))
    } else Left(violations.map(violation ⇒
      VarTypeMismatchViolation(definition.name, QueryRenderer.render(definition.tpe), input map um.render, violation: Violation, sourceMapper, definition.position.toList)))
  }

  private val emptyArgs = Success(Args.empty)

  def getFieldArgumentValues(path: ExecutionPath, argumentDefs: List[Argument[_]], argumentAsts: List[ast.Argument], variables: Map[String, VariableValue]): Try[Args] =
    if(argumentDefs.isEmpty)
      emptyArgs
    else
      argumentCache.getOrElseUpdate(path.cacheKey → argumentAsts, getArgumentValues(argumentDefs, argumentAsts, variables))

  def getArgumentValues(argumentDefs: List[Argument[_]], argumentAsts: List[ast.Argument], variables: Map[String, VariableValue]): Try[Args] =
    if (argumentDefs.isEmpty)
      emptyArgs
    else {
      val astArgMap = argumentAsts groupBy (_.name) mapValues (_.head)
      val marshaller = CoercedScalaResultMarshaller.default
      val errors = new VectorBuilder[Violation]

      val res = argumentDefs.foldLeft(marshaller.emptyMapNode(argumentDefs.map(_.name)): marshaller.MapBuilder) {
        case (acc, argDef) ⇒
          val argPath = argDef.name :: Nil
          val astValue = astArgMap get argDef.name map (_.value)
          val fromInput = argDef.fromInput

          import sangria.marshalling.queryAst.queryAstInputUnmarshaller

          try {
            resolveMapValue(argDef.argumentType, argPath, argDef.defaultValue, argDef.name, marshaller, fromInput.marshaller, allowErrorsOnDefault = true, errors = errors, valueMap = fromInput.fromResult)(
              acc, astValue map (coerceInputValue(argDef.argumentType, argPath, _, Some(variables), marshaller, fromInput.marshaller)))
          } catch {
            case InputParsingError(e) =>
              errors ++= e.map(InvalidInputValueViolation(argDef.name, _, sourceMapper, astValue.flatMap(_.position).toList))
              acc
          }
      }

      val errorRes = errors.result()

      if (errorRes.nonEmpty) Failure(AttributeCoercionError(errorRes, exceptionHandler))
      else Success(Args(marshaller.mapNode(res).asInstanceOf[Map[String, Any]]))
    }
}

case class VariableValue(fn: (ResultMarshaller, ResultMarshaller) ⇒ Either[Vector[Violation], Option[ResultMarshaller#Node]]) {
  private val cache = TrieMap[Int, Either[Vector[Violation], Option[ResultMarshaller#Node]]]()

  def resolve(marshaller: ResultMarshaller, firstKindMarshaller: ResultMarshaller): Either[Vector[Violation], Option[firstKindMarshaller.Node]] =
    cache.getOrElseUpdate(System.identityHashCode(firstKindMarshaller),
      fn(marshaller, firstKindMarshaller)).asInstanceOf[Either[Vector[Violation], Option[firstKindMarshaller.Node]]]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy