Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit number of errors returned (when variables are used) #1034

Merged
merged 6 commits into from
Sep 7, 2023

Conversation

bc-dima-pasieka
Copy link
Contributor

Follow up to #1017

The solution from the previous PR works only for cases where input is specified (hardcoded) as a part of the query but doesn't work/executed when variables are used. For example, the following query will still return an unlimited number of errors:

mutation createProduct($input: CreateProductInput!) {
  product {
    createProduct(input: $input) {
      ...
    }
  }
}

// variables
{
    "input":{
      "field_with_invalid_objects":[{},{},{},{},{}, ...]
    }
  }

implicit um: InputUnmarshaller[In]): Vector[Violation] = {

// keeping track of the number of errors
var errors = 0
Copy link
Contributor Author

@bc-dima-pasieka bc-dima-pasieka Jul 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using mutable variable was the only viable option here to avoid making huge refactoring and keeping the same signature of the original method (by returning Vector[Violation]).

The alternative 1 was to use ListBuffer[Violation] but it would require making signature changes (probably just to return Unit) + it didn't work well with .map(ListValueViolation(0, _, sourceMapper, Nil)).

The alternative 2 was to propagate the number of errors back to the recursion function but I was not able to make it work properly 🙂

objTpe.fields.toVector.flatMap(f =>
isValidValue(f.fieldType, um.getMapValue(valueMap, f.name))
.map(MapValueViolation(f.name, _, sourceMapper, Nil)))
def isValidValueRec(tpe: InputType[_], in: Option[In])(implicit
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a separate recursion function (basically the old method migrated here for the most part) to be called internally so it has easy access to mutable variable

case other => scalar.coerceUserInput(other)
}
case (objTpe: InputObjectType[_], _) =>
addViolation(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So instead of returning Vector(violation), we call addViolation(violation) that mutates number of errors and returns the same Vector(violation)

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])]) {
definitions.foldLeft(
(0, Vector.empty[(String, Either[Vector[Violation], VariableValue])])) {
Copy link
Contributor Author

@bc-dima-pasieka bc-dima-pasieka Jul 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed the accumulator's signature from Vector to (Int, Vector) where Int is to keep track of the number of errors.

We need this change in addition to ValueCoercionHelper.scala, so we can limit the number of errors when multiple inputs are provided

case Left(violations) => acc :+ (varDef.name -> Left(violations))
fromScalarMiddleware,
// calculate the allowed number of errors to be returned (if any)
errorsLimit.map(_ - accErrors)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

propagate the remaining number of errors that are allowed to be returned

val ctx = new ValidationContext(
schema,
queryAst,
queryAst.sourceMapper,
new TypeInfo(schema),
errorsLimit)
errorsLimitOverride)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would take precedence over RuleBasedQueryValidator.errorsLimit so we could pass and use a single limit via execute method

@bc-dima-pasieka
Copy link
Contributor Author

@yanns pls take a look when you have time 🙂

@yanns yanns self-requested a review July 17, 2023 20:50
@yanns
Copy link
Contributor

yanns commented Jul 19, 2023

so that you're not surprised by the delay:
I'll have some time off. I'll start the review again after.

@bc-dima-pasieka
Copy link
Contributor Author

Hey @yanns, just following up in case you are back 🙂

@yanns
Copy link
Contributor

yanns commented Aug 14, 2023

Do you think that we should revert part of the initial PR, and go into that direction: bc-dima-pasieka#2 ?

@bc-dima-pasieka
Copy link
Contributor Author

@yanns

Do you think that we should revert part of the initial PR, and go into that direction: bc-dima-pasieka#2 ?

Probably yes, it might play better in the long run. Let me update the PR

@@ -11,7 +11,10 @@ import scala.collection.mutable.{ListBuffer, Map => MutableMap, Set => MutableSe
import scala.reflect.{ClassTag, classTag}

trait QueryValidator {
def validateQuery(schema: Schema[_, _], queryAst: ast.Document): Vector[Violation]
def validateQuery(
Copy link
Contributor Author

@bc-dima-pasieka bc-dima-pasieka Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces quite a lot of important breaking changes.
I'd suggest that we ignore them for now to focus on the feature.
Once we're happy with the result, we can check how to handle the breaking changes.
You can temporary remove the CI step about breaking changes so that we can focus on the tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented out a few checks. Let me know what would be the best next steps from here. It seems to be "ready" from my end

Copy link
Contributor

@yanns yanns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good to me.
You can add the exceptions for the binary compatibility checks:

    mimaBinaryIssueFilters ++= Seq(
      ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.execution.Executor.apply"),
      ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.execution.Executor.copy"),
      ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.execution.Executor.this"),
      ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.execution.Executor.execute"),
      ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.execution.Executor.prepare"),
      ProblemFilters.exclude[DirectMissingMethodProblem](
        "sangria.execution.QueryReducerExecutor.reduceQueryWithoutVariables"),
      ProblemFilters.exclude[DirectMissingMethodProblem](
        "sangria.execution.ValueCoercionHelper.isValidValue"),
      ProblemFilters.exclude[DirectMissingMethodProblem](
        "sangria.execution.ValueCoercionHelper.getVariableValue"),
      ProblemFilters.exclude[DirectMissingMethodProblem](
        "sangria.execution.batch.BatchExecutor.executeBatch"),
      ProblemFilters.exclude[DirectMissingMethodProblem](
        "sangria.schema.ResolverBasedAstSchemaBuilder.validateSchema"),
      ProblemFilters.exclude[DirectMissingMethodProblem](
        "sangria.validation.QueryValidator.validateQuery"),
      ProblemFilters.exclude[ReversedMissingMethodProblem](
        "sangria.validation.QueryValidator.validateQuery"),
      ProblemFilters.exclude[DirectMissingMethodProblem](
        "sangria.validation.RuleBasedQueryValidator.validateQuery"),
      ProblemFilters.exclude[DirectMissingMethodProblem](
        "sangria.validation.ValidationContext.this")
    ),

I've tried to minimize this, but it's a nightmare as the new argument is optional.
The source compatibility is good though.

Can you add those exceptions are enable the binary check again?

Copy link
Contributor

@yanns yanns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot!

@yanns yanns added this pull request to the merge queue Sep 7, 2023
Merged via the queue into sangria-graphql:main with commit 80d7e09 Sep 7, 2023
4 checks passed
yanns added a commit that referenced this pull request Sep 10, 2023
Use secure configuration by default
Follow-up of #1034
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants