diff --git a/src/main/scala/sangria/ast/QueryAst.scala b/src/main/scala/sangria/ast/QueryAst.scala index 8fd73606..0d68257c 100644 --- a/src/main/scala/sangria/ast/QueryAst.scala +++ b/src/main/scala/sangria/ast/QueryAst.scala @@ -467,9 +467,10 @@ case class DirectiveLocation( case class SchemaDefinition( operationTypes: Vector[OperationTypeDefinition], directives: Vector[Directive] = Vector.empty, + description: Option[StringValue] = None, comments: Vector[Comment] = Vector.empty, trailingComments: Vector[Comment] = Vector.empty, - location: Option[AstLocation] = None) extends TypeSystemDefinition with WithTrailingComments with WithDirectives + location: Option[AstLocation] = None) extends TypeSystemDefinition with WithDescription with WithTrailingComments with WithDirectives case class OperationTypeDefinition( operation: OperationType, @@ -921,10 +922,11 @@ object AstVisitor { comment.foreach(s ⇒ loop(s)) breakOrSkip(onLeave(n)) } - case n @ SchemaDefinition(ops, dirs, comment, trailingComments, _) ⇒ + case n @ SchemaDefinition(ops, dirs, descr, comment, trailingComments, _) ⇒ if (breakOrSkip(onEnter(n))) { ops.foreach(s ⇒ loop(s)) dirs.foreach(s ⇒ loop(s)) + descr.foreach(s ⇒ loop(s)) comment.foreach(s ⇒ loop(s)) trailingComments.foreach(s ⇒ loop(s)) breakOrSkip(onLeave(n)) diff --git a/src/main/scala/sangria/introspection/IntrospectionParser.scala b/src/main/scala/sangria/introspection/IntrospectionParser.scala index db2ce79f..3f22f4cb 100644 --- a/src/main/scala/sangria/introspection/IntrospectionParser.scala +++ b/src/main/scala/sangria/introspection/IntrospectionParser.scala @@ -101,7 +101,8 @@ object IntrospectionParser { mutationType = mapFieldOpt(schema, "mutationType") map (parseNamedTypeRef(_, path :+ "mutationType")), subscriptionType = mapFieldOpt(schema, "subscriptionType") map (parseNamedTypeRef(_, path :+ "subscriptionType")), types = um.getListValue(mapField(schema, "types", path)) map (parseType(_, path :+ "types")), - directives = mapFieldOpt(schema, "directives") map um.getListValue getOrElse Vector.empty map (i ⇒ parseDirective(i, path :+ "directives"))) + directives = mapFieldOpt(schema, "directives") map um.getListValue getOrElse Vector.empty map (i ⇒ parseDirective(i, path :+ "directives")), + description = mapStringFieldOpt(schema, "description", path)) private def parseNamedTypeRef[In : InputUnmarshaller](in: In, path: Vector[String]) = IntrospectionNamedTypeRef(mapStringFieldOpt(in, "kind", path) map TypeKind.fromString getOrElse TypeKind.Object, mapStringField(in, "name", path)) diff --git a/src/main/scala/sangria/introspection/model.scala b/src/main/scala/sangria/introspection/model.scala index c67df73c..d2bc102c 100644 --- a/src/main/scala/sangria/introspection/model.scala +++ b/src/main/scala/sangria/introspection/model.scala @@ -9,7 +9,8 @@ case class IntrospectionSchema( mutationType: Option[IntrospectionNamedTypeRef], subscriptionType: Option[IntrospectionNamedTypeRef], types: Seq[IntrospectionType], - directives: Seq[IntrospectionDirective] + directives: Seq[IntrospectionDirective], + description: Option[String] ) { def toAst = SchemaRenderer.schemaAstFromIntrospection(this) def toAst(filter: SchemaFilter): Document = SchemaRenderer.schemaAstFromIntrospection(this, filter) diff --git a/src/main/scala/sangria/introspection/package.scala b/src/main/scala/sangria/introspection/package.scala index fcab5d34..ff52405b 100644 --- a/src/main/scala/sangria/introspection/package.scala +++ b/src/main/scala/sangria/introspection/package.scala @@ -1,6 +1,7 @@ package sangria import sangria.parser.QueryParser +import sangria.parser.DeliveryScheme.Throw import sangria.schema._ import scala.util.Success @@ -277,6 +278,7 @@ package object introspection { "server. It exposes all available types and directives on " + "the server, as well as the entry points for query, mutation, and subscription operations.", fields = List[Field[Unit, Schema[Any, Any]]]( + Field("description", OptionType(StringType), resolve = _.value.description), Field("types", ListType(__Type), Some("A list of all types supported by this server."), resolve = _.value.typeList map (true → _)), Field("queryType", __Type, Some("The type that query operations will be rooted at."), @@ -320,95 +322,99 @@ package object introspection { val IntrospectionTypesByName: Map[String, Type with Named] = IntrospectionTypes.groupBy(_.name).mapValues(_.head) - lazy val Success(introspectionQuery) = QueryParser.parse( - """ - |query IntrospectionQuery { - | __schema { - | queryType { name } - | mutationType { name } - | subscriptionType { name } - | types { - | ...FullType - | } - | directives { - | name - | description - | locations - | args { - | ...InputValue - | } - | } - | } - |} - |fragment FullType on __Type { - | kind - | name - | description - | fields(includeDeprecated: true) { - | name - | description - | args { - | ...InputValue - | } - | type { - | ...TypeRef - | } - | isDeprecated - | deprecationReason - | } - | inputFields { - | ...InputValue - | } - | interfaces { - | ...TypeRef - | } - | enumValues(includeDeprecated: true) { - | name - | description - | isDeprecated - | deprecationReason - | } - | possibleTypes { - | ...TypeRef - | } - |} - |fragment InputValue on __InputValue { - | name - | description - | type { ...TypeRef } - | defaultValue - |} - |fragment TypeRef on __Type { - | kind - | name - | ofType { - | kind - | name - | ofType { - | kind - | name - | ofType { - | kind - | name - | ofType { - | kind - | name - | ofType { - | kind - | name - | ofType { - | kind - | name - | ofType { - | kind - | name - | } - | } - | } - | } - | } - | } - | } - |} - """.stripMargin) + def introspectionQuery: ast.Document = introspectionQuery() + + def introspectionQuery(schemaDescription: Boolean = true): ast.Document = + QueryParser.parse(introspectionQueryString(schemaDescription)) + + def introspectionQueryString(schemaDescription: Boolean = true): String = + s"""query IntrospectionQuery { + | __schema { + | queryType { name } + | mutationType { name } + | subscriptionType { name } + | types { + | ...FullType + | } + | directives { + | name + | description + | locations + | args { + | ...InputValue + | } + | } + | ${if (schemaDescription) "description" else ""} + | } + |} + |fragment FullType on __Type { + | kind + | name + | description + | fields(includeDeprecated: true) { + | name + | description + | args { + | ...InputValue + | } + | type { + | ...TypeRef + | } + | isDeprecated + | deprecationReason + | } + | inputFields { + | ...InputValue + | } + | interfaces { + | ...TypeRef + | } + | enumValues(includeDeprecated: true) { + | name + | description + | isDeprecated + | deprecationReason + | } + | possibleTypes { + | ...TypeRef + | } + |} + |fragment InputValue on __InputValue { + | name + | description + | type { ...TypeRef } + | defaultValue + |} + |fragment TypeRef on __Type { + | kind + | name + | ofType { + | kind + | name + | ofType { + | kind + | name + | ofType { + | kind + | name + | ofType { + | kind + | name + | ofType { + | kind + | name + | ofType { + | kind + | name + | ofType { + | kind + | name + | } + | } + | } + | } + | } + | } + | } + |}""".stripMargin } diff --git a/src/main/scala/sangria/macros/AstLiftable.scala b/src/main/scala/sangria/macros/AstLiftable.scala index 28e81aa4..d533ee06 100644 --- a/src/main/scala/sangria/macros/AstLiftable.scala +++ b/src/main/scala/sangria/macros/AstLiftable.scala @@ -78,8 +78,8 @@ trait AstLiftable { case DirectiveDefinition(n, a, l, desc, c, p) ⇒ q"_root_.sangria.ast.DirectiveDefinition($n, $a, $l, $desc, $c, $p)" - case SchemaDefinition(o, d, c, tc, p) ⇒ - q"_root_.sangria.ast.SchemaDefinition($o, $d, $c, $tc, $p)" + case SchemaDefinition(o, d, desc, c, tc, p) ⇒ + q"_root_.sangria.ast.SchemaDefinition($o, $d, $desc, $c, $tc, $p)" case ObjectTypeExtensionDefinition(n, i, f, d, c, tc, p) ⇒ q"_root_.sangria.ast.ObjectTypeExtensionDefinition($n, $i, $f, $d, $c, $tc, $p)" diff --git a/src/main/scala/sangria/parser/QueryParser.scala b/src/main/scala/sangria/parser/QueryParser.scala index c6aae99b..5a385ada 100644 --- a/src/main/scala/sangria/parser/QueryParser.scala +++ b/src/main/scala/sangria/parser/QueryParser.scala @@ -358,8 +358,8 @@ trait TypeSystemDefinitions { this: Parser with Tokens with Ignored with Directi } def SchemaDefinition = rule { - Comments ~ trackPos ~ schema ~ (DirectivesConst.? ~> (_ getOrElse Vector.empty)) ~ wsNoComment('{') ~ OperationTypeDefinition.+ ~ Comments ~ wsNoComment('}') ~> ( - (comment, location, dirs, ops, tc) ⇒ ast.SchemaDefinition(ops.toVector, dirs, comment, tc, location)) + Description ~ Comments ~ trackPos ~ schema ~ (DirectivesConst.? ~> (_ getOrElse Vector.empty)) ~ wsNoComment('{') ~ OperationTypeDefinition.+ ~ Comments ~ wsNoComment('}') ~> ( + (descr, comment, location, dirs, ops, tc) ⇒ ast.SchemaDefinition(ops.toVector, dirs, descr, comment, tc, location)) } def OperationTypeDefinition = rule { diff --git a/src/main/scala/sangria/renderer/QueryRenderer.scala b/src/main/scala/sangria/renderer/QueryRenderer.scala index 6bb2e1b1..76985acd 100644 --- a/src/main/scala/sangria/renderer/QueryRenderer.scala +++ b/src/main/scala/sangria/renderer/QueryRenderer.scala @@ -573,13 +573,9 @@ object QueryRenderer { case dl @ DirectiveLocation(name, _, _) ⇒ renderComment(dl, prev, indent, config) + indent.str + name - case sd @ SchemaDefinition(ops, dirs, _, _, _) ⇒ - val renderedOps = ops.zipWithIndex map { case (op, idx) ⇒ - (if (idx != 0 && shouldRenderComment(op, None, config)) config.lineBreak else "") + - renderNode(op, config, indent.inc) - } mkString config.mandatoryLineBreak - - renderComment(sd, prev, indent, config) + + case sd @ SchemaDefinition(ops, dirs, description, _, _, _) ⇒ + renderDescription(sd, prev, indent, config) + + renderComment(sd, description orElse prev, indent, config) + indent.str + "schema" + config.separator + renderDirs(dirs, config, indent) + renderOperationTypeDefinitions(ops, sd, indent, config) diff --git a/src/main/scala/sangria/renderer/SchemaRenderer.scala b/src/main/scala/sangria/renderer/SchemaRenderer.scala index 41cc5ac8..bcaeca39 100644 --- a/src/main/scala/sangria/renderer/SchemaRenderer.scala +++ b/src/main/scala/sangria/renderer/SchemaRenderer.scala @@ -170,18 +170,18 @@ object SchemaRenderer { val withMutation = schema.mutationType.fold(withQuery)(t ⇒ withQuery :+ ast.OperationTypeDefinition(ast.OperationType.Mutation, ast.NamedType(t.name))) val withSubs = schema.subscriptionType.fold(withMutation)(t ⇒ withMutation :+ ast.OperationTypeDefinition(ast.OperationType.Subscription, ast.NamedType(t.name))) - Some(ast.SchemaDefinition(withSubs)) + Some(ast.SchemaDefinition(withSubs, description = renderDescription(schema.description))) } private def renderSchemaDefinition(schema: Schema[_, _]): Option[ast.SchemaDefinition] = - if (isSchemaOfCommonNames(schema.query.name, schema.mutation.map(_.name), schema.subscription.map(_.name))) + if (isSchemaOfCommonNames(schema.query.name, schema.mutation.map(_.name), schema.subscription.map(_.name)) && schema.description.isEmpty && schema.astDirectives.isEmpty) None else { val withQuery = Vector(ast.OperationTypeDefinition(ast.OperationType.Query, ast.NamedType(schema.query.name))) val withMutation = schema.mutation.fold(withQuery)(t ⇒ withQuery :+ ast.OperationTypeDefinition(ast.OperationType.Mutation, ast.NamedType(t.name))) val withSubs = schema.subscription.fold(withMutation)(t ⇒ withMutation :+ ast.OperationTypeDefinition(ast.OperationType.Subscription, ast.NamedType(t.name))) - Some(ast.SchemaDefinition(withSubs, schema.astDirectives)) + Some(ast.SchemaDefinition(withSubs, schema.astDirectives, renderDescription(schema.description))) } private def isSchemaOfCommonNames(query: String, mutation: Option[String], subscription: Option[String]) = diff --git a/src/main/scala/sangria/schema/AstSchemaBuilder.scala b/src/main/scala/sangria/schema/AstSchemaBuilder.scala index e8a28b44..439eff2a 100644 --- a/src/main/scala/sangria/schema/AstSchemaBuilder.scala +++ b/src/main/scala/sangria/schema/AstSchemaBuilder.scala @@ -321,6 +321,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] { mutation = mutationType, subscription = subscriptionType, additionalTypes = additionalTypes, + description = definition.flatMap(_.description.map(_.value)), directives = directives, astDirectives = definition.fold(Vector.empty[ast.Directive])(_.directives) ++ extensions.flatMap(_.directives), astNodes = Vector(mat.document) ++ extensions ++ definition.toVector) @@ -340,6 +341,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] { subscription = subscriptionType, additionalTypes = additionalTypes, directives = directives, + description = originalSchema.description, validationRules = originalSchema.validationRules, astDirectives = originalSchema.astDirectives ++ extensions.flatMap(_.directives), astNodes = { diff --git a/src/main/scala/sangria/schema/IntrospectionSchemaBuilder.scala b/src/main/scala/sangria/schema/IntrospectionSchemaBuilder.scala index dbfacc2a..a1c11127 100644 --- a/src/main/scala/sangria/schema/IntrospectionSchemaBuilder.scala +++ b/src/main/scala/sangria/schema/IntrospectionSchemaBuilder.scala @@ -104,7 +104,8 @@ class DefaultIntrospectionSchemaBuilder[Ctx] extends IntrospectionSchemaBuilder[ mutation = mutationType, subscription = subscriptionType, additionalTypes = additionalTypes, - directives = directives) + directives = directives, + description = definition.description) def buildObjectType( definition: IntrospectionObjectType, diff --git a/src/main/scala/sangria/schema/Schema.scala b/src/main/scala/sangria/schema/Schema.scala index 80724995..e65d5a1d 100644 --- a/src/main/scala/sangria/schema/Schema.scala +++ b/src/main/scala/sangria/schema/Schema.scala @@ -72,9 +72,12 @@ sealed trait MappedAbstractType[T] extends Type with AbstractType with OutputTyp sealed trait NullableType sealed trait UnmodifiedType -sealed trait Named { - def name: String +sealed trait HasDescription { def description: Option[String] +} + +sealed trait Named extends HasDescription { + def name: String def rename(newName: String): this.type } @@ -756,10 +759,11 @@ case class Schema[Ctx, Val]( mutation: Option[ObjectType[Ctx, Val]] = None, subscription: Option[ObjectType[Ctx, Val]] = None, additionalTypes: List[Type with Named] = Nil, + description: Option[String] = None, directives: List[Directive] = BuiltinDirectives, validationRules: List[SchemaValidationRule] = SchemaValidationRule.default, astDirectives: Vector[ast.Directive] = Vector.empty, - astNodes: Vector[ast.AstNode] = Vector.empty) extends HasAstInfo { + astNodes: Vector[ast.AstNode] = Vector.empty) extends HasAstInfo with HasDescription { def extend(document: ast.Document, builder: AstSchemaBuilder[Ctx] = AstSchemaBuilder.default[Ctx]): Schema[Ctx, Val] = AstSchemaMaterializer.extendSchema(this, document, builder) diff --git a/src/main/scala/sangria/schema/SchemaComparator.scala b/src/main/scala/sangria/schema/SchemaComparator.scala index bf40e35d..9036057f 100644 --- a/src/main/scala/sangria/schema/SchemaComparator.scala +++ b/src/main/scala/sangria/schema/SchemaComparator.scala @@ -108,7 +108,10 @@ object SchemaComparator { added = SchemaChange.SchemaAstDirectiveAdded(newSchema, _), removed = SchemaChange.SchemaAstDirectiveRemoved(newSchema, _)) - withSubscription ++ directiveChanges + val descriptionChanges = + findDescriptionChanged(oldSchema, newSchema, SchemaChange.SchemaDescriptionChanged) + + withSubscription ++ directiveChanges ++ descriptionChanges } def findChangesInTypes(oldType: Type with Named, newType: Type with Named): Vector[SchemaChange] = { @@ -417,7 +420,7 @@ object SchemaComparator { case tpe ⇒ tpe } - private def findDescriptionChanged(o: Named, n: Named, fn: (Option[String], Option[String]) ⇒ SchemaChange): Vector[SchemaChange] = + private def findDescriptionChanged(o: HasDescription, n: HasDescription, fn: (Option[String], Option[String]) ⇒ SchemaChange): Vector[SchemaChange] = if (o.description != n.description) Vector(fn(o.description, n.description)) else Vector.empty @@ -444,7 +447,7 @@ object SchemaChange { def newDeprecationReason: Option[String] } - sealed trait DescriptionChange extends TypeChange { + sealed trait DescriptionChange { def oldDescription: Option[String] def newDescription: Option[String] } @@ -500,13 +503,16 @@ object SchemaChange { case class DirectiveAdded(directive: Directive) extends AbstractChange(s"`${directive.name}` directive was added", false) case class TypeDescriptionChanged(tpe: Type with Named, oldDescription: Option[String], newDescription: Option[String]) - extends AbstractChange(s"`${tpe.name}` type description is changed", false) with DescriptionChange + extends AbstractChange(s"`${tpe.name}` type description is changed", false) with DescriptionChange with TypeChange case class EnumValueAdded(tpe: EnumType[_], value: EnumValue[_]) extends AbstractChange(s"Enum value `${value.name}` was added to enum `${tpe.name}`", false, true) with TypeChange case class EnumValueDescriptionChanged(tpe: EnumType[_], value: EnumValue[_], oldDescription: Option[String], newDescription: Option[String]) - extends AbstractChange(s"`${tpe.name}.${value.name}` description changed", false) with DescriptionChange + extends AbstractChange(s"`${tpe.name}.${value.name}` description changed", false) with DescriptionChange with TypeChange + + case class SchemaDescriptionChanged(oldDescription: Option[String], newDescription: Option[String]) + extends AbstractChange(s"Schema description changed", false) with DescriptionChange case class EnumValueDeprecated(tpe: EnumType[_], value: EnumValue[_], oldDeprecationReason: Option[String], newDeprecationReason: Option[String]) extends AbstractChange(s"Enum value `${value.name}` was deprecated in enum `${tpe.name}`", false) with DeprecationChange @@ -515,19 +521,19 @@ object SchemaChange { extends AbstractChange(s"`${member.name}` type was added to union `${tpe.name}`", false, true) with TypeChange case class InputFieldDescriptionChanged(tpe: InputObjectType[_], field: InputField[_], oldDescription: Option[String], newDescription: Option[String]) - extends AbstractChange(s"`${tpe.name}.${field.name}` description is changed", false) with DescriptionChange + extends AbstractChange(s"`${tpe.name}.${field.name}` description is changed", false) with DescriptionChange with TypeChange case class DirectiveDescriptionChanged(directive: Directive, oldDescription: Option[String], newDescription: Option[String]) extends AbstractChange(s"`${directive.name}` directive description is changed", false) case class FieldDescriptionChanged(tpe: ObjectLikeType[_, _], field: Field[_, _], oldDescription: Option[String], newDescription: Option[String]) - extends AbstractChange(s"`${tpe.name}.${field.name}` description is changed", false) with DescriptionChange + extends AbstractChange(s"`${tpe.name}.${field.name}` description is changed", false) with DescriptionChange with TypeChange case class ObjectTypeArgumentDescriptionChanged(tpe: ObjectLikeType[_, _], field: Field[_, _], argument: Argument[_], oldDescription: Option[String], newDescription: Option[String]) - extends AbstractChange(s"`${tpe.name}.${field.name}(${argument.name})` description is changed", false) with DescriptionChange + extends AbstractChange(s"`${tpe.name}.${field.name}(${argument.name})` description is changed", false) with DescriptionChange with TypeChange case class DirectiveArgumentDescriptionChanged(directive: Directive, argument: Argument[_], oldDescription: Option[String], newDescription: Option[String]) - extends AbstractChange(s"`${directive.name}(${argument.name})` description is changed", false) + extends AbstractChange(s"`${directive.name}(${argument.name})` description is changed", false) with DescriptionChange case class FieldDeprecationChanged(tpe: ObjectLikeType[_, _], field: Field[_, _], oldDeprecationReason: Option[String], newDeprecationReason: Option[String]) extends AbstractChange(s"Field `${field.name}` was deprecated in `${tpe.name}` type", false) with DeprecationChange diff --git a/src/test/scala/sangria/introspection/IntrospectionSpec.scala b/src/test/scala/sangria/introspection/IntrospectionSpec.scala index d3496d04..cabc9391 100644 --- a/src/test/scala/sangria/introspection/IntrospectionSpec.scala +++ b/src/test/scala/sangria/introspection/IntrospectionSpec.scala @@ -4,7 +4,7 @@ import org.scalatest.{Matchers, WordSpec} import sangria.execution.Executor import sangria.parser.QueryParser import sangria.schema._ -import sangria.util.FutureResultSupport +import sangria.util.{DebugUtil, FutureResultSupport} import sangria.validation.QueryValidator import scala.concurrent.ExecutionContext.Implicits.global @@ -24,11 +24,9 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "subscriptionType" → null, "types" → Vector( Map( - "inputFields" → null, + "kind" → "OBJECT", "name" → "QueryRoot", "description" → null, - "interfaces" → Nil, - "enumValues" → null, "fields" → Vector( Map( "name" → "foo", @@ -43,45 +41,42 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → null)), "isDeprecated" → false, "deprecationReason" → null)), - "kind" → "OBJECT", - "possibleTypes" → null - ), - Map( "inputFields" → null, - "name" → "__Directive", - "description" → "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL’s execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", - "interfaces" → Nil, + "interfaces" → Vector.empty, "enumValues" → null, + "possibleTypes" → null), + Map( + "kind" → "OBJECT", + "name" → "__Directive", + "description" → "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL\u2019s execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", "fields" → Vector( Map( "name" → "name", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "description", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null)), + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "locations", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, @@ -94,12 +89,13 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → Map( "kind" → "ENUM", "name" → "__DirectiveLocation", - "ofType" → null))))), - Map("name" → "args", - "description" → null, + "ofType" → null)))), "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "deprecationReason" → null), + Map( + "name" → "args", + "description" → null, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, @@ -112,52 +108,58 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → Map( "kind" → "OBJECT", "name" → "__InputValue", - "ofType" → null))))), + "ofType" → null)))), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "onOperation", "description" → null, - "isDeprecated" → true, - "deprecationReason" → "Use `locations`.", - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "SCALAR", "name" → "Boolean", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → true, + "deprecationReason" → "Use `locations`."), Map( "name" → "onFragment", "description" → null, - "isDeprecated" → true, - "deprecationReason" → "Use `locations`.", - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "SCALAR", "name" → "Boolean", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → true, + "deprecationReason" → "Use `locations`."), Map( "name" → "onField", "description" → null, - "isDeprecated" → true, - "deprecationReason" → "Use `locations`.", - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "SCALAR", "name" → "Boolean", - "ofType" → null)))), - "kind" → "OBJECT", + "ofType" → null)), + "isDeprecated" → true, + "deprecationReason" → "Use `locations`.")), + "inputFields" → null, + "interfaces" → Vector.empty, + "enumValues" → null, "possibleTypes" → null), Map( - "inputFields" → null, + "kind" → "ENUM", "name" → "__DirectiveLocation", "description" → "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields" → null, + "inputFields" → null, "interfaces" → null, "enumValues" → Vector( Map( @@ -250,102 +252,94 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "description" → "Location adjacent to an input object field definition.", "isDeprecated" → false, "deprecationReason" → null)), - "fields" → null, - "kind" → "ENUM", "possibleTypes" → null), Map( - "inputFields" → null, + "kind" → "OBJECT", "name" → "__EnumValue", "description" → "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", - "interfaces" → Nil, - "enumValues" → null, "fields" → Vector( Map( "name" → "name", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "description", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null)), + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "isDeprecated", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "SCALAR", "name" → "Boolean", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "deprecationReason", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null)) - ), - "kind" → "OBJECT", - "possibleTypes" → null - ), - Map( + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null)), "inputFields" → null, + "interfaces" → Vector.empty, + "enumValues" → null, + "possibleTypes" → null), + Map( + "kind" → "OBJECT", "name" → "__Field", "description" → "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", - "interfaces" → Nil, - "enumValues" → null, "fields" → Vector( Map( "name" → "name", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "description", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null)), + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "args", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, @@ -358,117 +352,123 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → Map( "kind" → "OBJECT", "name" → "__InputValue", - "ofType" → null))))), + "ofType" → null)))), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "type", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "OBJECT", "name" → "__Type", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "isDeprecated", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "SCALAR", "name" → "Boolean", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "deprecationReason", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null)) - ), - "kind" → "OBJECT", - "possibleTypes" → null - ), - Map( + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null)), "inputFields" → null, + "interfaces" → Vector.empty, + "enumValues" → null, + "possibleTypes" → null), + Map( + "kind" → "OBJECT", "name" → "__InputValue", "description" → "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", - "interfaces" → Nil, - "enumValues" → null, "fields" → Vector( Map( "name" → "name", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "description", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null)), + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "type", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "OBJECT", "name" → "__Type", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "defaultValue", "description" → "A GraphQL-formatted string representing the default value for this input value.", - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null)) - ), - "kind" → "OBJECT", - "possibleTypes" → null - ), - Map( + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null)), "inputFields" → null, + "interfaces" → Vector.empty, + "enumValues" → null, + "possibleTypes" → null), + Map( + "kind" → "OBJECT", "name" → "__Schema", "description" → "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", - "interfaces" → Nil, - "enumValues" → null, "fields" → Vector( + Map( + "name" → "description", + "description" → null, + "args" → Vector.empty, + "type" → Map( + "kind" → "SCALAR", + "name" → "String", + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "types", "description" → "A list of all types supported by this server.", - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, @@ -481,46 +481,46 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → Map( "kind" → "OBJECT", "name" → "__Type", - "ofType" → null))))), + "ofType" → null)))), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "queryType", "description" → "The type that query operations will be rooted at.", - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "OBJECT", "name" → "__Type", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "mutationType", "description" → "If this server supports mutation, the type that mutation operations will be rooted at.", - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "OBJECT", "name" → "__Type", - "ofType" → null)), + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "subscriptionType", "description" → "If this server support subscription, the type that subscription operations will be rooted at.", - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "OBJECT", "name" → "__Type", - "ofType" → null)), + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "directives", "description" → "A list of all directives supported by this server.", - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, @@ -533,56 +533,54 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → Map( "kind" → "OBJECT", "name" → "__Directive", - "ofType" → null))))) - ), - "kind" → "OBJECT", - "possibleTypes" → null - ), - Map( + "ofType" → null)))), + "isDeprecated" → false, + "deprecationReason" → null)), "inputFields" → null, + "interfaces" → Vector.empty, + "enumValues" → null, + "possibleTypes" → null), + Map( + "kind" → "OBJECT", "name" → "__Type", "description" → "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", - "interfaces" → Nil, - "enumValues" → null, "fields" → Vector( Map( "name" → "kind", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "NON_NULL", "name" → null, "ofType" → Map( "kind" → "ENUM", "name" → "__TypeKind", - "ofType" → null))), + "ofType" → null)), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "name", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null)), + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "description", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "SCALAR", "name" → "String", - "ofType" → null)), + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "fields", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, "args" → Vector( Map( "name" → "includeDeprecated", @@ -601,13 +599,13 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → Map( "kind" → "OBJECT", "name" → "__Field", - "ofType" → null)))), + "ofType" → null))), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "interfaces", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "LIST", "name" → null, @@ -617,13 +615,13 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → Map( "kind" → "OBJECT", "name" → "__Type", - "ofType" → null)))), + "ofType" → null))), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "possibleTypes", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "LIST", "name" → null, @@ -633,12 +631,12 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → Map( "kind" → "OBJECT", "name" → "__Type", - "ofType" → null)))), + "ofType" → null))), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "enumValues", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, "args" → Vector( Map( "name" → "includeDeprecated", @@ -657,13 +655,13 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → Map( "kind" → "OBJECT", "name" → "__EnumValue", - "ofType" → null)))), + "ofType" → null))), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "inputFields", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "LIST", "name" → null, @@ -673,25 +671,29 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "ofType" → Map( "kind" → "OBJECT", "name" → "__InputValue", - "ofType" → null)))), + "ofType" → null))), + "isDeprecated" → false, + "deprecationReason" → null), Map( "name" → "ofType", "description" → null, - "isDeprecated" → false, - "deprecationReason" → null, - "args" → Nil, + "args" → Vector.empty, "type" → Map( "kind" → "OBJECT", "name" → "__Type", - "ofType" → null)) - ), - "kind" → "OBJECT", - "possibleTypes" → null - ), - Map( + "ofType" → null), + "isDeprecated" → false, + "deprecationReason" → null)), "inputFields" → null, + "interfaces" → Vector.empty, + "enumValues" → null, + "possibleTypes" → null), + Map( + "kind" → "ENUM", "name" → "__TypeKind", "description" → "An enum describing what kind of type a given `__Type` is.", + "fields" → null, + "inputFields" → null, "interfaces" → null, "enumValues" → Vector( Map( @@ -733,48 +735,43 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "name" → "NON_NULL", "description" → "Indicates this type is a non-null. `ofType` is a valid field.", "isDeprecated" → false, - "deprecationReason" → null) - ), - "fields" → null, - "kind" → "ENUM", + "deprecationReason" → null)), "possibleTypes" → null), Map( - "inputFields" → null, + "kind" → "SCALAR", "name" → "Boolean", "description" → "The `Boolean` scalar type represents `true` or `false`.", + "fields" → null, + "inputFields" → null, "interfaces" → null, "enumValues" → null, - "fields" → null, - "kind" → "SCALAR", "possibleTypes" → null), Map( - "inputFields" → null, + "kind" → "SCALAR", "name" → "Int", - "description" → ( - "The `Int` scalar type represents non-fractional signed whole numeric values. " + - "Int can represent values between -(2^31) and 2^31 - 1."), + "description" → "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.", + "fields" → null, + "inputFields" → null, "interfaces" → null, "enumValues" → null, - "fields" → null, - "kind" → "SCALAR", "possibleTypes" → null), Map( - "inputFields" → null, + "kind" → "SCALAR", "name" → "String", - "description" → ( - "The `String` scalar type represents textual data, represented as UTF-8 " + - "character sequences. The String type is most often used by GraphQL to " + - "represent free-form human-readable text."), + "description" → "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields" → null, + "inputFields" → null, "interfaces" → null, "enumValues" → null, - "fields" → null, - "kind" → "SCALAR", "possibleTypes" → null)), "directives" → Vector( Map( "name" → "include", "description" → "Directs the executor to include this field or fragment only when the `if` argument is true.", - "locations" → Vector("FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"), + "locations" → Vector( + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT"), "args" → Vector( Map( "name" → "if", @@ -790,7 +787,10 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport Map( "name" → "skip", "description" → "Directs the executor to skip this field or fragment when the `if` argument is true.", - "locations" → Vector("FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"), + "locations" → Vector( + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT"), "args" → Vector( Map( "name" → "if", @@ -806,13 +806,19 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport Map( "name" → "deprecated", "description" → "Marks an element of a GraphQL schema as no longer supported.", - "locations" → Vector("ENUM_VALUE", "FIELD_DEFINITION"), + "locations" → Vector( + "ENUM_VALUE", + "FIELD_DEFINITION"), "args" → Vector( Map( "name" → "reason", "description" → "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", - "type" → Map("kind" → "SCALAR", "name" → "String", "ofType" → null), - "defaultValue" → "\"No longer supported\"")))))))) + "type" → Map( + "kind" → "SCALAR", + "name" → "String", + "ofType" → null), + "defaultValue" → "\"No longer supported\"")))), + "description" → null)))) } "introspects on input object" in { @@ -1252,39 +1258,45 @@ class IntrospectionSpec extends WordSpec with Matchers with FutureResultSupport "data" → Map( "schemaType" → Map( "name" → "__Schema", - "description" → ( - "A GraphQL Schema defines the capabilities of a GraphQL " + - "server. It exposes all available types and directives on " + - "the server, as well as the entry points for query, mutation, and subscription operations."), - "fields" → List( + "description" → "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields" → Vector( + Map( + "name" → "description", + "description" → null), Map( "name" → "types", - "description" → "A list of all types supported by this server." - ), + "description" → "A list of all types supported by this server."), Map( "name" → "queryType", - "description" → "The type that query operations will be rooted at." - ), + "description" → "The type that query operations will be rooted at."), Map( "name" → "mutationType", - "description" → ( - "If this server supports mutation, the type that " + - "mutation operations will be rooted at.") - ), + "description" → "If this server supports mutation, the type that mutation operations will be rooted at."), Map( "name" → "subscriptionType", - "description" → ( - "If this server support subscription, the type that " + - "subscription operations will be rooted at.") - ), + "description" → "If this server support subscription, the type that subscription operations will be rooted at."), Map( "name" → "directives", - "description" → "A list of all directives supported by this server." - ) - ) - ) - ) - )) + "description" → "A list of all directives supported by this server.")))))) + } + + "exposes description on schema" in { + val schema = Schema(ObjectType("QueryRoot", fields[Unit, Unit](Field("foo", IntType, resolve = _ ⇒ 1))), + description = Some("test schema")) + + val Success(query) = QueryParser.parse( + """ + { + __schema { + description + } + } + """) + + Executor.execute(schema, query).await should be (Map( + "data" → Map( + "__schema" → Map( + "description" → "test schema")))) } "exposes descriptions on enums" in { diff --git a/src/test/scala/sangria/macros/LiteralMacroSpec.scala b/src/test/scala/sangria/macros/LiteralMacroSpec.scala index cd43b663..c055b306 100644 --- a/src/test/scala/sangria/macros/LiteralMacroSpec.scala +++ b/src/test/scala/sangria/macros/LiteralMacroSpec.scala @@ -759,6 +759,7 @@ class LiteralMacroSpec extends WordSpec with Matchers { OperationTypeDefinition(OperationType.Query, NamedType("QueryType", None), Vector.empty, None), OperationTypeDefinition(OperationType.Mutation, NamedType("MutationType", None), Vector.empty, None)), Vector.empty, + None, Vector( Comment(" Copyright (c) 2015, Facebook, Inc.", None), Comment(" All rights reserved.", None), diff --git a/src/test/scala/sangria/parser/SchemaParserSpec.scala b/src/test/scala/sangria/parser/SchemaParserSpec.scala index f01ef799..233a8244 100644 --- a/src/test/scala/sangria/parser/SchemaParserSpec.scala +++ b/src/test/scala/sangria/parser/SchemaParserSpec.scala @@ -9,7 +9,6 @@ import sangria.util.{DebugUtil, FileUtil, StringMatchers} import scala.util.Success class SchemaParserSpec 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) @@ -25,6 +24,7 @@ class SchemaParserSpec extends WordSpec with Matchers with StringMatchers { OperationTypeDefinition(OperationType.Query, NamedType("QueryType", Some(AstLocation(306, 9, 10))), Vector.empty, Some(AstLocation(299, 9, 3))), OperationTypeDefinition(OperationType.Mutation, NamedType("MutationType", Some(AstLocation(328, 10, 13))), Vector.empty, Some(AstLocation(318, 10, 3)))), Vector.empty, + None, Vector( Comment(" Copyright (c) 2015, Facebook, Inc.", Some(AstLocation(0, 1, 1))), Comment(" All rights reserved.", Some(AstLocation(37, 2, 1))), @@ -1113,6 +1113,39 @@ class SchemaParserSpec extends WordSpec with Matchers with StringMatchers { )) } + "Allow schema with description" in { + val Success(ast) = parseQuery( + """ + "the best schema ever" + schema @dir1 { + query: Query + } + """) + + ast should be ( + Document( + Vector( + SchemaDefinition( + Vector( + OperationTypeDefinition(OperationType.Query, NamedType("Query", Some(AstLocation("", 78, 4, 20))), Vector.empty, Some(AstLocation("", 71, 4, 13)))), + Vector( + Directive( + "dir1", + Vector.empty, + Vector.empty, + Some(AstLocation("", 51, 3, 18)) + )), + Some(StringValue("the best schema ever", false, None, Vector.empty, Some(AstLocation("", 11, 2, 11)))), + Vector.empty, + Vector.empty, + Some(AstLocation("", 44, 3, 11)) + )), + Vector.empty, + Some(AstLocation("", 11, 2, 11)), + None + )) + } + "Allow extensions on the schema" in { val Success(ast) = parseQuery( """ @@ -1144,8 +1177,7 @@ class SchemaParserSpec extends WordSpec with Matchers with StringMatchers { Some(AstLocation("", 11, 2, 11)) ), SchemaDefinition( - Vector( - OperationTypeDefinition(OperationType.Query, NamedType("Query", Some(AstLocation("", 76, 5, 20))), Vector.empty, Some(AstLocation("", 69, 5, 13)))), + Vector(OperationTypeDefinition(OperationType.Query, NamedType("Query", Some(AstLocation("", 76, 5, 20))), Vector.empty, Some(AstLocation("", 69, 5, 13)))), Vector( Directive( "dir1", @@ -1153,6 +1185,7 @@ class SchemaParserSpec extends WordSpec with Matchers with StringMatchers { Vector.empty, Some(AstLocation("", 49, 4, 18)) )), + None, Vector.empty, Vector.empty, Some(AstLocation("", 42, 4, 11)) diff --git a/src/test/scala/sangria/renderer/SchemaRenderSpec.scala b/src/test/scala/sangria/renderer/SchemaRenderSpec.scala index 0e7c39e6..069f8ebf 100644 --- a/src/test/scala/sangria/renderer/SchemaRenderSpec.scala +++ b/src/test/scala/sangria/renderer/SchemaRenderSpec.scala @@ -6,7 +6,7 @@ import sangria.execution.Executor import sangria.marshalling.InputUnmarshaller import sangria.schema._ import sangria.macros._ -import sangria.util.{FutureResultSupport, StringMatchers} +import sangria.util.{DebugUtil, FutureResultSupport, StringMatchers} import sangria.introspection.introspectionQuery import sangria.validation.IntCoercionViolation @@ -709,6 +709,8 @@ class SchemaRenderSpec extends WordSpec with Matchers with FutureResultSupport w | |"A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations." |type __Schema { + | description: String + | | "A list of all types supported by this server." | types: [__Type!]! | @@ -877,6 +879,8 @@ class SchemaRenderSpec extends WordSpec with Matchers with FutureResultSupport w | |# A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations. |type __Schema { + | description: String + | | # A list of all types supported by this server. | types: [__Type!]! | @@ -1109,6 +1113,27 @@ class SchemaRenderSpec extends WordSpec with Matchers with FutureResultSupport w |""".stripMargin) (after being strippedOfCarriageReturns) } + "render schema description" in { + val schema = + graphql""" + #comment1 + "test" + #comment 2 + schema @dir1 { + query: Query + } + """ + + cycleRender(schema) should equal (""" + |# comment1 + |"test" + |# comment 2 + |schema @dir1 { + | query: Query + |} + |""".stripMargin) (after being strippedOfCarriageReturns) + } + "render scalar types" in { val schema = graphql""" diff --git a/src/test/scala/sangria/schema/AstSchemaMaterializerSpec.scala b/src/test/scala/sangria/schema/AstSchemaMaterializerSpec.scala index 467c3056..6ff2739e 100644 --- a/src/test/scala/sangria/schema/AstSchemaMaterializerSpec.scala +++ b/src/test/scala/sangria/schema/AstSchemaMaterializerSpec.scala @@ -939,6 +939,14 @@ class AstSchemaMaterializerSpec extends WordSpec with Matchers with FutureResult "Use SDL descriptions" in { val schemaDef = s"""$quotes + |test schema + |descr + |$quotes + |schema { + | query: Query + |} + | + |$quotes |fooo bar |baz |$quotes @@ -969,6 +977,8 @@ class AstSchemaMaterializerSpec extends WordSpec with Matchers with FutureResult val schema = Schema.buildFromAst(QueryParser.parse(schemaDef)) + schema.description should be (Some("test schema\ndescr")) + val myEnum = schema.inputTypes("MyEnum").asInstanceOf[EnumType[_]] myEnum.description should be (Some("fooo bar\nbaz")) diff --git a/src/test/scala/sangria/schema/IntrospectionSchemaMaterializerSpec.scala b/src/test/scala/sangria/schema/IntrospectionSchemaMaterializerSpec.scala index 11678910..b499ee99 100644 --- a/src/test/scala/sangria/schema/IntrospectionSchemaMaterializerSpec.scala +++ b/src/test/scala/sangria/schema/IntrospectionSchemaMaterializerSpec.scala @@ -239,6 +239,10 @@ class IntrospectionSchemaMaterializerSpec extends WordSpec with Matchers with Fu EnumValue("MAUVE", Some("So sickening"), "MAUVE", deprecationReason = Some("No longer in fashion"))))), resolve = _ ⇒ None))))) + "builds a schema with description" in testSchema( + Schema(ObjectType("Simple", "This is a simple type", fields[Any, Any](Field("shinyString", OptionType(StringType), resolve = _ ⇒ None))), + description = Some("test"))) + "cannot use client schema for general execution" in { val clientSchema = testSchema( Schema(ObjectType("Query", fields[Any, Any]( diff --git a/src/test/scala/sangria/schema/SchemaComparatorSpec.scala b/src/test/scala/sangria/schema/SchemaComparatorSpec.scala index 2fd23436..97f7d0a4 100644 --- a/src/test/scala/sangria/schema/SchemaComparatorSpec.scala +++ b/src/test/scala/sangria/schema/SchemaComparatorSpec.scala @@ -513,6 +513,30 @@ class SchemaComparatorSpec extends WordSpec with Matchers { nonBreakingChange[SchemaAstDirectiveAdded]("Directive `@bar(ids:[1,2])` added on a schema"), nonBreakingChange[SchemaAstDirectiveRemoved]("Directive `@foo` removed from a schema")) + "detect changes in schema description" in checkChangesWithoutQueryType( + gql""" + type Query { + foo: String + } + + schema { + query: Query + } + """, + + gql""" + type Query { + foo: String + } + + "new description" + schema { + query: Query + } + """, + + nonBreakingChange[SchemaDescriptionChanged]("Schema description changed")) + "detect changes in type AST directives" in checkChangesWithoutQueryType( gql""" type Query implements Foo2 {