Skip to content

Commit

Permalink
make query validation check variables #major
Browse files Browse the repository at this point in the history
QueryValidator needs access to variables now, so this causes a breaking change.
  • Loading branch information
Amanda Steinwedel committed Jul 19, 2024
1 parent cf734e4 commit bea2b56
Show file tree
Hide file tree
Showing 15 changed files with 561 additions and 230 deletions.
6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,11 @@ lazy val core = project
ProblemFilters.exclude[IncompatibleResultTypeProblem](
"sangria.schema.WithInputTypeRendering.deprecationTracker"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"sangria.schema.WithInputTypeRendering.deprecationTracker")
"sangria.schema.WithInputTypeRendering.deprecationTracker"),
ProblemFilters.exclude[DirectMissingMethodProblem](
"sangria.validation.RuleBasedQueryValidator.validateInputDocument"),
ProblemFilters.exclude[DirectMissingMethodProblem](
"sangria.validation.RuleBasedQueryValidator.validateInputDocument")
),
Test / testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-oF"),
libraryDependencies ++= Seq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class OverlappingFieldsCanBeMergedBenchmark {
bh.consume(doValidate(validator, deepAbstractConcrete))

private def doValidate(validator: QueryValidator, document: Document): Vector[Violation] = {
val result = validator.validateQuery(schema, document, None)
val result = validator.validateQuery(schema, document, Map.empty, None)
require(result.isEmpty)
result
}
Expand Down
341 changes: 180 additions & 161 deletions modules/core/src/main/scala/sangria/execution/Executor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,107 +29,116 @@ case class Executor[Ctx, Root](
operationName: Option[String] = None,
variables: Input = emptyMapVars
)(implicit um: InputUnmarshaller[Input]): Future[PreparedQuery[Ctx, Root, Input]] = {
val (violations, validationTiming) =
TimeMeasurement.measure(queryValidator.validateQuery(schema, queryAst, errorsLimit))
val scalarMiddleware = Middleware.composeFromScalarMiddleware(middleware, userContext)
val valueCollector = new ValueCollector[Ctx, Input](
schema,
variables,
queryAst.sourceMapper,
deprecationTracker,
userContext,
exceptionHandler,
scalarMiddleware,
false)(um)

if (violations.nonEmpty)
Future.failed(ValidationError(violations, exceptionHandler))
else {
val scalarMiddleware = Middleware.composeFromScalarMiddleware(middleware, userContext)
val valueCollector = new ValueCollector[Ctx, Input](
schema,
variables,
queryAst.sourceMapper,
deprecationTracker,
userContext,
exceptionHandler,
val operationCtx = for {
operation <- Executor.getOperation(exceptionHandler, queryAst, operationName)
unmarshalledVariables <- valueCollector.getVariableValues(
operation.variables,
scalarMiddleware,
false)(um)

val executionResult = for {
operation <- Executor.getOperation(exceptionHandler, queryAst, operationName)
unmarshalledVariables <- valueCollector.getVariableValues(
operation.variables,
scalarMiddleware,
errorsLimit
)
fieldCollector = new FieldCollector[Ctx, Root](
schema,
queryAst,
unmarshalledVariables,
queryAst.sourceMapper,
valueCollector,
exceptionHandler)
tpe <- Executor.getOperationRootType(
schema,
exceptionHandler,
operation,
queryAst.sourceMapper)
fields <- fieldCollector.collectFields(ExecutionPath.empty, tpe, Vector(operation))
} yield {
val preparedFields = fields.fields.flatMap {
case CollectedField(_, astField, Success(_)) =>
val allFields =
tpe.getField(schema, astField.name).asInstanceOf[Vector[Field[Ctx, Root]]]
val field = allFields.head
val args = valueCollector.getFieldArgumentValues(
ExecutionPath.empty.add(astField, tpe),
Some(astField),
field.arguments,
astField.arguments,
unmarshalledVariables)
errorsLimit
)
} yield (operation, unmarshalledVariables)

args.toOption.map(PreparedField(field, _))
case _ => None
}
operationCtx match {
case Failure(error) => Future.failed(error)
case Success((operation, unmarshalledVariables)) =>
val (violations, validationTiming) =
TimeMeasurement.measure(
queryValidator.validateQuery(schema, queryAst, unmarshalledVariables, errorsLimit))

QueryReducerExecutor
.reduceQuery(
schema,
queryReducers,
exceptionHandler,
fieldCollector,
valueCollector,
unmarshalledVariables,
tpe,
fields,
userContext)
.map { case (newCtx, timing) =>
new PreparedQuery[Ctx, Root, Input](
queryAst,
if (violations.nonEmpty)
Future.failed(ValidationError(violations, exceptionHandler))
else {
val executionResult = for {
tpe <- Executor.getOperationRootType(
schema,
exceptionHandler,
operation,
tpe,
newCtx,
root,
preparedFields,
(c: Ctx, r: Root, m: ResultMarshaller, scheme: ExecutionScheme) =>
executeOperation(
queryAst.sourceMapper)
fieldCollector = new FieldCollector[Ctx, Root](
schema,
queryAst,
unmarshalledVariables,
queryAst.sourceMapper,
valueCollector,
exceptionHandler)
fields <- fieldCollector.collectFields(ExecutionPath.empty, tpe, Vector(operation))
} yield {
val preparedFields = fields.fields.flatMap {
case CollectedField(_, astField, Success(_)) =>
val allFields =
tpe.getField(schema, astField.name).asInstanceOf[Vector[Field[Ctx, Root]]]
val field = allFields.head
val args = valueCollector.getFieldArgumentValues(
ExecutionPath.empty.add(astField, tpe),
Some(astField),
field.arguments,
astField.arguments,
unmarshalledVariables)

args.toOption.map(PreparedField(field, _))
case _ => None
}

QueryReducerExecutor
.reduceQuery(
schema,
queryReducers,
exceptionHandler,
fieldCollector,
valueCollector,
unmarshalledVariables,
tpe,
fields,
userContext)
.map { case (newCtx, timing) =>
new PreparedQuery[Ctx, Root, Input](
queryAst,
operationName,
variables,
um,
operation,
queryAst.sourceMapper,
valueCollector,
fieldCollector,
m,
unmarshalledVariables,
tpe,
fields,
c,
r,
scheme,
validationTiming,
timing
newCtx,
root,
preparedFields,
(c: Ctx, r: Root, m: ResultMarshaller, scheme: ExecutionScheme) =>
executeOperation(
queryAst,
operationName,
variables,
um,
operation,
queryAst.sourceMapper,
valueCollector,
fieldCollector,
m,
unmarshalledVariables,
tpe,
fields,
c,
r,
scheme,
validationTiming,
timing
)
)
)
}
}
}

executionResult match {
case Success(future) => future
case Failure(error) => Future.failed(error)
}
executionResult match {
case Success(future) => future
case Failure(error) => Future.failed(error)
}
}

}
}

Expand All @@ -143,82 +152,92 @@ case class Executor[Ctx, Root](
marshaller: ResultMarshaller,
um: InputUnmarshaller[Input],
scheme: ExecutionScheme): scheme.Result[Ctx, marshaller.Node] = {
val (violations, validationTiming) =
TimeMeasurement.measure(queryValidator.validateQuery(schema, queryAst, errorsLimit))

if (violations.nonEmpty)
scheme.failed(ValidationError(violations, exceptionHandler))
else {
val scalarMiddleware = Middleware.composeFromScalarMiddleware(middleware, userContext)
val valueCollector = new ValueCollector[Ctx, Input](
schema,
variables,
queryAst.sourceMapper,
deprecationTracker,
userContext,
exceptionHandler,
val scalarMiddleware = Middleware.composeFromScalarMiddleware(middleware, userContext)
val valueCollector = new ValueCollector[Ctx, Input](
schema,
variables,
queryAst.sourceMapper,
deprecationTracker,
userContext,
exceptionHandler,
scalarMiddleware,
false)(um)

val operationCtx = for {
operation <- Executor.getOperation(exceptionHandler, queryAst, operationName)
unmarshalledVariables <- valueCollector.getVariableValues(
operation.variables,
scalarMiddleware,
false)(um)
errorsLimit
)
} yield (operation, unmarshalledVariables)

val executionResult = for {
operation <- Executor.getOperation(exceptionHandler, queryAst, operationName)
unmarshalledVariables <- valueCollector.getVariableValues(
operation.variables,
scalarMiddleware,
errorsLimit
)
fieldCollector = new FieldCollector[Ctx, Root](
schema,
queryAst,
unmarshalledVariables,
queryAst.sourceMapper,
valueCollector,
exceptionHandler)
tpe <- Executor.getOperationRootType(
schema,
exceptionHandler,
operation,
queryAst.sourceMapper)
fields <- fieldCollector.collectFields(ExecutionPath.empty, tpe, Vector(operation))
} yield {
val reduced = QueryReducerExecutor.reduceQuery(
schema,
queryReducers,
exceptionHandler,
fieldCollector,
valueCollector,
unmarshalledVariables,
tpe,
fields,
userContext)
scheme.flatMapFuture(reduced) { case (newCtx, timing) =>
executeOperation(
queryAst,
operationName,
variables,
um,
operation,
queryAst.sourceMapper,
valueCollector,
fieldCollector,
marshaller,
unmarshalledVariables,
tpe,
fields,
newCtx,
root,
scheme,
validationTiming,
timing
)
}
}
operationCtx match {
case Failure(error) => scheme.failed(error)
case Success((operation, unmarshalledVariables)) =>
val (violations, validationTiming) =
TimeMeasurement.measure(
queryValidator.validateQuery(schema, queryAst, unmarshalledVariables, errorsLimit))

executionResult match {
case Success(result) => result
case Failure(error) => scheme.failed(error)
}
if (violations.nonEmpty)
scheme.failed(ValidationError(violations, exceptionHandler))
else {
val executionResult = for {
tpe <- Executor.getOperationRootType(
schema,
exceptionHandler,
operation,
queryAst.sourceMapper)
fieldCollector = new FieldCollector[Ctx, Root](
schema,
queryAst,
unmarshalledVariables,
queryAst.sourceMapper,
valueCollector,
exceptionHandler)
fields <- fieldCollector.collectFields(ExecutionPath.empty, tpe, Vector(operation))
} yield {
val reduced = QueryReducerExecutor.reduceQuery(
schema,
queryReducers,
exceptionHandler,
fieldCollector,
valueCollector,
unmarshalledVariables,
tpe,
fields,
userContext)
scheme.flatMapFuture(reduced) { case (newCtx, timing) =>
executeOperation(
queryAst,
operationName,
variables,
um,
operation,
queryAst.sourceMapper,
valueCollector,
fieldCollector,
marshaller,
unmarshalledVariables,
tpe,
fields,
newCtx,
root,
scheme,
validationTiming,
timing
)
}
}

executionResult match {
case Success(result) => result
case Failure(error) => scheme.failed(error)
}
}
}

}

private def executeOperation[Input](
Expand Down
Loading

0 comments on commit bea2b56

Please sign in to comment.