diff --git a/modules/core/src/main/scala/sangria/introspection/IntrospectionParser.scala b/modules/core/src/main/scala/sangria/introspection/IntrospectionParser.scala index c8412aed..12cc5ca9 100644 --- a/modules/core/src/main/scala/sangria/introspection/IntrospectionParser.scala +++ b/modules/core/src/main/scala/sangria/introspection/IntrospectionParser.scala @@ -24,7 +24,9 @@ object IntrospectionParser { name = mapStringField(value, "name", path), description = mapStringFieldOpt(value, "description"), tpe = parseTypeRef(mapField(value, "type", path), path :+ "type"), - defaultValue = mapStringFieldOpt(value, "defaultValue") + defaultValue = mapStringFieldOpt(value, "defaultValue"), + isDeprecated = mapBooleanFieldOpt(value, "isDeprecated", path), + deprecationReason = mapStringFieldOpt(value, "deprecationReason") ) private def parseField[In: InputUnmarshaller](field: In, path: Vector[String]) = diff --git a/modules/core/src/main/scala/sangria/introspection/model.scala b/modules/core/src/main/scala/sangria/introspection/model.scala index d67be661..5efc7db2 100644 --- a/modules/core/src/main/scala/sangria/introspection/model.scala +++ b/modules/core/src/main/scala/sangria/introspection/model.scala @@ -115,7 +115,10 @@ case class IntrospectionInputValue( name: String, description: Option[String], tpe: IntrospectionTypeRef, - defaultValue: Option[String]) + defaultValue: Option[String], + isDeprecated: Option[Boolean], + deprecationReason: Option[String] +) sealed trait IntrospectionTypeRef { def kind: TypeKind.Value diff --git a/modules/core/src/main/scala/sangria/introspection/package.scala b/modules/core/src/main/scala/sangria/introspection/package.scala index 9a19a738..fe9ec12d 100644 --- a/modules/core/src/main/scala/sangria/introspection/package.scala +++ b/modules/core/src/main/scala/sangria/introspection/package.scala @@ -168,7 +168,20 @@ package object introspection { List[Field[Unit, Field[_, _]]]( Field("name", StringType, resolve = _.value.name), Field("description", OptionType(StringType), resolve = _.value.description), - Field("args", ListType(__InputValue), resolve = _.value.arguments), + Field( + "args", + ListType(__InputValue), + arguments = includeDeprecated :: Nil, + resolve = ctx => { + val incDep = ctx.arg(includeDeprecated) + + if (incDep) { + ctx.value.arguments + } else { + ctx.value.arguments.filter(_.deprecationReason.isEmpty) + } + } + ), Field("type", __Type, resolve = false -> _.value.fieldType), Field("isDeprecated", BooleanType, resolve = _.value.deprecationReason.isDefined), Field("deprecationReason", OptionType(StringType), resolve = _.value.deprecationReason) @@ -309,10 +322,17 @@ package object introspection { Field( "inputFields", OptionType(ListType(__InputValue)), - resolve = _.value._2 match { - case io: InputObjectType[_] => Some(io.fields) - case _ => None - }), + arguments = includeDeprecated :: Nil, + resolve = ctx => { + val incDep = ctx.arg(includeDeprecated) + + ctx.value._2 match { + case io: InputObjectType[_] if incDep => Some(io.fields) + case io: InputObjectType[_] => Some(io.fields.filter(_.deprecationReason.isEmpty)) + case _ => None + } + } + ), Field( "ofType", OptionType(__Type), @@ -342,7 +362,9 @@ package object introspection { Some("A GraphQL-formatted string representing the default value for this input value."), resolve = ctx => ctx.value.defaultValue.flatMap(ctx.renderInputValueCompact(_, ctx.value.inputValueType)) - ) + ), + Field("isDeprecated", OptionType(BooleanType), resolve = _.value.deprecationReason.isDefined), + Field("deprecationReason", OptionType(StringType), resolve = _.value.deprecationReason) ) ) @@ -374,7 +396,20 @@ package object introspection { "locations", ListType(__DirectiveLocation), resolve = _.value.locations.toVector.sorted), - Field("args", ListType(__InputValue), resolve = _.value.arguments), + Field( + "args", + ListType(__InputValue), + arguments = includeDeprecated :: Nil, + resolve = ctx => { + val incDep = ctx.arg(includeDeprecated) + + if (incDep) { + ctx.value.arguments + } else { + ctx.value.arguments.filter(_.deprecationReason.isEmpty) + } + } + ), Field( "isRepeatable", BooleanType, @@ -457,12 +492,17 @@ package object introspection { def introspectionQuery( schemaDescription: Boolean = true, - directiveRepeatableFlag: Boolean = true): ast.Document = - QueryParser.parse(introspectionQueryString(schemaDescription, directiveRepeatableFlag)).get + directiveRepeatableFlag: Boolean = true, + inputValueDeprecation: Boolean = false): ast.Document = + QueryParser + .parse( + introspectionQueryString(schemaDescription, directiveRepeatableFlag, inputValueDeprecation)) + .get def introspectionQueryString( schemaDescription: Boolean = true, - directiveRepeatableFlag: Boolean = true): String = + directiveRepeatableFlag: Boolean = true, + inputValueDeprecation: Boolean = false): String = s"""query IntrospectionQuery { | __schema { | queryType { name } @@ -475,7 +515,7 @@ package object introspection { | name | description | locations - | args { + | args${if (inputValueDeprecation) "(includeDeprecated: true)" else ""} { | ...InputValue | } | ${if (directiveRepeatableFlag) "isRepeatable" else ""} @@ -490,7 +530,7 @@ package object introspection { | fields(includeDeprecated: true) { | name | description - | args { + | args${if (inputValueDeprecation) "(includeDeprecated: true)" else ""} { | ...InputValue | } | type { @@ -499,7 +539,7 @@ package object introspection { | isDeprecated | deprecationReason | } - | inputFields { + | inputFields${if (inputValueDeprecation) "(includeDeprecated: true)" else ""} { | ...InputValue | } | interfaces { @@ -520,6 +560,8 @@ package object introspection { | description | type { ...TypeRef } | defaultValue + ${if (inputValueDeprecation) """| isDeprecated + | deprecationReason""" else "|"} |} |fragment TypeRef on __Type { | kind diff --git a/modules/core/src/main/scala/sangria/renderer/SchemaRenderer.scala b/modules/core/src/main/scala/sangria/renderer/SchemaRenderer.scala index d17c16c6..fb827aaf 100644 --- a/modules/core/src/main/scala/sangria/renderer/SchemaRenderer.scala +++ b/modules/core/src/main/scala/sangria/renderer/SchemaRenderer.scala @@ -79,7 +79,9 @@ object SchemaRenderer { arg.name, renderTypeNameAst(arg.argumentType), arg.defaultValue.flatMap(renderDefault(_, arg.argumentType)), - arg.astDirectives, + withoutDeprecated(arg.astDirectives) ++ renderDeprecation( + arg.deprecationReason.isDefined, + arg.deprecationReason), renderDescription(arg.description) ) @@ -138,14 +140,18 @@ object SchemaRenderer { field.name, renderTypeName(field.tpe), renderDefault(field.defaultValue), - description = renderDescription(field.description)) + directives = renderDeprecation(field.isDeprecated.getOrElse(false), field.deprecationReason), + description = renderDescription(field.description) + ) def renderInputField(field: InputField[_]) = ast.InputValueDefinition( field.name, renderTypeNameAst(field.fieldType), field.defaultValue.flatMap(renderDefault(_, field.fieldType)), - field.astDirectives, + withoutDeprecated(field.astDirectives) ++ renderDeprecation( + field.deprecationReason.isDefined, + field.deprecationReason), renderDescription(field.description) ) diff --git a/modules/core/src/main/scala/sangria/schema/AstSchemaBuilder.scala b/modules/core/src/main/scala/sangria/schema/AstSchemaBuilder.scala index c4ed8947..a91fef51 100644 --- a/modules/core/src/main/scala/sangria/schema/AstSchemaBuilder.scala +++ b/modules/core/src/main/scala/sangria/schema/AstSchemaBuilder.scala @@ -691,6 +691,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] { fieldType = tpe, defaultValue = defaultValue, astDirectives = definition.directives, + deprecationReason = inputValueDeprecationReason(definition), astNodes = Vector(definition) )) @@ -719,7 +720,8 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] { defaultValue = defaultValue, fromInput = argumentFromInput(typeDefinition, fieldDefinition, definition), astDirectives = definition.directives, - astNodes = Vector(definition) + astNodes = Vector(definition), + deprecationReason = inputValueDeprecationReason(definition) )) def buildArgumentType( @@ -853,6 +855,9 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] { def enumValueDeprecationReason(definition: ast.EnumValueDefinition): Option[String] = deprecationReason(definition.directives.toList) + def inputValueDeprecationReason(definition: ast.InputValueDefinition): Option[String] = + deprecationReason(definition.directives.toList) + def fieldDeprecationReason(definition: ast.FieldDefinition): Option[String] = deprecationReason(definition.directives.toList) diff --git a/modules/core/src/main/scala/sangria/schema/IntrospectionSchemaBuilder.scala b/modules/core/src/main/scala/sangria/schema/IntrospectionSchemaBuilder.scala index 14df7f0d..71d999d7 100644 --- a/modules/core/src/main/scala/sangria/schema/IntrospectionSchemaBuilder.scala +++ b/modules/core/src/main/scala/sangria/schema/IntrospectionSchemaBuilder.scala @@ -260,6 +260,7 @@ class DefaultIntrospectionSchemaBuilder[Ctx] extends IntrospectionSchemaBuilder[ InputField( name = inputFieldName(definition), description = inputFieldDescription(definition), + deprecationReason = inputValueDeprecationReason(definition), fieldType = tpe, defaultValue = defaultValue, astDirectives = Vector.empty, @@ -280,7 +281,8 @@ class DefaultIntrospectionSchemaBuilder[Ctx] extends IntrospectionSchemaBuilder[ defaultValue = defaultValue, fromInput = argumentFromInput(fieldDefinition, definition), astDirectives = Vector.empty, - astNodes = Vector.empty + astNodes = Vector.empty, + deprecationReason = inputValueDeprecationReason(definition) )) def buildDirective( @@ -377,6 +379,11 @@ class DefaultIntrospectionSchemaBuilder[Ctx] extends IntrospectionSchemaBuilder[ def enumValue(definition: IntrospectionEnumValue): String = definition.name + def inputValueDeprecationReason(definition: IntrospectionInputValue): Option[String] = + definition.deprecationReason.orElse( + if (definition.isDeprecated.getOrElse(false)) Some(DefaultDeprecationReason) else None + ) + def fieldDeprecationReason(definition: IntrospectionField): Option[String] = definition.deprecationReason.orElse( if (definition.isDeprecated) Some(DefaultDeprecationReason) else None) diff --git a/modules/core/src/main/scala/sangria/schema/Schema.scala b/modules/core/src/main/scala/sangria/schema/Schema.scala index 38e234ce..093c1f0a 100644 --- a/modules/core/src/main/scala/sangria/schema/Schema.scala +++ b/modules/core/src/main/scala/sangria/schema/Schema.scala @@ -708,6 +708,7 @@ trait InputValue[T] { def inputValueType: InputType[_] def description: Option[String] def defaultValue: Option[(_, ToInput[_, _])] + def deprecationReason: Option[String] } /** @param description @@ -719,11 +720,13 @@ case class Argument[T]( description: Option[String], defaultValue: Option[(_, ToInput[_, _])], fromInput: FromInput[_], + deprecationReason: Option[String], astDirectives: Vector[ast.Directive], astNodes: Vector[ast.AstNode]) extends InputValue[T] with Named - with HasAstInfo { + with HasAstInfo + with HasDeprecation { def withDirective(directive: ast.Directive): Argument[T] = copy(astDirectives = astDirectives :+ directive) @@ -731,6 +734,9 @@ case class Argument[T]( def withDirectives(directives: ast.Directive*): Argument[T] = copy(astDirectives = astDirectives ++ directives) + def withDeprecationReason(deprecationReason: String): Argument[T] = + copy(deprecationReason = Some(deprecationReason)) + override def inputValueType: InputType[_] = argumentType override def rename(newName: String): this.type = copy(name = newName).asInstanceOf[this.type] def toAst: ast.InputValueDefinition = SchemaRenderer.renderArg(this) @@ -751,6 +757,7 @@ object Argument { Some(description), Some(defaultValue -> toInput), fromInput, + None, Vector.empty, Vector.empty) @@ -764,18 +771,29 @@ object Argument { None, Some(defaultValue -> toInput), fromInput, + None, Vector.empty, Vector.empty) - def apply[T](name: String, argumentType: InputType[T], description: String)(implicit - fromInput: FromInput[T], - res: WithoutInputTypeTags[T]): Argument[res.Res] = - Argument(name, argumentType, Some(description), None, fromInput, Vector.empty, Vector.empty) + def apply[T]( + name: String, + argumentType: InputType[T], + description: String + )(implicit fromInput: FromInput[T], res: WithoutInputTypeTags[T]): Argument[res.Res] = + Argument( + name, + argumentType, + Some(description), + None, + fromInput, + None, + Vector.empty, + Vector.empty) def apply[T](name: String, argumentType: InputType[T])(implicit fromInput: FromInput[T], res: WithoutInputTypeTags[T]): Argument[res.Res] = - Argument(name, argumentType, None, None, fromInput, Vector.empty, Vector.empty) + Argument(name, argumentType, None, None, fromInput, None, Vector.empty, Vector.empty) def createWithoutDefault[T]( name: String, @@ -783,7 +801,7 @@ object Argument { description: Option[String])(implicit fromInput: FromInput[T], res: ArgumentType[T]): Argument[res.Res] = - Argument(name, argumentType, description, None, fromInput, Vector.empty, Vector.empty) + Argument(name, argumentType, description, None, fromInput, None, Vector.empty, Vector.empty) def createWithDefault[T, Default]( name: String, @@ -799,6 +817,7 @@ object Argument { description, Some(defaultValue -> toInput), fromInput, + None, Vector.empty, Vector.empty) } @@ -1102,15 +1121,19 @@ case class InputField[T]( fieldType: InputType[T], description: Option[String], defaultValue: Option[(_, ToInput[_, _])], + deprecationReason: Option[String], astDirectives: Vector[ast.Directive], astNodes: Vector[ast.AstNode] ) extends InputValue[T] with Named - with HasAstInfo { + with HasAstInfo + with HasDeprecation { def withDirective(directive: ast.Directive): InputField[T] = copy(astDirectives = astDirectives :+ directive) def withDirectives(directives: ast.Directive*): InputField[T] = copy(astDirectives = astDirectives ++ directives) + def withDeprecationReason(deprecationReason: String): InputField[T] = + copy(deprecationReason = Some(deprecationReason)) def inputValueType: InputType[T] = fieldType def rename(newName: String): InputField.this.type = copy(name = newName).asInstanceOf[this.type] def toAst: ast.InputValueDefinition = SchemaRenderer.renderInputField(this) @@ -1129,23 +1152,31 @@ object InputField { fieldType, Some(description), Some(defaultValue -> toInput), + None, Vector.empty, Vector.empty).asInstanceOf[InputField[res.Res]] def apply[T, Default](name: String, fieldType: InputType[T], defaultValue: Default)(implicit toInput: ToInput[Default, _], res: WithoutInputTypeTags[T]): InputField[res.Res] = - InputField(name, fieldType, None, Some(defaultValue -> toInput), Vector.empty, Vector.empty) + InputField( + name, + fieldType, + None, + Some(defaultValue -> toInput), + None, + Vector.empty, + Vector.empty) .asInstanceOf[InputField[res.Res]] def apply[T](name: String, fieldType: InputType[T], description: String)(implicit res: WithoutInputTypeTags[T]): InputField[res.Res] = - InputField(name, fieldType, Some(description), None, Vector.empty, Vector.empty) + InputField(name, fieldType, Some(description), None, None, Vector.empty, Vector.empty) .asInstanceOf[InputField[res.Res]] def apply[T](name: String, fieldType: InputType[T])(implicit res: WithoutInputTypeTags[T]): InputField[res.Res] = - InputField(name, fieldType, None, None, Vector.empty, Vector.empty) + InputField(name, fieldType, None, None, None, Vector.empty, Vector.empty) .asInstanceOf[InputField[res.Res]] def createFromMacroWithDefault[T, Default]( @@ -1159,6 +1190,7 @@ object InputField { fieldType, description, Some(defaultValue -> toInput), + None, Vector.empty, Vector.empty).asInstanceOf[InputField[res.Res]] @@ -1166,7 +1198,7 @@ object InputField { name: String, fieldType: InputType[T], description: Option[String])(implicit res: WithoutInputTypeTags[T]): InputField[res.Res] = - InputField(name, fieldType, description, None, Vector.empty, Vector.empty) + InputField(name, fieldType, description, None, None, Vector.empty, Vector.empty) .asInstanceOf[InputField[res.Res]] } diff --git a/modules/core/src/main/scala/sangria/schema/SchemaComparator.scala b/modules/core/src/main/scala/sangria/schema/SchemaComparator.scala index e646ab45..d356ee68 100644 --- a/modules/core/src/main/scala/sangria/schema/SchemaComparator.scala +++ b/modules/core/src/main/scala/sangria/schema/SchemaComparator.scala @@ -82,6 +82,7 @@ object SchemaComparator { added = SchemaChange.DirectiveArgumentAdded(newDir, _, _), removed = SchemaChange.DirectiveArgumentRemoved(oldDir, _), description = SchemaChange.DirectiveArgumentDescriptionChanged(newDir, _, _, _), + deprecated = SchemaChange.DirectiveArgumentDeprecated(newDir, _, _, _), default = SchemaChange.DirectiveArgumentDefaultChanged(newDir, _, _, _), typeChange = SchemaChange.DirectiveArgumentTypeChanged(newDir, _, _, _, _), dirAdded = SchemaChange.DirectiveArgumentAstDirectiveAdded(newDir, _, _), @@ -269,6 +270,10 @@ object SchemaComparator { oldField, newField, SchemaChange.InputFieldDescriptionChanged(newType, newField, _, _)) ++ + findDeprecationChanged( + oldField, + newField, + SchemaChange.InputFieldDeprecated(newType, newField, _, _)) ++ findInInputFields(oldType, newType, oldField, newField) } @@ -397,6 +402,7 @@ object SchemaComparator { added = SchemaChange.ObjectTypeArgumentAdded(newType, newField, _, _), removed = SchemaChange.ObjectTypeArgumentRemoved(oldType, oldField, _), description = SchemaChange.ObjectTypeArgumentDescriptionChanged(newType, newField, _, _, _), + deprecated = SchemaChange.ObjectTypeArgumentDeprecated(newType, newField, _, _, _), default = SchemaChange.ObjectTypeArgumentDefaultChanged(newType, newField, _, _, _), typeChange = SchemaChange.ObjectTypeArgumentTypeChanged(newType, newField, _, _, _, _), dirAdded = SchemaChange.FieldArgumentAstDirectiveAdded(newType, newField, _, _), @@ -412,6 +418,7 @@ object SchemaComparator { added: (Argument[_], Boolean) => SchemaChange, removed: Argument[_] => SchemaChange, description: (Argument[_], Option[String], Option[String]) => SchemaChange, + deprecated: (Argument[_], Option[String], Option[String]) => SchemaChange, default: (Argument[_], Option[ast.Value], Option[ast.Value]) => SchemaChange, typeChange: (Argument[_], Boolean, InputType[_], InputType[_]) => SchemaChange, dirAdded: (Argument[_], ast.Directive) => SchemaChange, @@ -433,6 +440,7 @@ object SchemaComparator { val newArg = newArgs.find(_.name == name).get findDescriptionChanged(oldArg, newArg, description(newArg, _, _)) ++ + findDeprecationChanged(oldArg, newArg, deprecated(newArg, _, _)) ++ findInArg( oldArg, newArg, @@ -751,6 +759,14 @@ object SchemaChange { with DescriptionChange with TypeChange + case class InputFieldDeprecated( + tpe: InputObjectType[_], + field: InputField[_], + oldDeprecationReason: Option[String], + newDeprecationReason: Option[String] + ) extends AbstractChange(s"`${tpe.name}.${field.name}` was deprecated", false) + with DeprecationChange + case class DirectiveDescriptionChanged( directive: Directive, oldDescription: Option[String], @@ -778,6 +794,17 @@ object SchemaChange { with DescriptionChange with TypeChange + case class ObjectTypeArgumentDeprecated( + tpe: ObjectLikeType[_, _], + field: Field[_, _], + argument: Argument[_], + oldDeprecationReason: Option[String], + newDeprecationReason: Option[String]) + extends AbstractChange( + s"Argument `${argument.name}` on `${tpe.name}.${field.name}` was deprecated", + false) + with DeprecationChange + case class DirectiveArgumentDescriptionChanged( directive: Directive, argument: Argument[_], @@ -786,6 +813,16 @@ object SchemaChange { extends AbstractChange(s"`${directive.name}(${argument.name})` description is changed", false) with DescriptionChange + case class DirectiveArgumentDeprecated( + directive: Directive, + argument: Argument[_], + oldDescription: Option[String], + newDescription: Option[String]) + extends AbstractChange( + s"Directive argument `${directive.name}.${argument.name}` was deprecated", + false) + with DescriptionChange + case class FieldDeprecationChanged( tpe: ObjectLikeType[_, _], field: Field[_, _], diff --git a/modules/core/src/main/scala/sangria/schema/package.scala b/modules/core/src/main/scala/sangria/schema/package.scala index e6f390d2..c53edd74 100644 --- a/modules/core/src/main/scala/sangria/schema/package.scala +++ b/modules/core/src/main/scala/sangria/schema/package.scala @@ -258,7 +258,12 @@ package object schema { "deprecated", description = Some("Marks an element of a GraphQL schema as no longer supported."), arguments = ReasonArg :: Nil, - locations = Set(DirectiveLocation.FieldDefinition, DirectiveLocation.EnumValue), + locations = Set( + DirectiveLocation.FieldDefinition, + DirectiveLocation.EnumValue, + DirectiveLocation.ArgumentDefinition, + DirectiveLocation.InputFieldDefinition + ), shouldInclude = ctx => !ctx.arg(IfArg) ) diff --git a/modules/core/src/main/scala/sangria/validation/SchemaBasedDocumentAnalyzer.scala b/modules/core/src/main/scala/sangria/validation/SchemaBasedDocumentAnalyzer.scala index 0cc946ac..111db0a9 100644 --- a/modules/core/src/main/scala/sangria/validation/SchemaBasedDocumentAnalyzer.scala +++ b/modules/core/src/main/scala/sangria/validation/SchemaBasedDocumentAnalyzer.scala @@ -69,10 +69,43 @@ case class SchemaBasedDocumentAnalyzer(schema: Schema[_, _], document: ast.Docum if (value.deprecationReason.isDefined && !deprecated.contains(key)) deprecated(key) = DeprecatedEnumValue(parent, value, enumValue, value.deprecationReason.get) + case _ => // do nothing + } + + case _: ast.Argument + if typeInfo.argument.isDefined && typeInfo.argument.get.deprecationReason.isDefined => + val arg = typeInfo.argument.get + val deprecationReason = arg.deprecationReason.get + + (typeInfo.directive, typeInfo.previousParentType, typeInfo.fieldDef) match { + case (Some(directive), _, _) => + val key = directive.name + "." + arg.name + + if (!deprecated.contains(key)) + deprecated(key) = DeprecatedDirectiveArgument(directive, arg, deprecationReason) + + case (_, Some(parent), Some(field)) => + val key = parent.name + "." + field.name + "." + arg.name + if (!deprecated.contains(key)) + deprecated(key) = DeprecatedFieldArgument(parent, field, arg, deprecationReason) + + case _ => // do nothing + } + + case _: ast.ObjectField + if typeInfo.inputField.isDefined && typeInfo.inputField.get.deprecationReason.isDefined => + val field = typeInfo.inputField.get + typeInfo.argument.map(_.argumentType.namedInputType) match { + case Some(parent: InputObjectType[_]) => + val key = parent.name + "." + field.name + if (!deprecated.contains(key)) + deprecated(key) = + DeprecatedInputField(parent, field, field.deprecationReason.get) case _ => // do nothing } + case _ => // do nothing } } .values @@ -123,6 +156,34 @@ object SchemaBasedDocumentAnalyzer { s"The field '${parentType.name}.${field.name}' is deprecated. $deprecationReason" } + case class DeprecatedInputField( + parentType: InputObjectType[_], + field: InputField[_], + deprecationReason: String + ) extends DeprecatedUsage { + def errorMessage = + s"The input field '${parentType.name}.${field.name}' is deprecated. $deprecationReason" + } + + case class DeprecatedFieldArgument( + parentType: CompositeType[_], + field: Field[_, _], + argument: Argument[_], + deprecationReason: String + ) extends DeprecatedUsage { + def errorMessage = + s"The argument '${argument.name}' on '${parentType.name}.${field.name}' is deprecated. $deprecationReason" + } + + case class DeprecatedDirectiveArgument( + directive: Directive, + argument: Argument[_], + deprecationReason: String + ) extends DeprecatedUsage { + def errorMessage = + s"The argument '${argument.name}' on directive '${directive.name}' is deprecated. $deprecationReason" + } + case class DeprecatedEnumValue( parentType: EnumType[_], value: EnumValue[_], diff --git a/modules/core/src/main/scala/sangria/validation/TypeInfo.scala b/modules/core/src/main/scala/sangria/validation/TypeInfo.scala index e155c6b7..2a2a0ab3 100644 --- a/modules/core/src/main/scala/sangria/validation/TypeInfo.scala +++ b/modules/core/src/main/scala/sangria/validation/TypeInfo.scala @@ -21,6 +21,7 @@ class TypeInfo(schema: Schema[_, _], initialType: Option[Type] = None) { var directive: Option[Directive] = None var enumValue: Option[EnumValue[_]] = None var argument: Option[Argument[_]] = None + var inputField: Option[InputField[_]] = None def tpe = typeStack.headOption.flatten def previousParentType = parentTypeStack.headOption(1).flatten @@ -132,21 +133,18 @@ class TypeInfo(schema: Schema[_, _], initialType: Option[Type] = None) { } case ast.ObjectField(name, value, _, _) => - val (fieldType, defaultValue) = inputType match { + inputField = inputType match { case Some(it) if it.namedType.isInstanceOf[InputObjectType[_]] => it.namedType match { - case obj: InputObjectType[_] => - val field = obj.fieldsByName.get(name) - - field.map(_.inputValueType) -> field.flatMap(_.defaultValue) - case _ => None -> None + case obj: InputObjectType[_] => obj.fieldsByName.get(name) + case _ => None } - case _ => None -> None + case _ => None } - defaultValueStack.push(defaultValue) - inputTypeStack.push(fieldType) + defaultValueStack.push(inputField.flatMap(_.defaultValue)) + inputTypeStack.push(inputField.map(_.fieldType)) case _ => // ignore } diff --git a/modules/core/src/test/scala/sangria/introspection/IntrospectionSpec.scala b/modules/core/src/test/scala/sangria/introspection/IntrospectionSpec.scala index 313192bc..1ca27ae9 100644 --- a/modules/core/src/test/scala/sangria/introspection/IntrospectionSpec.scala +++ b/modules/core/src/test/scala/sangria/introspection/IntrospectionSpec.scala @@ -91,7 +91,12 @@ class IntrospectionSpec extends AnyWordSpec with Matchers with FutureResultSuppo Map( "name" -> "args", "description" -> null, - "args" -> Vector.empty, + "args" -> Vector(Map( + "name" -> "includeDeprecated", + "description" -> null, + "type" -> Map("kind" -> "SCALAR", "name" -> "Boolean", "ofType" -> null), + "defaultValue" -> "false" + )), "type" -> Map( "kind" -> "NON_NULL", "name" -> null, @@ -309,7 +314,12 @@ class IntrospectionSpec extends AnyWordSpec with Matchers with FutureResultSuppo Map( "name" -> "args", "description" -> null, - "args" -> Vector.empty, + "args" -> Vector(Map( + "name" -> "includeDeprecated", + "description" -> null, + "type" -> Map("kind" -> "SCALAR", "name" -> "Boolean", "ofType" -> null), + "defaultValue" -> "false" + )), "type" -> Map( "kind" -> "NON_NULL", "name" -> null, @@ -405,6 +415,22 @@ class IntrospectionSpec extends AnyWordSpec with Matchers with FutureResultSuppo "type" -> Map("kind" -> "SCALAR", "name" -> "String", "ofType" -> null), "isDeprecated" -> false, "deprecationReason" -> null + ), + Map( + "name" -> "isDeprecated", + "description" -> null, + "args" -> Vector.empty, + "type" -> Map("kind" -> "SCALAR", "name" -> "Boolean", "ofType" -> null), + "isDeprecated" -> false, + "deprecationReason" -> null + ), + Map( + "name" -> "deprecationReason", + "description" -> null, + "args" -> Vector.empty, + "type" -> Map("kind" -> "SCALAR", "name" -> "String", "ofType" -> null), + "isDeprecated" -> false, + "deprecationReason" -> null ) ), "inputFields" -> null, @@ -599,7 +625,12 @@ class IntrospectionSpec extends AnyWordSpec with Matchers with FutureResultSuppo Map( "name" -> "inputFields", "description" -> null, - "args" -> Vector.empty, + "args" -> Vector(Map( + "name" -> "includeDeprecated", + "description" -> null, + "type" -> Map("kind" -> "SCALAR", "name" -> "Boolean", "ofType" -> null), + "defaultValue" -> "false" + )), "type" -> Map( "kind" -> "LIST", "name" -> null, @@ -746,7 +777,11 @@ class IntrospectionSpec extends AnyWordSpec with Matchers with FutureResultSuppo Map( "name" -> "deprecated", "description" -> "Marks an element of a GraphQL schema as no longer supported.", - "locations" -> Vector("ENUM_VALUE", "FIELD_DEFINITION"), + "locations" -> Vector( + "ARGUMENT_DEFINITION", + "ENUM_VALUE", + "FIELD_DEFINITION", + "INPUT_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/).", diff --git a/modules/core/src/test/scala/sangria/renderer/SchemaRenderSpec.scala b/modules/core/src/test/scala/sangria/renderer/SchemaRenderSpec.scala index 43f3b310..90c16ff4 100644 --- a/modules/core/src/test/scala/sangria/renderer/SchemaRenderSpec.scala +++ b/modules/core/src/test/scala/sangria/renderer/SchemaRenderSpec.scala @@ -883,7 +883,7 @@ class SchemaRenderSpec | name: String! | description: String | locations: [__DirectiveLocation!]! - | args: [__InputValue!]! + | args(includeDeprecated: Boolean = false): [__InputValue!]! | | "Permits using the directive multiple times at the same location." | isRepeatable: Boolean! @@ -961,7 +961,7 @@ class SchemaRenderSpec |type __Field { | name: String! | description: String - | args: [__InputValue!]! + | args(includeDeprecated: Boolean = false): [__InputValue!]! | type: __Type! | isDeprecated: Boolean! | deprecationReason: String @@ -975,6 +975,8 @@ class SchemaRenderSpec | | "A GraphQL-formatted string representing the default value for this input value." | defaultValue: String + | isDeprecated: Boolean + | deprecationReason: String |} | |"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." @@ -1010,7 +1012,7 @@ class SchemaRenderSpec | interfaces: [__Type!] | possibleTypes: [__Type!] | enumValues(includeDeprecated: Boolean = false): [__EnumValue!] - | inputFields: [__InputValue!] + | inputFields(includeDeprecated: Boolean = false): [__InputValue!] | ofType: __Type |} | diff --git a/modules/core/src/test/scala/sangria/schema/IntrospectionSchemaMaterializerSpec.scala b/modules/core/src/test/scala/sangria/schema/IntrospectionSchemaMaterializerSpec.scala index d100a6b8..581ec5fe 100644 --- a/modules/core/src/test/scala/sangria/schema/IntrospectionSchemaMaterializerSpec.scala +++ b/modules/core/src/test/scala/sangria/schema/IntrospectionSchemaMaterializerSpec.scala @@ -19,14 +19,17 @@ import spray.json._ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec +import sangria.util.StringMatchers import sangria.util.tag.@@ import sangria.marshalling.FromInput.CoercedScalaResult import sangria.marshalling.ScalaInput +import sangria.schema.IntType class IntrospectionSchemaMaterializerSpec extends AnyWordSpec with Matchers - with FutureResultSupport { + with FutureResultSupport + with StringMatchers { // Test property: // Given a server's schema, a client may query that server with introspection, @@ -37,9 +40,11 @@ class IntrospectionSchemaMaterializerSpec def testSchema(serverSchema: Schema[Any, Any]): Schema[Any, Any] = { import sangria.marshalling.queryAst._ - val initialIntrospection = Executor.execute(serverSchema, introspectionQuery).await + val initialIntrospection = + Executor.execute(serverSchema, introspectionQuery(true, true, true)).await val clientSchema = IntrospectionSchemaMaterializer.buildSchema(initialIntrospection) - val secondIntrospection = Executor.execute(clientSchema, introspectionQuery).await + val secondIntrospection = + Executor.execute(clientSchema, introspectionQuery(true, true, true)).await initialIntrospection should be(secondIntrospection) @@ -409,41 +414,119 @@ class IntrospectionSchemaMaterializerSpec ) )) - "builds a schema aware of deprecation" in testSchema( - Schema(ObjectType( - "Simple", - "This is a simple type", - fields[Any, Any]( - Field( - "shinyString", - OptionType(StringType), - Some("This is a shiny string field"), - resolve = _ => None), - Field( - "deprecatedString", - OptionType(StringType), - Some("This is a deprecated string field"), - deprecationReason = Some("Use shinyString"), - resolve = _ => None), - Field( - "color", - fieldType = OptionType(EnumType( - "Color", - values = List( - EnumValue("RED", Some("So rosy"), "RED"), - EnumValue("GREEN", Some("So grassy"), "GREEN"), - EnumValue("BLUE", Some("So calming"), "BLUE"), - EnumValue( - "MAUVE", - Some("So sickening"), - "MAUVE", - deprecationReason = Some("No longer in fashion")) + "builds a schema aware of deprecation" in { + import sangria.marshalling.sprayJson._ + + val serverSchema = testSchema( + Schema( + ObjectType( + "Simple", + "This is a simple type", + fields[Any, Any]( + Field( + "shinyString", + OptionType(StringType), + Some("This is a shiny string field"), + resolve = _ => None), + Field( + "deprecatedString", + OptionType(StringType), + Some("This is a deprecated string field"), + deprecationReason = Some("Use shinyString"), + resolve = _ => None), + Field( + "color", + fieldType = OptionType(EnumType( + "Color", + values = List( + EnumValue("RED", Some("So rosy"), "RED"), + EnumValue("GREEN", Some("So grassy"), "GREEN"), + EnumValue("BLUE", Some("So calming"), "BLUE"), + EnumValue( + "MAUVE", + Some("So sickening"), + "MAUVE", + deprecationReason = Some("No longer in fashion")) + ) + )), + resolve = _ => None + ), + Field( + "foo", + fieldType = OptionType(StringType), + Some("This is a field with some deprecated args"), + resolve = _ => None, + arguments = List( + Argument( + "arg1", + IntType + ).withDeprecationReason("use arg2"), + Argument("arg2", StringType), + Argument( + "additionalInput", + InputObjectType( + "SimpleInput", + List( + InputField("before", StringType).withDeprecationReason("use after"), + InputField("after", IntType) + )) + ) + ) ) - )), - resolve = _ => None + ) + ), + directives = BuiltinDirectives :+ Directive( + "customDirective", + arguments = List( + Argument("deprecated", OptionInputType(IntType)).withDeprecationReason( + "use notDeprecated"), + Argument("notDeprecated", OptionInputType(StringType)) + ), + locations = + Set(DirectiveLocation.ArgumentDefinition, DirectiveLocation.InputFieldDefinition) ) ) - ))) + ) + + withClue(serverSchema.renderPretty)(serverSchema.renderPretty.normalizeAllWS should be(""" + schema { + query: Simple + } + + enum Color { + "So rosy" + RED + + "So grassy" + GREEN + + "So calming" + BLUE + + "So sickening" + MAUVE @deprecated(reason: "No longer in fashion") + } + + "This is a simple type" + type Simple { + "This is a shiny string field" + shinyString: String + + "This is a deprecated string field" + deprecatedString: String @deprecated(reason: "Use shinyString") + color: Color + + "This is a field with some deprecated args" + foo(arg1: Int! @deprecated(reason: "use arg2"), arg2: String!, additionalInput: SimpleInput!): String + } + + input SimpleInput { + before: String! @deprecated(reason: "use after") + after: Int! + } + + directive @customDirective(deprecated: Int @deprecated(reason: "use notDeprecated"), notDeprecated: String) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION """.normalizeAllWS)) + } "builds a schema with description" in testSchema( Schema( diff --git a/modules/core/src/test/scala/sangria/schema/SchemaComparatorSpec.scala b/modules/core/src/test/scala/sangria/schema/SchemaComparatorSpec.scala index 85396af8..09c7a92c 100644 --- a/modules/core/src/test/scala/sangria/schema/SchemaComparatorSpec.scala +++ b/modules/core/src/test/scala/sangria/schema/SchemaComparatorSpec.scala @@ -193,6 +193,7 @@ class SchemaComparatorSpec extends AnyWordSpec with Matchers { input Filter { name: String! descr: String + foo: String } """, graphql""" @@ -203,13 +204,18 @@ class SchemaComparatorSpec extends AnyWordSpec with Matchers { "filter by size" size: Int + + foo: String @deprecated } """, nonBreakingChange[TypeAdded]("`Int` type was added"), breakingChange[InputFieldRemoved]("Input field `descr` was removed from `Filter` type"), nonBreakingChange[InputFieldAdded]("Input field `size` was added to `Filter` type"), nonBreakingChange[InputFieldDescriptionChanged]("`Filter.name` description is changed"), - nonBreakingChange[TypeDescriptionChanged]("`Filter` type description is changed") + nonBreakingChange[TypeDescriptionChanged]("`Filter` type description is changed"), + nonBreakingChange[InputFieldDeprecated]("`Filter.foo` was deprecated"), + nonBreakingChange[InputFieldAstDirectiveAdded]( + "Directive `@deprecated` added on an input field `Filter.foo`") ) "detect changes in object like type fields and interfaces when they are added or removed" in checkChanges( @@ -267,6 +273,7 @@ class SchemaComparatorSpec extends AnyWordSpec with Matchers { b: String b1: String c: [String] + c1: String ): String! } """, @@ -278,6 +285,7 @@ class SchemaComparatorSpec extends AnyWordSpec with Matchers { b: [String] b1: String! c: [String]! + c1: String @deprecated d: Int e: Int! ): String! @@ -297,7 +305,11 @@ class SchemaComparatorSpec extends AnyWordSpec with Matchers { nonBreakingChange[ObjectTypeArgumentDefaultChanged]( "`Filter.foo(a)` default value changed from none to `\"foo\"`"), nonBreakingChange[ObjectTypeArgumentDescriptionChanged]( - "`Filter.foo(a)` description is changed") + "`Filter.foo(a)` description is changed"), + nonBreakingChange[ObjectTypeArgumentDeprecated]( + "Argument `c1` on `Filter.foo` was deprecated"), + nonBreakingChange[FieldArgumentAstDirectiveAdded]( + "Directive `@deprecated` added on a field argument `Filter.foo[c1]`") ) "detect changes in input type fields default value changes" in checkChanges( @@ -466,14 +478,14 @@ class SchemaComparatorSpec extends AnyWordSpec with Matchers { foo(bar: String @foo): String } - directive @test(bar: String @hello) on FIELD + directive @test(bar: String @hello foo: String) on FIELD """, gql""" type Query { foo(bar: String @bar(ids: [1, 2])): String } - directive @test(bar: String @world) on FIELD + directive @test(bar: String @world foo: String @deprecated) on FIELD """, nonBreakingChange[FieldArgumentAstDirectiveAdded]( "Directive `@bar(ids:[1,2])` added on a field argument `Query.foo[bar]`"), @@ -482,7 +494,10 @@ class SchemaComparatorSpec extends AnyWordSpec with Matchers { nonBreakingChange[DirectiveArgumentAstDirectiveRemoved]( "Directive `@hello` removed from a directive argument `test.bar`"), nonBreakingChange[DirectiveArgumentAstDirectiveAdded]( - "Directive `@world` added on a directive argument `test.bar`") + "Directive `@world` added on a directive argument `test.bar`"), + nonBreakingChange[DirectiveArgumentAstDirectiveAdded]( + "Directive `@deprecated` added on a directive argument `test.foo`"), + nonBreakingChange[DirectiveArgumentDeprecated]("Directive argument `test.foo` was deprecated") ) "detect changes in input field AST directives" in checkChangesWithoutQueryType( diff --git a/modules/core/src/test/scala/sangria/util/StringMatchers.scala b/modules/core/src/test/scala/sangria/util/StringMatchers.scala index d5e7c2a0..8810a8d0 100644 --- a/modules/core/src/test/scala/sangria/util/StringMatchers.scala +++ b/modules/core/src/test/scala/sangria/util/StringMatchers.scala @@ -9,7 +9,13 @@ trait StringMatchers { def stripCarriageReturns(s: String) = s.replaceAll("\\r", "") + val whitespace = "\\s+".r + + def normalizeAllWhitespace(str: String): String = + whitespace.replaceAllIn(str, " ").trim + implicit class StringHelpers(s: String) { def stripCR: String = stripCarriageReturns(s) + def normalizeAllWS: String = normalizeAllWhitespace(s) } } diff --git a/modules/core/src/test/scala/sangria/validation/DocumentAnalyzerSpec.scala b/modules/core/src/test/scala/sangria/validation/DocumentAnalyzerSpec.scala index 3c2b2622..e05c9b7b 100644 --- a/modules/core/src/test/scala/sangria/validation/DocumentAnalyzerSpec.scala +++ b/modules/core/src/test/scala/sangria/validation/DocumentAnalyzerSpec.scala @@ -1,5 +1,6 @@ package sangria.validation +import sangria.ast import sangria.schema._ import sangria.macros._ import sangria.util.StringMatchers @@ -11,12 +12,25 @@ import sangria.util.tag.@@ // Scala 3 issue workaround import sangria.marshalling.FromInput.CoercedScalaResult class DocumentAnalyzerSpec extends AnyWordSpec with Matchers with StringMatchers { + def makeDirective(name: String): (Directive, ast.Directive) = ( + Directive( + name, + arguments = Argument[Option[Int @@ CoercedScalaResult]]( + s"${name}Arg", + OptionInputType(IntType) + ).withDeprecationReason("Some dir arg reason.") :: Nil), + ast.Directive(name, Vector(ast.Argument(s"${name}Arg", ast.IntValue(123)))) + ) + val NumberType = EnumType( "Number", values = List( EnumValue("ONE", value = 1), EnumValue("TWO", value = 2, deprecationReason = Some("Some enum reason.")))) + val fieldDirective = makeDirective("fieldDir") + val ioDirective = makeDirective("ioDir") + val QueryType = ObjectType( "Query", fields[Unit, Unit]( @@ -32,11 +46,57 @@ class DocumentAnalyzerSpec extends AnyWordSpec with Matchers with StringMatchers "deprecatedField", OptionType(StringType), deprecationReason = Some("Some field reason."), - resolve = _ => "foo") + resolve = _ => "foo"), + Field( + "fieldWithDeprecatedArg", + OptionType(StringType), + resolve = ctx => ctx.argOpt[String]("deprecatedArg"), + arguments = Argument[Option[Int @@ CoercedScalaResult]]( + "deprecatedArg", + OptionInputType(IntType) + ).withDeprecationReason("Some arg reason.") :: Nil + ), + Field( + "fieldWithInputObjectFieldDeprecated", + OptionType(StringType), + resolve = _ => "foo", + arguments = Argument( + "input", + InputObjectType( + "FooInput", + "", + fieldsFn = () => + InputField("deprecatedField", StringType) + .withDeprecationReason("Some input field reason.") :: Nil + ).withDirective(ioDirective._2) + ) :: Nil + ), + Field( + "fieldWithOptionalInputObjectFieldDeprecated", + OptionType(StringType), + resolve = _ => "foo", + arguments = Argument( + "input", + OptionInputType( + InputObjectType( + "FooInput", + "", + fieldsFn = () => + InputField("deprecatedField", OptionInputType(StringType)) + .withDeprecationReason("Some input field reason.") :: Nil + ).withDirective(ioDirective._2)) + ) :: Nil + ), + Field( + "fieldWithDeprecatedDirectiveArg", + OptionType(StringType), + resolve = _ => "foo", + astDirectives = Vector(fieldDirective._2) + ) ) ) - val schema = Schema(QueryType) + val schema = Schema(QueryType, directives = List(fieldDirective, ioDirective).map(_._1)) "DocumentAnalyzer" should { "report empty set for no deprecated usages" in { @@ -59,6 +119,53 @@ class DocumentAnalyzerSpec extends AnyWordSpec with Matchers with StringMatchers contain("The enum value 'Number.TWO' is deprecated. Some enum reason.") } + "report usage of deprecated field args" in { + schema + .analyzer(gql"""{ fieldWithDeprecatedArg(deprecatedArg: "foo") }""") + .deprecatedUsages + .map(_.errorMessage) should + contain( + "The argument 'deprecatedArg' on 'Query.fieldWithDeprecatedArg' is deprecated. Some arg reason.") + } + + "report usage of deprecated field directive arg" in { + schema + .analyzer( + gql"""{ fieldWithDeprecatedDirectiveArg @fieldDir(fieldDirArg: 123)}""" + ) + .deprecatedUsages + .map(_.errorMessage) should contain( + "The argument 'fieldDirArg' on directive 'fieldDir' is deprecated. Some dir arg reason.") + } + + "report usage of deprecated input object field args" in { + schema + .analyzer( + gql"""{ fieldWithInputObjectFieldDeprecated(input: { deprecatedField: "foo" }) }""") + .deprecatedUsages + .map(_.errorMessage) should + contain( + "The input field 'FooInput.deprecatedField' is deprecated. Some input field reason.") + } + + "report usage of deprecated optional input object field args" in { + schema + .analyzer( + gql"""{ fieldWithOptionalInputObjectFieldDeprecated(input: { deprecatedField: "foo" }) }""") + .deprecatedUsages + .map(_.errorMessage) should + contain( + "The input field 'FooInput.deprecatedField' is deprecated. Some input field reason.") + } + + "report usage of deprecated input object directive args" in { + schema + .analyzer(gql"""{ fieldWithInputObjectFieldDeprecated @ioDir(ioDirArg: 123) }""") + .deprecatedUsages + .map(_.errorMessage) should + contain("The argument 'ioDirArg' on directive 'ioDir' is deprecated. Some dir arg reason.") + } + "report usage of deprecated enums in variables" in { schema .analyzer(gql"""query Foo($$x: Number = TWO) { normalField }""") diff --git a/modules/derivation/src/test/scala/sangria/macros/derive/DeriveObjectTypeMacroSpec.scala b/modules/derivation/src/test/scala/sangria/macros/derive/DeriveObjectTypeMacroSpec.scala index d2f602ab..b266f229 100644 --- a/modules/derivation/src/test/scala/sangria/macros/derive/DeriveObjectTypeMacroSpec.scala +++ b/modules/derivation/src/test/scala/sangria/macros/derive/DeriveObjectTypeMacroSpec.scala @@ -603,25 +603,34 @@ class DeriveObjectTypeMacroSpec extends AnyWordSpec with Matchers with FutureRes "id", None, IntrospectionNamedTypeRef(TypeKind.Scalar, "Int"), - Some("123")), + Some("123"), + None, + None), IntrospectionInputValue( "songs", None, IntrospectionNonNullTypeRef(IntrospectionListTypeRef( IntrospectionNonNullTypeRef(IntrospectionNamedTypeRef(TypeKind.Scalar, "String")))), + None, + None, None ), IntrospectionInputValue( "pet", None, IntrospectionNamedTypeRef(TypeKind.InputObject, "Pet"), - Some("""{name:"xxx",size:322}""")), + Some("""{name:"xxx",size:322}"""), + None, + None), IntrospectionInputValue( "aaa", Some("bbbb"), IntrospectionListTypeRef( IntrospectionNonNullTypeRef(IntrospectionNamedTypeRef(TypeKind.Enum, "Color"))), - Some("[Red]")) + Some("[Red]"), + None, + None + ) )) val Some(optField) = introType.fields.find(_.name == "opt") @@ -634,16 +643,22 @@ class DeriveObjectTypeMacroSpec extends AnyWordSpec with Matchers with FutureRes "str", None, IntrospectionNamedTypeRef(TypeKind.Scalar, "String"), + None, + None, None), IntrospectionInputValue( "color", None, IntrospectionNamedTypeRef(TypeKind.Enum, "Color"), + None, + None, None), IntrospectionInputValue( "pet", None, IntrospectionNamedTypeRef(TypeKind.InputObject, "Pet"), + None, + None, None) )) @@ -656,12 +671,16 @@ class DeriveObjectTypeMacroSpec extends AnyWordSpec with Matchers with FutureRes "id", None, IntrospectionNonNullTypeRef(IntrospectionNamedTypeRef(TypeKind.Scalar, "ID")), + None, + None, None), IntrospectionInputValue( "defaultId", None, IntrospectionNamedTypeRef(TypeKind.Scalar, "ID"), - Some(""""47589"""")) + Some(""""47589""""), + None, + None) )) } @@ -720,24 +739,32 @@ class DeriveObjectTypeMacroSpec extends AnyWordSpec with Matchers with FutureRes "id", Some("`id`"), IntrospectionNonNullTypeRef(IntrospectionNamedTypeRef(TypeKind.Scalar, "Int")), + None, + None, None), IntrospectionInputValue( "songs", Some("`songs`"), IntrospectionListTypeRef( IntrospectionNonNullTypeRef(IntrospectionNamedTypeRef(TypeKind.Scalar, "String"))), - Some("""["My favorite song"]""") + Some("""["My favorite song"]"""), + None, + None ), IntrospectionInputValue( "pet", Some("`pet`"), IntrospectionNamedTypeRef(TypeKind.InputObject, "Pet"), - Some("""{name:"Octocat"}""")), + Some("""{name:"Octocat"}"""), + None, + None), IntrospectionInputValue( "colors", None, IntrospectionNonNullTypeRef(IntrospectionListTypeRef( IntrospectionNonNullTypeRef(IntrospectionNamedTypeRef(TypeKind.Enum, "Color")))), + None, + None, None ) )) @@ -752,17 +779,23 @@ class DeriveObjectTypeMacroSpec extends AnyWordSpec with Matchers with FutureRes "description", Some("Optional description"), IntrospectionNamedTypeRef(TypeKind.Scalar, "String"), + None, + None, None), IntrospectionInputValue( "color", Some("a color"), IntrospectionNamedTypeRef(TypeKind.Enum, "Color"), + None, + None, None), IntrospectionInputValue( "pet", None, IntrospectionNamedTypeRef(TypeKind.InputObject, "Pet"), - Some("""{name:"Bell",size:3}""")) + Some("""{name:"Bell",size:3}"""), + None, + None) )) }