diff --git a/src/main/scala/sangria/ast/QueryAst.scala b/src/main/scala/sangria/ast/QueryAst.scala index 0d68257c..519812ae 100644 --- a/src/main/scala/sangria/ast/QueryAst.scala +++ b/src/main/scala/sangria/ast/QueryAst.scala @@ -198,8 +198,9 @@ case class VariableDefinition( name: String, tpe: Type, defaultValue: Option[Value], + directives: Vector[Directive] = Vector.empty, comments: Vector[Comment] = Vector.empty, - location: Option[AstLocation] = None) extends AstNode with WithComments + location: Option[AstLocation] = None) extends AstNode with WithComments with WithDirectives sealed trait Type extends AstNode { def namedType: NamedType = { @@ -652,10 +653,11 @@ object AstVisitor { trailingComments.foreach(s ⇒ loop(s)) breakOrSkip(onLeave(n)) } - case n @ VariableDefinition(_, tpe, default, comment, _) ⇒ + case n @ VariableDefinition(_, tpe, default, dirs, comment, _) ⇒ if (breakOrSkip(onEnter(n))) { loop(tpe) default.foreach(d ⇒ loop(d)) + dirs.foreach(d ⇒ loop(d)) comment.foreach(s ⇒ loop(s)) breakOrSkip(onLeave(n)) } diff --git a/src/main/scala/sangria/execution/batch/BatchExecutor.scala b/src/main/scala/sangria/execution/batch/BatchExecutor.scala index cf4516b5..0fdca9a7 100644 --- a/src/main/scala/sangria/execution/batch/BatchExecutor.scala +++ b/src/main/scala/sangria/execution/batch/BatchExecutor.scala @@ -392,7 +392,7 @@ object BatchExecutor { if (violations.nonEmpty) inferenceViolations ++= violations else - newVariableDefs += ast.VariableDefinition(ud, firstAstType, None, Vector(ast.Comment("Inferred variable"))) + newVariableDefs += ast.VariableDefinition(ud, firstAstType, None, Vector.empty, Vector(ast.Comment("Inferred variable"))) } if (newVariableDefs.nonEmpty && inferenceViolations.isEmpty) diff --git a/src/main/scala/sangria/introspection/package.scala b/src/main/scala/sangria/introspection/package.scala index 7f142138..b14461a0 100644 --- a/src/main/scala/sangria/introspection/package.scala +++ b/src/main/scala/sangria/introspection/package.scala @@ -65,6 +65,8 @@ package object introspection { description = Some("Location adjacent to a fragment spread.")), EnumValue("INLINE_FRAGMENT", value = DirectiveLocation.InlineFragment, description = Some("Location adjacent to an inline fragment.")), + EnumValue("VARIABLE_DEFINITION", value = DirectiveLocation.VariableDefinition, + description = Some("Location adjacent to a variable definition.")), EnumValue("SCHEMA", value = DirectiveLocation.Schema, description = Some("Location adjacent to a schema definition.")), diff --git a/src/main/scala/sangria/macros/AstLiftable.scala b/src/main/scala/sangria/macros/AstLiftable.scala index d533ee06..3f4e038e 100644 --- a/src/main/scala/sangria/macros/AstLiftable.scala +++ b/src/main/scala/sangria/macros/AstLiftable.scala @@ -41,8 +41,8 @@ trait AstLiftable { } implicit def liftVarDef: Liftable[VariableDefinition] = Liftable { - case VariableDefinition(n, t, d, c, p) ⇒ - q"_root_.sangria.ast.VariableDefinition($n, $t, $d, $c, $p)" + case VariableDefinition(n, t, d, dirs, c, p) ⇒ + q"_root_.sangria.ast.VariableDefinition($n, $t, $d, $dirs, $c, $p)" } implicit def liftInpValDef: Liftable[InputValueDefinition] = Liftable { diff --git a/src/main/scala/sangria/parser/QueryParser.scala b/src/main/scala/sangria/parser/QueryParser.scala index 5a385ada..c8e9cada 100644 --- a/src/main/scala/sangria/parser/QueryParser.scala +++ b/src/main/scala/sangria/parser/QueryParser.scala @@ -354,7 +354,8 @@ trait TypeSystemDefinitions { this: Parser with Tokens with Ignored with Directi wsCapture("ENUM_VALUE") | wsCapture("ENUM") | wsCapture("INPUT_OBJECT") | - wsCapture("INPUT_FIELD_DEFINITION") + wsCapture("INPUT_FIELD_DEFINITION") | + wsCapture("VARIABLE_DEFINITION") } def SchemaDefinition = rule { @@ -395,8 +396,8 @@ trait Operations extends PositionTracking { this: Parser with Tokens with Ignore def VariableDefinitions = rule { wsNoComment('(') ~ VariableDefinition.+ ~ wsNoComment(')') ~> (_.toVector)} - def VariableDefinition = rule { Comments ~ trackPos ~ Variable ~ ws(':') ~ Type ~ DefaultValue.? ~> - ((comment, location, name, tpe, defaultValue) ⇒ ast.VariableDefinition(name, tpe, defaultValue, comment, location)) } + def VariableDefinition = rule { Comments ~ trackPos ~ Variable ~ ws(':') ~ Type ~ DefaultValue.? ~ (DirectivesConst.? ~> (_ getOrElse Vector.empty)) ~> + ((comment, location, name, tpe, defaultValue, dirs) ⇒ ast.VariableDefinition(name, tpe, defaultValue, dirs, comment, location)) } def Variable = rule { Ignored.* ~ '$' ~ NameStrict } diff --git a/src/main/scala/sangria/renderer/QueryRenderer.scala b/src/main/scala/sangria/renderer/QueryRenderer.scala index 76985acd..8c18aa7b 100644 --- a/src/main/scala/sangria/renderer/QueryRenderer.scala +++ b/src/main/scala/sangria/renderer/QueryRenderer.scala @@ -353,11 +353,12 @@ object QueryRenderer { renderDirs(dirs, config, indent) + renderSelections(sels, fd, indent, config) - case vd @ VariableDefinition(name, tpe, defaultValue, _, _) ⇒ + case vd @ VariableDefinition(name, tpe, defaultValue, dirs, _, _) ⇒ renderComment(vd, prev, indent, config) + indent.str + "$" + name + ":" + config.separator + renderNode(tpe, config, indent.zero) + - (defaultValue map (v ⇒ config.separator + "=" + config.separator + renderNode(v, config, indent.zero)) getOrElse "") + (defaultValue map (v ⇒ config.separator + "=" + config.separator + renderNode(v, config, indent.zero)) getOrElse "") + + renderDirs(dirs, config, indent, frontSep = true) case NotNullType(ofType, _) ⇒ renderNode(ofType, config, indent.zero) + "!" diff --git a/src/main/scala/sangria/schema/Schema.scala b/src/main/scala/sangria/schema/Schema.scala index e65d5a1d..727164d4 100644 --- a/src/main/scala/sangria/schema/Schema.scala +++ b/src/main/scala/sangria/schema/Schema.scala @@ -698,6 +698,7 @@ object DirectiveLocation extends Enumeration { val Schema = Value val Subscription = Value val Union = Value + val VariableDefinition = Value def fromString(location: String): DirectiveLocation.Value = location match { case "QUERY" ⇒ Query @@ -707,6 +708,7 @@ object DirectiveLocation extends Enumeration { case "FRAGMENT_DEFINITION" ⇒ FragmentDefinition case "FRAGMENT_SPREAD" ⇒ FragmentSpread case "INLINE_FRAGMENT" ⇒ InlineFragment + case "VARIABLE_DEFINITION" ⇒ VariableDefinition case "SCHEMA" ⇒ Schema case "SCALAR" ⇒ Scalar @@ -729,6 +731,7 @@ object DirectiveLocation extends Enumeration { case FragmentDefinition ⇒ "FRAGMENT_DEFINITION" case FragmentSpread ⇒ "FRAGMENT_SPREAD" case InlineFragment ⇒ "INLINE_FRAGMENT" + case VariableDefinition ⇒ "VARIABLE_DEFINITION" case Schema ⇒ "SCHEMA" case Scalar ⇒ "SCALAR" diff --git a/src/main/scala/sangria/validation/rules/KnownDirectives.scala b/src/main/scala/sangria/validation/rules/KnownDirectives.scala index 8e0ac0dd..f1a57f39 100644 --- a/src/main/scala/sangria/validation/rules/KnownDirectives.scala +++ b/src/main/scala/sangria/validation/rules/KnownDirectives.scala @@ -50,6 +50,7 @@ object KnownDirectives { case _: ast.FragmentDefinition ⇒ Some(DirectiveLocation.FragmentDefinition → "fragment definition") case _: ast.FragmentSpread ⇒ Some(DirectiveLocation.FragmentSpread → "fragment spread") case _: ast.InlineFragment ⇒ Some(DirectiveLocation.InlineFragment → "inline fragment") + case _: ast.VariableDefinition ⇒ Some(DirectiveLocation.VariableDefinition → "variable definition") case _: ast.SchemaDefinition ⇒ Some(DirectiveLocation.Schema → "schema definition") case _: ast.SchemaExtensionDefinition ⇒ Some(DirectiveLocation.Schema → "schema extension definition") diff --git a/src/main/scala/sangria/validation/rules/UniqueVariableNames.scala b/src/main/scala/sangria/validation/rules/UniqueVariableNames.scala index 5ee9abf6..7992abdc 100644 --- a/src/main/scala/sangria/validation/rules/UniqueVariableNames.scala +++ b/src/main/scala/sangria/validation/rules/UniqueVariableNames.scala @@ -21,7 +21,7 @@ class UniqueVariableNames extends ValidationRule { knownVariableNames.clear() AstVisitorCommand.RightContinue - case ast.VariableDefinition(name, _, _, _, pos) ⇒ + case ast.VariableDefinition(name, _, _, _, _, pos) ⇒ knownVariableNames get name match { case Some(otherPos) ⇒ Left(Vector(DuplicateVariableViolation(name, ctx.sourceMapper, otherPos ++ pos.toList))) diff --git a/src/main/scala/sangria/validation/rules/VariablesAreInputTypes.scala b/src/main/scala/sangria/validation/rules/VariablesAreInputTypes.scala index 14762e9c..acdae008 100644 --- a/src/main/scala/sangria/validation/rules/VariablesAreInputTypes.scala +++ b/src/main/scala/sangria/validation/rules/VariablesAreInputTypes.scala @@ -15,7 +15,7 @@ import sangria.validation._ class VariablesAreInputTypes extends ValidationRule { override def visitor(ctx: ValidationContext) = new AstValidatingVisitor { override val onEnter: ValidationVisit = { - case ast.VariableDefinition(name, tpe, _, _, pos) ⇒ + case ast.VariableDefinition(name, tpe, _, _, _, pos) ⇒ ctx.schema.getInputType(tpe) match { case Some(_) ⇒ AstVisitorCommand.RightContinue case None ⇒ Left(Vector( diff --git a/src/test/resources/scenarios/validation/KnownDirectives.yaml b/src/test/resources/scenarios/validation/KnownDirectives.yaml index 7777e52d..25a1be95 100644 --- a/src/test/resources/scenarios/validation/KnownDirectives.yaml +++ b/src/test/resources/scenarios/validation/KnownDirectives.yaml @@ -93,8 +93,8 @@ tests: - name: with well placed directives given: query: |- - query Foo @onQuery { - name @include(if: true) + query Foo($var: Boolean @onVariableDefinition) @onQuery { + name @include(if: $var) ...Frag @include(if: true) skippedField @skip(if: true) ...SkippedFrag @skip(if: true) @@ -111,8 +111,8 @@ tests: - name: with misplaced directives given: query: |- - query Foo @include(if: true) { - name @onQuery + query Foo($var: Boolean @onField) @include(if: true) { + name @onQuery @include(if: $var) ...Frag @onQuery } @@ -123,14 +123,21 @@ tests: validate: - KnownDirectives then: - - error-count: 4 + - error-count: 5 + - error-code: misplacedDirective + args: + directiveName: onField + location: VARIABLE_DEFINITION + loc: + line: 1 + column: 25 - error-code: misplacedDirective args: directiveName: include location: QUERY loc: line: 1 - column: 11 + column: 35 - error-code: misplacedDirective args: directiveName: onQuery diff --git a/src/test/resources/scenarios/validation/validation.schema.graphql b/src/test/resources/scenarios/validation/validation.schema.graphql index 198e3351..8fc9bb7e 100644 --- a/src/test/resources/scenarios/validation/validation.schema.graphql +++ b/src/test/resources/scenarios/validation/validation.schema.graphql @@ -106,6 +106,7 @@ directive @onField on FIELD directive @onFragmentDefinition on FRAGMENT_DEFINITION directive @onFragmentSpread on FRAGMENT_SPREAD directive @onInlineFragment on INLINE_FRAGMENT +directive @onVariableDefinition on VARIABLE_DEFINITION directive @onSchema on SCHEMA directive @onScalar on SCALAR directive @onObject on OBJECT diff --git a/src/test/scala/sangria/introspection/IntrospectionSpec.scala b/src/test/scala/sangria/introspection/IntrospectionSpec.scala index 218a8126..705e3958 100644 --- a/src/test/scala/sangria/introspection/IntrospectionSpec.scala +++ b/src/test/scala/sangria/introspection/IntrospectionSpec.scala @@ -158,6 +158,11 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "description" → "Location adjacent to an inline fragment.", "isDeprecated" → false, "deprecationReason" → null), + Map( + "name" → "VARIABLE_DEFINITION", + "description" → "Location adjacent to a variable definition.", + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "SCHEMA", "description" → "Location adjacent to a schema definition.", diff --git a/src/test/scala/sangria/macros/LiteralMacroSpec.scala b/src/test/scala/sangria/macros/LiteralMacroSpec.scala index c055b306..4478aedb 100644 --- a/src/test/scala/sangria/macros/LiteralMacroSpec.scala +++ b/src/test/scala/sangria/macros/LiteralMacroSpec.scala @@ -51,6 +51,7 @@ class LiteralMacroSpec extends WordSpec with Matchers { NamedType("Int", None), Some(BigDecimalValue(1.23, Vector.empty, None)), Vector.empty, + Vector.empty, None ), VariableDefinition( @@ -58,6 +59,7 @@ class LiteralMacroSpec extends WordSpec with Matchers { NamedType("Int", None), Some(BigIntValue(123, Vector.empty, None)), Vector.empty, + Vector.empty, None )), Vector( @@ -300,6 +302,7 @@ class LiteralMacroSpec extends WordSpec with Matchers { NamedType("ComplexType", None), None, Vector.empty, + Vector.empty, None ), VariableDefinition( @@ -307,6 +310,7 @@ class LiteralMacroSpec extends WordSpec with Matchers { NamedType("Site", None), Some(EnumValue("MOBILE", Vector.empty, None)), Vector.empty, + Vector.empty, None )), Vector.empty, @@ -441,6 +445,7 @@ class LiteralMacroSpec extends WordSpec with Matchers { NamedType("StoryLikeSubscribeInput", None), None, Vector.empty, + Vector.empty, None )), Vector.empty, diff --git a/src/test/scala/sangria/parser/QueryParserSpec.scala b/src/test/scala/sangria/parser/QueryParserSpec.scala index c5afd75c..f42243ba 100644 --- a/src/test/scala/sangria/parser/QueryParserSpec.scala +++ b/src/test/scala/sangria/parser/QueryParserSpec.scala @@ -11,7 +11,6 @@ import scala.reflect.ClassTag import scala.util.{Failure, Success} class QueryParserSpec extends WordSpec with Matchers with StringMatchers { - def parseQuery(query: String)(implicit scheme: DeliveryScheme[ast.Document]): scheme.Result = QueryParser.parse(query, ParserConfig.default.withEmptySourceId.withoutSourceMapper)(scheme) @@ -31,6 +30,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { NamedType("Int", Some(AstLocation(53, 2, 41))), Some(BigDecimalValue(1.23, Vector.empty, Some(AstLocation(59, 2, 47)))), Vector.empty, + Vector.empty, Some(AstLocation(43, 2, 31)) ), VariableDefinition( @@ -38,6 +38,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { NamedType("Int", Some(AstLocation(77, 2, 65))), Some(BigIntValue(123, Vector.empty, Some(AstLocation(83, 2, 71)))), Vector.empty, + Vector.empty, Some(AstLocation(64, 2, 52)) )), Vector( @@ -227,6 +228,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { NamedType("ComplexType", Some(AstLocation(310, 8, 23))), None, Vector.empty, + Vector.empty, Some(AstLocation(304, 8, 17)) ), VariableDefinition( @@ -234,6 +236,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { NamedType("Site", Some(AstLocation(330, 8, 43))), Some(EnumValue("MOBILE", Vector.empty, Some(AstLocation(337, 8, 50)))), Vector.empty, + Vector.empty, Some(AstLocation(323, 8, 36)) )), Vector.empty, @@ -420,6 +423,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { NamedType("StoryLikeSubscribeInput", Some(AstLocation(703, 31, 44))), None, Vector.empty, + Vector.empty, Some(AstLocation(695, 31, 36)) )), Vector.empty, @@ -661,6 +665,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { NamedType("ComplexType", None), None, Vector.empty, + Vector.empty, None ), VariableDefinition( @@ -668,6 +673,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { NamedType("Site", None), Some(EnumValue("MOBILE", Vector.empty, None)), Vector.empty, + Vector.empty, None )), Vector.empty, @@ -848,6 +854,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { NamedType("StoryLikeSubscribeInput", None), None, Vector.empty, + Vector.empty, None )), Vector.empty, @@ -1215,6 +1222,10 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { "parses constant default values" in { parseQuery("{ field(complex: { a: { b: [ $var ] } }) }").isSuccess should be (true) } + + "parses variable definition directives" in { + parseQuery("query Foo($x: Boolean = false @bar) { field }").isSuccess should be (true) + } "parses variable inline values" in { val Failure(error: SyntaxError) = parseQuery( @@ -1231,7 +1242,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { "query Foo($x: Complex = 1.) { field }") error.formattedError should equal ( - """Invalid input "1.)", expected ValueConst or VariableDefinition (line 1, column 25): + """Invalid input "1.)", expected ValueConst, DirectivesConst or VariableDefinition (line 1, column 25): |query Foo($x: Complex = 1.) { field } | ^""".stripMargin) (after being strippedOfCarriageReturns) } @@ -1251,7 +1262,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { "query Foo($x: Complex = 1.0e) { field }") error.formattedError should equal ( - """Invalid input "1.0e)", expected ValueConst or VariableDefinition (line 1, column 25): + """Invalid input "1.0e)", expected ValueConst, DirectivesConst or VariableDefinition (line 1, column 25): |query Foo($x: Complex = 1.0e) { field } | ^""".stripMargin) (after being strippedOfCarriageReturns) } @@ -1261,7 +1272,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { "query Foo($x: Complex = 1.A) { field }") error.formattedError should equal ( - """Invalid input "1.A", expected ValueConst or VariableDefinition (line 1, column 25): + """Invalid input "1.A", expected ValueConst, DirectivesConst or VariableDefinition (line 1, column 25): |query Foo($x: Complex = 1.A) { field } | ^""".stripMargin) (after being strippedOfCarriageReturns) } @@ -1281,7 +1292,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { "query Foo($x: Complex = 1.0eA) { field }") error.formattedError should equal ( - """Invalid input "1.0eA", expected ValueConst or VariableDefinition (line 1, column 25): + """Invalid input "1.0eA", expected ValueConst, DirectivesConst or VariableDefinition (line 1, column 25): |query Foo($x: Complex = 1.0eA) { field } | ^""".stripMargin) (after being strippedOfCarriageReturns) } @@ -1375,7 +1386,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { case node if implicitly[ClassTag[T]].runtimeClass.isAssignableFrom(node.getClass) ⇒ Some(node.asInstanceOf[T]) case Document(defs, _, _, _) ⇒ defs map findAst[T] find (_.isDefined) flatten case OperationDefinition(_, _, vars, _, _, _, _, _) ⇒ vars map findAst[T] find (_.isDefined) flatten - case VariableDefinition(_, _, default, _, _) ⇒ default flatMap findAst[T] + case VariableDefinition(_, _, default, _, _, _) ⇒ default flatMap findAst[T] case _ ⇒ None } @@ -1499,6 +1510,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { "foo", NamedType("ComplexType", Some(AstLocation(434, 23, 1))), None, + Vector.empty, Vector( Comment(" comment 5", Some(AstLocation(354, 15, 1))), Comment(" comment 6", Some(AstLocation(366, 16, 1)))), @@ -1508,6 +1520,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { "site", NamedType("Site", Some(AstLocation(565, 36, 1))), Some(EnumValue("MOBILE", Vector(Comment(" comment 16.5", Some(AstLocation(602, 40, 1))), Comment(" comment 16.6", Some(AstLocation(617, 41, 1)))), Some(AstLocation(632, 42, 1)))), + Vector.empty, Vector( Comment(" comment 11", Some(AstLocation(446, 24, 1))), Comment(" comment 12", Some(AstLocation(459, 25, 1))), @@ -1562,6 +1575,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { Comment(" comment 18.6", Some(AstLocation(762, 53, 1)))), Some(AstLocation(777, 54, 1)) )), + Vector.empty, Vector( Comment(" comment 17", Some(AstLocation(639, 43, 1))), Comment(" comment 18", Some(AstLocation(652, 44, 1))), @@ -1868,6 +1882,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { NamedType("String", Some(AstLocation(40, 1, 41))), Some(StringValue("hello \\\n world", true, Some("\n hello \\\n world"), Vector.empty, Some(AstLocation(53, 2, 5)))), Vector.empty, + Vector.empty, Some(AstLocation(30, 1, 31)) )), Vector.empty, @@ -1988,6 +2003,7 @@ class QueryParserSpec extends WordSpec with Matchers with StringMatchers { NamedType("Boolean", Some(AstLocation(15, 1, 16))), Some(BooleanValue(false, Vector.empty, Some(AstLocation(25, 1, 26)))), Vector.empty, + Vector.empty, Some(AstLocation(11, 1, 12)) )), Vector.empty, diff --git a/src/test/scala/sangria/renderer/QueryRendererSpec.scala b/src/test/scala/sangria/renderer/QueryRendererSpec.scala index d7711511..0bdd2498 100644 --- a/src/test/scala/sangria/renderer/QueryRendererSpec.scala +++ b/src/test/scala/sangria/renderer/QueryRendererSpec.scala @@ -622,7 +622,7 @@ class QueryRendererSpec extends WordSpec with Matchers with StringMatchers { prettyRendered should equal (FileUtil loadQuery "block-string-rendered.graphql") (after being strippedOfCarriageReturns) } - "Experimental: correctly prints fragment defined variables " in { + "Experimental: correctly prints fragment defined variables" in { val query = """ fragment Foo($a: ComplexType, $b: Boolean = false) on TestType { @@ -639,6 +639,20 @@ class QueryRendererSpec extends WordSpec with Matchers with StringMatchers { | id |}""".stripMargin) (after being strippedOfCarriageReturns) } + + "correctly render variable definition directives" in { + val query = + """ + query ($foo: TestType = {a: 123} @testDirective(if: true) @test) { id } + """ + + val Success(ast) = QueryParser.parse(query) + + ast.renderPretty should equal ( + """query ($foo: TestType = {a: 123} @testDirective(if: true) @test) { + | id + |}""".stripMargin) (after being strippedOfCarriageReturns) + } } "rendering schema definition" should { diff --git a/src/test/scala/sangria/renderer/SchemaRenderSpec.scala b/src/test/scala/sangria/renderer/SchemaRenderSpec.scala index f5e6d2ea..73ed2c74 100644 --- a/src/test/scala/sangria/renderer/SchemaRenderSpec.scala +++ b/src/test/scala/sangria/renderer/SchemaRenderSpec.scala @@ -642,6 +642,9 @@ class SchemaRenderSpec extends WordSpec with Matchers with FutureResultSupport w | "Location adjacent to an inline fragment." | INLINE_FRAGMENT | + | "Location adjacent to a variable definition." + | VARIABLE_DEFINITION + | | "Location adjacent to a schema definition." | SCHEMA | @@ -809,6 +812,9 @@ class SchemaRenderSpec extends WordSpec with Matchers with FutureResultSupport w | # Location adjacent to an inline fragment. | INLINE_FRAGMENT | + | # Location adjacent to a variable definition. + | VARIABLE_DEFINITION + | | # Location adjacent to a schema definition. | SCHEMA | diff --git a/src/test/scala/sangria/util/ValidationSupport.scala b/src/test/scala/sangria/util/ValidationSupport.scala index 345da6ad..d115ef68 100644 --- a/src/test/scala/sangria/util/ValidationSupport.scala +++ b/src/test/scala/sangria/util/ValidationSupport.scala @@ -168,6 +168,7 @@ trait ValidationSupport extends Matchers { Directive("onFragmentDefinition", locations = Set(DirectiveLocation.FragmentDefinition), shouldInclude = alwaysInclude), Directive("onFragmentSpread", locations = Set(DirectiveLocation.FragmentSpread), shouldInclude = alwaysInclude), Directive("onInlineFragment", locations = Set(DirectiveLocation.InlineFragment), shouldInclude = alwaysInclude), + Directive("onVariableDefinition", locations = Set(DirectiveLocation.VariableDefinition), shouldInclude = alwaysInclude), Directive("onSchema", locations = Set(DirectiveLocation.Schema), shouldInclude = alwaysInclude), Directive("onScalar", locations = Set(DirectiveLocation.Scalar), shouldInclude = alwaysInclude), Directive("onObject", locations = Set(DirectiveLocation.Object), shouldInclude = alwaysInclude), diff --git a/src/test/scala/sangria/validation/rules/KnownDirectivesSpec.scala b/src/test/scala/sangria/validation/rules/KnownDirectivesSpec.scala index 5b52f792..b945faf8 100644 --- a/src/test/scala/sangria/validation/rules/KnownDirectivesSpec.scala +++ b/src/test/scala/sangria/validation/rules/KnownDirectivesSpec.scala @@ -67,8 +67,8 @@ class KnownDirectivesSpec extends WordSpec with ValidationSupport { "with well placed directives" in expectPasses( """ - query Foo @onQuery { - name @include(if: true) + query Foo($var: Boolean @onVariableDefinition) @onQuery { + name @include(if: $var) ...Frag @include(if: true) skippedField @skip(if: true) ...SkippedFrag @skip(if: true) @@ -79,10 +79,10 @@ class KnownDirectivesSpec extends WordSpec with ValidationSupport { } """) - "with misplaced directives" in expectFails( + "with misplaced directives" in expectFailsSimple( """ - query Foo @include(if: true) { - name @onQuery + query Foo($var: Boolean @onField) @include(if: true) { + name @onQuery @include(if: $var) ...Frag @onQuery } @@ -90,12 +90,11 @@ class KnownDirectivesSpec extends WordSpec with ValidationSupport { someField } """, - List( - "Directive 'include' may not be used on query operation." → Some(Pos(2, 21)), - "Directive 'onQuery' may not be used on field." → Some(Pos(3, 18)), - "Directive 'onQuery' may not be used on fragment spread." → Some(Pos(4, 21)), - "Directive 'onQuery' may not be used on mutation operation." → Some(Pos(7, 24)) - )) + "Directive 'onField' may not be used on variable definition." → Seq(Pos(2, 35)), + "Directive 'include' may not be used on query operation." → Seq(Pos(2, 45)), + "Directive 'onQuery' may not be used on field." → Seq(Pos(3, 18)), + "Directive 'onQuery' may not be used on fragment spread." → Seq(Pos(4, 21)), + "Directive 'onQuery' may not be used on mutation operation." → Seq(Pos(7, 24))) } "within schema language" should {