Skip to content

Commit

Permalink
validate query using oneOf input type
Browse files Browse the repository at this point in the history
  • Loading branch information
Amanda Steinwedel committed Jul 18, 2024
1 parent 25e74b0 commit cf734e4
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ object QueryValidator {
new VariablesAreInputTypes,
new VariablesInAllowedPosition,
new InputDocumentNonConflictingVariableInference,
new SingleFieldSubscriptions
new SingleFieldSubscriptions,
new ExactlyOneOfFieldGiven
)

def ruleBased(rules: List[ValidationRule]): RuleBasedQueryValidator =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,14 @@ case class NoQueryTypeViolation(sourceMapper: Option[SourceMapper], locations: L
"Must provide schema definition with query type or a type named Query."
}

case class NotExactlyOneOfField(
typeName: String,
sourceMapper: Option[SourceMapper],
locations: List[AstLocation]
) extends AstNodeViolation {
lazy val simpleErrorMessage = s"Exactly one key must be specified for oneOf type '${typeName}'."
}

case class OneOfMandatoryField(
fieldName: String,
typeName: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package sangria.validation.rules

import sangria.ast
import sangria.schema
import sangria.ast.AstVisitorCommand
import sangria.validation._

/** For oneOf input objects, exactly one field should be non-null. */
class ExactlyOneOfFieldGiven extends ValidationRule {
override def visitor(ctx: ValidationContext) = new AstValidatingVisitor {
override val onEnter: ValidationVisit = { case ast.ObjectValue(fields, _, pos) =>
ctx.typeInfo.inputType match {
case Some(inputType) =>
inputType.namedInputType match {
case schema.InputObjectType(name, _, _, directives, _) if directives.exists { d =>
d.name == schema.OneOfDirective.name
} =>
val nonNullFields = fields.filter { field =>
field.value match {
case ast.NullValue(_, _) => false
case _ => true
}
}

nonNullFields.size match {
case 1 => AstVisitorCommand.RightContinue
case _ =>
Left(Vector(NotExactlyOneOfField(name, ctx.sourceMapper, pos.toList)))
}

case _ => AstVisitorCommand.RightContinue
}
case None => AstVisitorCommand.RightContinue
}
}
}
}
16 changes: 15 additions & 1 deletion modules/core/src/test/scala/sangria/util/ValidationSupport.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package sangria.util

import sangria.ast
import sangria.parser.QueryParser
import sangria.schema._
import sangria.validation._
Expand Down Expand Up @@ -145,6 +146,13 @@ trait ValidationSupport extends Matchers {
)
)

val OneOfInput = InputObjectType(
"OneOfInput",
List(
InputField("catName", OptionInputType(StringType)),
InputField("dogId", OptionInputType(IntType))
)).withDirective(ast.Directive(OneOfDirective.name))

val ComplicatedArgs = ObjectType(
"ComplicatedArgs",
List[TestField](
Expand Down Expand Up @@ -251,7 +259,13 @@ trait ValidationSupport extends Matchers {
Field("catOrDog", OptionType(CatOrDog), resolve = _ => None),
Field("dogOrHuman", OptionType(DogOrHuman), resolve = _ => None),
Field("humanOrAlien", OptionType(HumanOrAlien), resolve = _ => None),
Field("complicatedArgs", OptionType(ComplicatedArgs), resolve = _ => None)
Field("complicatedArgs", OptionType(ComplicatedArgs), resolve = _ => None),
Field(
"oneOfQuery",
OptionType(CatOrDog),
arguments = List(Argument("input", OneOfInput)),
resolve = _ => None
)
)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package sangria.validation.rules

import sangria.util.{Pos, ValidationSupport}
import org.scalatest.wordspec.AnyWordSpec

class ExactlyOneOfFieldGivenSpec extends AnyWordSpec with ValidationSupport {

override val defaultRule = Some(new ExactlyOneOfFieldGiven)

"Validate: exactly oneOf field given" should {
"with exactly one non-null field given" in expectPasses("""
query OneOfQuery {
oneOfQuery(input: {
catName: "Gretel"
}) {
... on Cat {
name
}
}
}
""")

"with exactly one null field given" in expectFails(
"""
query OneOfQuery {
oneOfQuery(input: {
catName: null
}) {
... on Cat {
name
}
}
}
""",
List("Exactly one key must be specified for oneOf type 'OneOfInput'." -> Some(Pos(3, 31)))
)

"with no fields given" in expectFails(
"""
query OneOfQuery {
oneOfQuery(input: {}) {
... on Cat {
name
}
}
}
""",
List("Exactly one key must be specified for oneOf type 'OneOfInput'." -> Some(Pos(3, 31)))
)

"with more than one non-null fields given" in expectFails(
"""
query OneOfQuery {
oneOfQuery(input: {
catName: "Gretel",
dogId: 123
}) {
... on Cat {
name
}
... on Dog {
name
}
}
}
""",
List("Exactly one key must be specified for oneOf type 'OneOfInput'." -> Some(Pos(3, 31)))
)
}
}

0 comments on commit cf734e4

Please sign in to comment.