diff --git a/core/src/main/scala/caliban/parsing/Parser.scala b/core/src/main/scala/caliban/parsing/Parser.scala index 8050dfe31..a65d4ba17 100644 --- a/core/src/main/scala/caliban/parsing/Parser.scala +++ b/core/src/main/scala/caliban/parsing/Parser.scala @@ -9,6 +9,8 @@ import caliban.parsing.adt.Definition.ExecutableDefinition._ import caliban.parsing.adt.Definition.TypeSystemDefinition.DirectiveLocation._ import caliban.parsing.adt.Definition.TypeSystemDefinition._ import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition._ +import caliban.parsing.adt.Definition.TypeSystemExtension._ +import caliban.parsing.adt.Definition.TypeSystemExtension.TypeExtension._ import caliban.parsing.adt.Selection._ import caliban.parsing.adt.Type._ import caliban.parsing.adt._ @@ -116,7 +118,7 @@ object Parser { private def argument[_: P]: P[(String, InputValue)] = P(name ~ ":" ~ value) private def arguments[_: P]: P[Map[String, InputValue]] = P("(" ~/ argument.rep ~ ")").map(_.toMap) - private def directive[_: P]: P[Directive] = P(Index ~ "@" ~/ name ~ arguments.?).map { + private def directive[_: P]: P[Directive] = P(Index ~ "@" ~ name ~ arguments.?).map { case (index, name, arguments) => Directive(name, arguments.getOrElse(Map()), index) } private def directives[_: P]: P[List[Directive]] = P(directive.rep).map(_.toList) @@ -231,11 +233,10 @@ object Parser { EnumValueDefinition(description.map(_.value), enumValue, directives.getOrElse(Nil)) } + private def enumName[_: P]: P[String] = name.filter(s => s != "true" && s != "false" && s != "null") + private def enumTypeDefinition[_: P]: P[EnumTypeDefinition] = - P( - stringValue.? ~ "enum" ~/ name - .filter(s => s != "true" && s != "false" && s != "null") ~ directives.? ~ "{" ~ enumValueDefinition.rep ~ "}" - ).map { + P(stringValue.? ~ "enum" ~/ enumName ~ directives.? ~ "{" ~ enumValueDefinition.rep ~ "}").map { case (description, name, directives, enumValuesDefinition) => EnumTypeDefinition(description.map(_.value), name, directives.getOrElse(Nil), enumValuesDefinition.toList) } @@ -266,6 +267,142 @@ object Parser { ) } + private def schemaExtensionWithOptionalDirectivesAndOperations[_: P]: P[SchemaExtension] = + P(directives.? ~ "{" ~ rootOperationTypeDefinition.rep ~ "}").map { + case (directives, ops) => + val opsMap = ops.toMap + SchemaExtension( + directives.getOrElse(Nil), + opsMap.get(OperationType.Query).map(_.name), + opsMap.get(OperationType.Mutation).map(_.name), + opsMap.get(OperationType.Subscription).map(_.name) + ) + } + + private def schemaExtensionWithDirectives[_: P]: P[SchemaExtension] = + P(directives).map(SchemaExtension(_, None, None, None)) + + private def schemaExtension[_: P]: P[SchemaExtension] = + P("extend schema" ~/ (schemaExtensionWithOptionalDirectivesAndOperations | schemaExtensionWithDirectives)) + + private def scalarTypeExtension[_: P]: P[ScalarTypeExtension] = + P("extend scalar" ~/ name ~ directives).map { + case (name, directives) => + ScalarTypeExtension(name, directives) + } + + private def objectTypeExtensionWithOptionalInterfacesOptionalDirectivesAndFields[_: P]: P[ObjectTypeExtension] = + P(name ~ implements.? ~ directives.? ~ "{" ~ fieldDefinition.rep ~ "}").map { + case (name, implements, directives, fields) => + ObjectTypeExtension( + name, + implements.getOrElse(Nil), + directives.getOrElse(Nil), + fields.toList + ) + } + + private def objectTypeExtensionWithOptionalInterfacesAndDirectives[_: P]: P[ObjectTypeExtension] = + P(name ~ implements.? ~ directives ~ !("{" ~ fieldDefinition.rep ~ "}")).map { + case (name, implements, directives) => + ObjectTypeExtension( + name, + implements.getOrElse(Nil), + directives, + Nil + ) + } + + private def objectTypeExtensionWithInterfaces[_: P]: P[ObjectTypeExtension] = + P(name ~ implements).map { + case (name, implements) => + ObjectTypeExtension( + name, + implements, + Nil, + Nil + ) + } + + private def objectTypeExtension[_: P]: P[ObjectTypeExtension] = + P( + "extend type" ~/ ( + objectTypeExtensionWithOptionalInterfacesOptionalDirectivesAndFields | + objectTypeExtensionWithOptionalInterfacesAndDirectives | + objectTypeExtensionWithInterfaces + ) + ) + + private def interfaceTypeExtensionWithOptionalDirectivesAndFields[_: P]: P[InterfaceTypeExtension] = + P(name ~ directives.? ~ "{" ~ fieldDefinition.rep ~ "}").map { + case (name, directives, fields) => + InterfaceTypeExtension(name, directives.getOrElse(Nil), fields.toList) + } + + private def interfaceTypeExtensionWithDirectives[_: P]: P[InterfaceTypeExtension] = + P(name ~ directives).map { + case (name, directives) => + InterfaceTypeExtension(name, directives, Nil) + } + + private def interfaceTypeExtension[_: P]: P[InterfaceTypeExtension] = + P( + "extend interface" ~/ ( + interfaceTypeExtensionWithOptionalDirectivesAndFields | + interfaceTypeExtensionWithDirectives + ) + ) + + private def unionTypeExtensionWithOptionalDirectivesAndUnionMembers[_: P]: P[UnionTypeExtension] = + P(name ~ directives.? ~ "=" ~ ("|".? ~ namedType) ~ ("|" ~ namedType).rep).map { + case (name, directives, m, ms) => + UnionTypeExtension(name, directives.getOrElse(Nil), (m :: ms.toList).map(_.name)) + } + + private def unionTypeExtensionWithDirectives[_: P]: P[UnionTypeExtension] = + P(name ~ directives).map { + case (name, directives) => + UnionTypeExtension(name, directives, Nil) + } + + private def unionTypeExtension[_: P]: P[UnionTypeExtension] = + P("extend union" ~/ (unionTypeExtensionWithOptionalDirectivesAndUnionMembers | unionTypeExtensionWithDirectives)) + + private def enumTypeExtensionWithOptionalDirectivesAndValues[_: P]: P[EnumTypeExtension] = + P(enumName ~ directives.? ~ "{" ~ enumValueDefinition.rep ~ "}").map { + case (name, directives, enumValuesDefinition) => + EnumTypeExtension(name, directives.getOrElse(Nil), enumValuesDefinition.toList) + } + + private def enumTypeExtensionWithDirectives[_: P]: P[EnumTypeExtension] = + P(enumName ~ directives).map { + case (name, directives) => + EnumTypeExtension(name, directives, Nil) + } + + private def enumTypeExtension[_: P]: P[EnumTypeExtension] = + P("extend enum" ~/ (enumTypeExtensionWithOptionalDirectivesAndValues | enumTypeExtensionWithDirectives)) + + private def inputObjectTypeExtensionWithOptionalDirectivesAndFields[_: P]: P[InputObjectTypeExtension] = + P(name ~ directives.? ~ "{" ~ argumentDefinition.rep ~ "}").map { + case (name, directives, fields) => + InputObjectTypeExtension(name, directives.getOrElse(Nil), fields.toList) + } + + private def inputObjectTypeExtensionWithDirectives[_: P]: P[InputObjectTypeExtension] = + P(name ~ directives).map { + case (name, directives) => + InputObjectTypeExtension(name, directives, Nil) + } + + private def inputObjectTypeExtension[_: P]: P[InputObjectTypeExtension] = + P( + "extend input" ~/ ( + inputObjectTypeExtensionWithOptionalDirectivesAndFields | + inputObjectTypeExtensionWithDirectives + ) + ) + private def directiveLocation[_: P]: P[DirectiveLocation] = P( StringIn( @@ -331,7 +468,18 @@ object Parser { private def executableDefinition[_: P]: P[ExecutableDefinition] = P(operationDefinition | fragmentDefinition) - private def definition[_: P]: P[Definition] = executableDefinition | typeSystemDefinition + private def typeExtension[_: P]: P[TypeExtension] = + objectTypeExtension | + interfaceTypeExtension | + inputObjectTypeExtension | + enumTypeExtension | + unionTypeExtension | + scalarTypeExtension + + private def typeSystemExtension[_: P]: P[TypeSystemExtension] = + schemaExtension | typeExtension + + private def definition[_: P]: P[Definition] = executableDefinition | typeSystemDefinition | typeSystemExtension private def document[_: P]: P[ParsedDocument] = P(Start ~ ignored ~ definition.rep ~ ignored ~ End).map(seq => ParsedDocument(seq.toList)) diff --git a/core/src/main/scala/caliban/parsing/adt/Definition.scala b/core/src/main/scala/caliban/parsing/adt/Definition.scala index 22ef07488..bfc50179c 100644 --- a/core/src/main/scala/caliban/parsing/adt/Definition.scala +++ b/core/src/main/scala/caliban/parsing/adt/Definition.scala @@ -1,7 +1,11 @@ package caliban.parsing.adt import caliban.InputValue -import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition.InputValueDefinition +import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition.{ + EnumValueDefinition, + FieldDefinition, + InputValueDefinition +} import caliban.parsing.adt.Type.NamedType sealed trait Definition @@ -133,4 +137,57 @@ object Definition { } } + + sealed trait TypeSystemExtension extends Definition + + object TypeSystemExtension { + + case class SchemaExtension( + directives: List[Directive], + query: Option[String], + mutation: Option[String], + subscription: Option[String] + ) extends TypeSystemExtension + + sealed trait TypeExtension extends TypeSystemExtension + + object TypeExtension { + + case class ScalarTypeExtension( + name: String, + directives: List[Directive] + ) extends TypeExtension + + case class ObjectTypeExtension( + name: String, + implements: List[NamedType], + directives: List[Directive], + fields: List[FieldDefinition] + ) extends TypeExtension + + case class InterfaceTypeExtension( + name: String, + directives: List[Directive], + fields: List[FieldDefinition] + ) extends TypeExtension + + case class UnionTypeExtension( + name: String, + directives: List[Directive], + memberTypes: List[String] + ) extends TypeExtension + + case class EnumTypeExtension( + name: String, + directives: List[Directive], + enumValuesDefinition: List[EnumValueDefinition] + ) extends TypeExtension + + case class InputObjectTypeExtension( + name: String, + directives: List[Directive], + fields: List[InputValueDefinition] + ) extends TypeExtension + } + } } diff --git a/core/src/main/scala/caliban/validation/Validator.scala b/core/src/main/scala/caliban/validation/Validator.scala index 12a08b6f3..d4e42f739 100644 --- a/core/src/main/scala/caliban/validation/Validator.scala +++ b/core/src/main/scala/caliban/validation/Validator.scala @@ -8,7 +8,7 @@ import caliban.introspection.Introspector import caliban.introspection.adt._ import caliban.parsing.SourceMapper import caliban.parsing.adt.Definition.ExecutableDefinition.{ FragmentDefinition, OperationDefinition } -import caliban.parsing.adt.Definition.TypeSystemDefinition +import caliban.parsing.adt.Definition.{ TypeSystemDefinition, TypeSystemExtension } import caliban.parsing.adt.OperationType._ import caliban.parsing.adt.Selection.{ Field, FragmentSpread, InlineFragment } import caliban.parsing.adt.Type.NamedType @@ -101,7 +101,7 @@ object Validator { } private def check(document: Document, rootType: RootType): IO[ValidationError, Map[String, FragmentDefinition]] = { - val (operations, fragments, _) = collectDefinitions(document) + val (operations, fragments, _, _) = collectDefinitions(document) for { fragmentMap <- validateFragments(fragments) selectionSets = collectSelectionSets(operations.flatMap(_.selectionSet) ++ fragments.flatMap(_.selectionSet)) @@ -118,13 +118,23 @@ object Validator { private def collectDefinitions( document: Document - ): (List[OperationDefinition], List[FragmentDefinition], List[TypeSystemDefinition]) = + ): (List[OperationDefinition], List[FragmentDefinition], List[TypeSystemDefinition], List[TypeSystemExtension]) = document.definitions.foldLeft( - (List.empty[OperationDefinition], List.empty[FragmentDefinition], List.empty[TypeSystemDefinition]) + ( + List.empty[OperationDefinition], + List.empty[FragmentDefinition], + List.empty[TypeSystemDefinition], + List.empty[TypeSystemExtension] + ) ) { - case ((operations, fragments, types), o: OperationDefinition) => (o :: operations, fragments, types) - case ((operations, fragments, types), f: FragmentDefinition) => (operations, f :: fragments, types) - case ((operations, fragments, types), t: TypeSystemDefinition) => (operations, fragments, t :: types) + case ((operations, fragments, types, extensions), o: OperationDefinition) => + (o :: operations, fragments, types, extensions) + case ((operations, fragments, types, extensions), f: FragmentDefinition) => + (operations, f :: fragments, types, extensions) + case ((operations, fragments, types, extensions), t: TypeSystemDefinition) => + (operations, fragments, t :: types, extensions) + case ((operations, fragments, types, extensions), e: TypeSystemExtension) => + (operations, fragments, types, e :: extensions) } private def collectVariablesUsed(context: Context, selectionSet: List[Selection]): Set[String] = { @@ -309,6 +319,7 @@ object Validator { } case _: FragmentDefinition => IO.unit case _: TypeSystemDefinition => IO.unit + case _: TypeSystemExtension => IO.unit } .unit diff --git a/core/src/test/scala/caliban/parsing/ParserSpec.scala b/core/src/test/scala/caliban/parsing/ParserSpec.scala index dddd8f629..a25ae88c7 100644 --- a/core/src/test/scala/caliban/parsing/ParserSpec.scala +++ b/core/src/test/scala/caliban/parsing/ParserSpec.scala @@ -6,13 +6,15 @@ import caliban.InputValue._ import caliban.Value._ import caliban.parsing.adt.Definition.ExecutableDefinition.{ FragmentDefinition, OperationDefinition } import caliban.parsing.adt.Definition.TypeSystemDefinition.TypeDefinition._ +import caliban.parsing.adt.Definition.TypeSystemExtension.SchemaExtension +import caliban.parsing.adt.Definition.TypeSystemExtension.TypeExtension._ import caliban.parsing.adt.OperationType.{ Mutation, Query } import caliban.parsing.adt.Selection.{ Field, FragmentSpread, InlineFragment } import caliban.parsing.adt.Type.{ ListType, NamedType } import caliban.parsing.adt._ import zio.test.Assertion._ -import zio.test.environment.TestEnvironment import zio.test._ +import zio.test.environment.TestEnvironment object ParserSpec extends DefaultRunnableSpec { @@ -467,6 +469,546 @@ object ParserSpec extends DefaultRunnableSpec { ) ) ) + }, + testM("extend schema with directives") { + val gqlSchemaExtension = "extend schema @addedDirective" + assertM(Parser.parseQuery(gqlSchemaExtension))( + equalTo( + Document( + List( + SchemaExtension( + List(Directive("addedDirective", index = 14)), + None, + None, + None + ) + ), + sourceMapper = SourceMapper.apply(gqlSchemaExtension) + ) + ) + ) + }, + testM("extend schema with directives and operations") { + val gqlSchemaExtension = + """ + |extend schema @addedDirective { + | query: Query + | mutation: Mutation + | subscription: Subscription + |} + |""".stripMargin + assertM(Parser.parseQuery(gqlSchemaExtension))( + equalTo( + Document( + List( + SchemaExtension( + List(Directive("addedDirective", index = 15)), + Some("Query"), + Some("Mutation"), + Some("Subscription") + ) + ), + sourceMapper = SourceMapper.apply(gqlSchemaExtension) + ) + ) + ) + }, + testM("extend schema with operations") { + val gqlSchemaExtension = + """ + |extend schema { + | query: Query + | mutation: Mutation + | subscription: Subscription + |} + |""".stripMargin + assertM(Parser.parseQuery(gqlSchemaExtension))( + equalTo( + Document( + List( + SchemaExtension( + Nil, + Some("Query"), + Some("Mutation"), + Some("Subscription") + ) + ), + sourceMapper = SourceMapper.apply(gqlSchemaExtension) + ) + ) + ) + }, + testM("extend scalar with directives") { + val gqlScalarExtension = "extend scalar SomeScalar @foo(arg0: $someTestM)" + assertM(Parser.parseQuery(gqlScalarExtension))( + equalTo( + Document( + List( + ScalarTypeExtension( + "SomeScalar", + List(Directive("foo", Map("arg0" -> VariableValue("someTestM")), index = 25)) + ) + ), + sourceMapper = SourceMapper.apply(gqlScalarExtension) + ) + ) + ) + }, + testM("extend type with interfaces") { + val gqlTypeExtension = "extend type Hero implements SomeInterface" + assertM(Parser.parseQuery(gqlTypeExtension))( + equalTo( + Document( + List( + ObjectTypeExtension( + "Hero", + List(NamedType("SomeInterface", false)), + Nil, + Nil + ) + ), + sourceMapper = SourceMapper.apply(gqlTypeExtension) + ) + ) + ) + }, + testM("extend type with interfaces and fields") { + val gqlTypeExtension = + """extend type Hero implements SomeInterface { + |"name desc" name(pad: Int!): String! @skip(if: $someTestM) + |"nick desc" nick: String! + |bday: Int + |suits: [String] + |powers: [String!]! + |}""".stripMargin + assertM(Parser.parseQuery(gqlTypeExtension))( + equalTo( + Document( + List( + ObjectTypeExtension( + "Hero", + List(NamedType("SomeInterface", false)), + Nil, + List( + FieldDefinition( + Some("name desc"), + "name", + List(InputValueDefinition(None, "pad", NamedType("Int", true), None, Nil)), + NamedType("String", true), + List(Directive("skip", Map("if" -> VariableValue("someTestM")), index = 81)) + ), + FieldDefinition(Some("nick desc"), "nick", List(), NamedType("String", true), List()), + FieldDefinition(None, "bday", List(), NamedType("Int", false), List()), + FieldDefinition(None, "suits", List(), ListType(NamedType("String", false), false), List()), + FieldDefinition(None, "powers", List(), ListType(NamedType("String", true), true), List()) + ) + ) + ), + sourceMapper = SourceMapper.apply(gqlTypeExtension) + ) + ) + ) + }, + testM("extend type with interfaces and directives") { + val gqlTypeExtension = "extend type Hero implements SomeInterface @addedDirective" + assertM(Parser.parseQuery(gqlTypeExtension))( + equalTo( + Document( + List( + ObjectTypeExtension( + "Hero", + List(NamedType("SomeInterface", false)), + List(Directive("addedDirective", index = 42)), + Nil + ) + ), + sourceMapper = SourceMapper.apply(gqlTypeExtension) + ) + ) + ) + }, + testM("extend type with interfaces, directives and fields") { + val gqlTypeExtension = + """extend type Hero implements SomeInterface @addedDirective { + |"name desc" name(pad: Int!): String! @skip(if: $someTestM) + |"nick desc" nick: String! + |bday: Int + |suits: [String] + |powers: [String!]! + |}""".stripMargin + assertM(Parser.parseQuery(gqlTypeExtension))( + equalTo( + Document( + List( + ObjectTypeExtension( + "Hero", + List(NamedType("SomeInterface", false)), + List(Directive("addedDirective", index = 42)), + List( + FieldDefinition( + Some("name desc"), + "name", + List(InputValueDefinition(None, "pad", NamedType("Int", true), None, Nil)), + NamedType("String", true), + List(Directive("skip", Map("if" -> VariableValue("someTestM")), index = 97)) + ), + FieldDefinition(Some("nick desc"), "nick", List(), NamedType("String", true), List()), + FieldDefinition(None, "bday", List(), NamedType("Int", false), List()), + FieldDefinition(None, "suits", List(), ListType(NamedType("String", false), false), List()), + FieldDefinition(None, "powers", List(), ListType(NamedType("String", true), true), List()) + ) + ) + ), + sourceMapper = SourceMapper.apply(gqlTypeExtension) + ) + ) + ) + }, + testM("extend type with directives") { + val gqlTypeExtension = "extend type Hero @addedDirective" + assertM(Parser.parseQuery(gqlTypeExtension))( + equalTo( + Document( + List( + ObjectTypeExtension( + "Hero", + Nil, + List(Directive("addedDirective", index = 17)), + Nil + ) + ), + sourceMapper = SourceMapper.apply(gqlTypeExtension) + ) + ) + ) + }, + testM("extend type with directives and fields") { + val gqlTypeExtension = + """extend type Hero @addedDirective { + |"name desc" name(pad: Int!): String! @skip(if: $someTestM) + |"nick desc" nick: String! + |bday: Int + |suits: [String] + |powers: [String!]! + |}""".stripMargin + assertM(Parser.parseQuery(gqlTypeExtension))( + equalTo( + Document( + List( + ObjectTypeExtension( + "Hero", + Nil, + List(Directive("addedDirective", index = 17)), + List( + FieldDefinition( + Some("name desc"), + "name", + List(InputValueDefinition(None, "pad", NamedType("Int", true), None, Nil)), + NamedType("String", true), + List(Directive("skip", Map("if" -> VariableValue("someTestM")), index = 72)) + ), + FieldDefinition(Some("nick desc"), "nick", List(), NamedType("String", true), List()), + FieldDefinition(None, "bday", List(), NamedType("Int", false), List()), + FieldDefinition(None, "suits", List(), ListType(NamedType("String", false), false), List()), + FieldDefinition(None, "powers", List(), ListType(NamedType("String", true), true), List()) + ) + ) + ), + sourceMapper = SourceMapper.apply(gqlTypeExtension) + ) + ) + ) + }, + testM("extend type with fields") { + val gqlTypeExtension = + """extend type Hero { + |"name desc" name(pad: Int!): String! @skip(if: $someTestM) + |"nick desc" nick: String! + |bday: Int + |suits: [String] + |powers: [String!]! + |}""".stripMargin + assertM(Parser.parseQuery(gqlTypeExtension))( + equalTo( + Document( + List( + ObjectTypeExtension( + "Hero", + Nil, + Nil, + List( + FieldDefinition( + Some("name desc"), + "name", + List(InputValueDefinition(None, "pad", NamedType("Int", true), None, Nil)), + NamedType("String", true), + List(Directive("skip", Map("if" -> VariableValue("someTestM")), index = 56)) + ), + FieldDefinition(Some("nick desc"), "nick", List(), NamedType("String", true), List()), + FieldDefinition(None, "bday", List(), NamedType("Int", false), List()), + FieldDefinition(None, "suits", List(), ListType(NamedType("String", false), false), List()), + FieldDefinition(None, "powers", List(), ListType(NamedType("String", true), true), List()) + ) + ) + ), + sourceMapper = SourceMapper.apply(gqlTypeExtension) + ) + ) + ) + }, + testM("extend interface with directives") { + val gqlInterfaceExtension = "extend interface NamedEntity @addedDirective" + assertM(Parser.parseQuery(gqlInterfaceExtension))( + equalTo( + Document( + List( + InterfaceTypeExtension( + "NamedEntity", + List(Directive("addedDirective", index = 29)), + Nil + ) + ), + sourceMapper = SourceMapper.apply(gqlInterfaceExtension) + ) + ) + ) + }, + testM("extend interface with directives and fields") { + val gqlInterfaceExtension = + """ + |extend interface NamedEntity @addedDirective { + | nickname: String + |} + |""".stripMargin + assertM(Parser.parseQuery(gqlInterfaceExtension))( + equalTo( + Document( + List( + InterfaceTypeExtension( + "NamedEntity", + List(Directive("addedDirective", index = 30)), + List(FieldDefinition(None, "nickname", Nil, NamedType("String", false), Nil)) + ) + ), + sourceMapper = SourceMapper.apply(gqlInterfaceExtension) + ) + ) + ) + }, + testM("extend interface with fields") { + val gqlInterfaceExtension = + """ + |extend interface NamedEntity { + | nickname: String + |} + |""".stripMargin + assertM(Parser.parseQuery(gqlInterfaceExtension))( + equalTo( + Document( + List( + InterfaceTypeExtension( + "NamedEntity", + Nil, + List(FieldDefinition(None, "nickname", Nil, NamedType("String", false), Nil)) + ) + ), + sourceMapper = SourceMapper.apply(gqlInterfaceExtension) + ) + ) + ) + }, + testM("extend union with directives") { + val gqlUnionExtension = "extend union SearchResult @addedDirective" + assertM(Parser.parseQuery(gqlUnionExtension))( + equalTo( + Document( + List( + UnionTypeExtension( + "SearchResult", + List(Directive("addedDirective", index = 26)), + Nil + ) + ), + sourceMapper = SourceMapper.apply(gqlUnionExtension) + ) + ) + ) + }, + testM("extend union with directives and union members") { + val gqlUnionExtension = "extend union SearchResult @addedDirective = Photo | Person" + assertM(Parser.parseQuery(gqlUnionExtension))( + equalTo( + Document( + List( + UnionTypeExtension( + "SearchResult", + List(Directive("addedDirective", index = 26)), + List("Photo", "Person") + ) + ), + sourceMapper = SourceMapper.apply(gqlUnionExtension) + ) + ) + ) + }, + testM("extend union with union members") { + val gqlUnionExtension = "extend union SearchResult = Photo | Person" + assertM(Parser.parseQuery(gqlUnionExtension))( + equalTo( + Document( + List( + UnionTypeExtension( + "SearchResult", + Nil, + List("Photo", "Person") + ) + ), + sourceMapper = SourceMapper.apply(gqlUnionExtension) + ) + ) + ) + }, + testM("extend enum with directives") { + val gqlEnumExtension = "extend enum Direction @addedDirective" + assertM(Parser.parseQuery(gqlEnumExtension))( + equalTo( + Document( + List( + EnumTypeExtension( + "Direction", + List(Directive("addedDirective", index = 22)), + Nil + ) + ), + sourceMapper = SourceMapper.apply(gqlEnumExtension) + ) + ) + ) + }, + testM("extend enum with directives and values") { + val gqlEnumExtension = + """ + |extend enum Direction @addedDirective { + | NORTH_WEST + | NORTH_EAST + | SOUTH_WEST + | SOUTH_EAST + |} + |""".stripMargin + assertM(Parser.parseQuery(gqlEnumExtension))( + equalTo( + Document( + List( + EnumTypeExtension( + "Direction", + List(Directive("addedDirective", index = 23)), + List( + EnumValueDefinition(None, "NORTH_WEST", Nil), + EnumValueDefinition(None, "NORTH_EAST", Nil), + EnumValueDefinition(None, "SOUTH_WEST", Nil), + EnumValueDefinition(None, "SOUTH_EAST", Nil) + ) + ) + ), + sourceMapper = SourceMapper.apply(gqlEnumExtension) + ) + ) + ) + }, + testM("extend enum with values") { + val gqlEnumExtension = + """ + |extend enum Direction { + | NORTH_WEST + | NORTH_EAST + | SOUTH_WEST + | SOUTH_EAST + |} + |""".stripMargin + assertM(Parser.parseQuery(gqlEnumExtension))( + equalTo( + Document( + List( + EnumTypeExtension( + "Direction", + Nil, + List( + EnumValueDefinition(None, "NORTH_WEST", Nil), + EnumValueDefinition(None, "NORTH_EAST", Nil), + EnumValueDefinition(None, "SOUTH_WEST", Nil), + EnumValueDefinition(None, "SOUTH_EAST", Nil) + ) + ) + ), + sourceMapper = SourceMapper.apply(gqlEnumExtension) + ) + ) + ) + }, + testM("extend input with directives") { + val gqlInputExtension = "extend input Point @addedDirective" + assertM(Parser.parseQuery(gqlInputExtension))( + equalTo( + Document( + List( + InputObjectTypeExtension( + "Point", + List(Directive("addedDirective", index = 19)), + Nil + ) + ), + sourceMapper = SourceMapper.apply(gqlInputExtension) + ) + ) + ) + }, + testM("extend input with directives and fields") { + val gqlInputExtension = + """ + |extend input Point @addedDirective { + | z: Int! + |} + |""".stripMargin + assertM(Parser.parseQuery(gqlInputExtension))( + equalTo( + Document( + List( + InputObjectTypeExtension( + "Point", + List(Directive("addedDirective", index = 20)), + List( + InputValueDefinition(None, "z", NamedType("Int", nonNull = true), None, Nil) + ) + ) + ), + sourceMapper = SourceMapper.apply(gqlInputExtension) + ) + ) + ) + }, + testM("extend input with fields") { + val gqlInputExtension = + """ + |extend input Point { + | z: Int! + |} + |""".stripMargin + assertM(Parser.parseQuery(gqlInputExtension))( + equalTo( + Document( + List( + InputObjectTypeExtension( + "Point", + Nil, + List( + InputValueDefinition(None, "z", NamedType("Int", nonNull = true), None, Nil) + ) + ) + ), + sourceMapper = SourceMapper.apply(gqlInputExtension) + ) + ) + ) } )