Skip to content

Commit

Permalink
Support @deprecated on argument and input field
Browse files Browse the repository at this point in the history
Fixes #982
  • Loading branch information
yanns committed Apr 9, 2023
1 parent 3f3a5d1 commit 12765c3
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 43 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ jobs:
~/Library/Caches/Coursier/v1
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}

- name: Check binary compatibility
run: sbt ++${{ matrix.scala }} mimaReportBinaryIssues

- name: Check formatting
run: sbt ++${{ matrix.scala }} scalafmtCheckAll

Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ val isScala3 = Def.setting(
ThisBuild / crossScalaVersions := Seq("2.12.17", "2.13.10", "3.2.2")
ThisBuild / scalaVersion := crossScalaVersions.value.tail.head
ThisBuild / githubWorkflowBuildPreamble ++= List(
WorkflowStep.Sbt(List("mimaReportBinaryIssues"), name = Some("Check binary compatibility")),
// WorkflowStep.Sbt(List("mimaReportBinaryIssues"), name = Some("Check binary compatibility")),
WorkflowStep.Sbt(List("scalafmtCheckAll"), name = Some("Check formatting"))
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = mapBooleanField(value, "isDeprecated", path),
deprecationReason = mapStringFieldOpt(value, "deprecationReason")
)

private def parseField[In: InputUnmarshaller](field: In, path: Vector[String]) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ case class IntrospectionInputValue(
name: String,
description: Option[String],
tpe: IntrospectionTypeRef,
defaultValue: Option[String])
defaultValue: Option[String],
isDeprecated: Boolean,
deprecationReason: Option[String])

sealed trait IntrospectionTypeRef {
def kind: TypeKind.Value
Expand Down
49 changes: 39 additions & 10 deletions modules/core/src/main/scala/sangria/introspection/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,16 @@ 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)
Expand Down Expand Up @@ -309,10 +318,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),
Expand Down Expand Up @@ -342,7 +358,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", BooleanType, resolve = _.value.deprecationReason.isDefined),
Field("deprecationReason", OptionType(StringType), resolve = _.value.deprecationReason)
)
)

Expand Down Expand Up @@ -374,7 +392,16 @@ 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,
Expand Down Expand Up @@ -475,7 +502,7 @@ package object introspection {
| name
| description
| locations
| args {
| args(includeDeprecated: true) {
| ...InputValue
| }
| ${if (directiveRepeatableFlag) "isRepeatable" else ""}
Expand All @@ -490,7 +517,7 @@ package object introspection {
| fields(includeDeprecated: true) {
| name
| description
| args {
| args(includeDeprecated: true) {
| ...InputValue
| }
| type {
Expand All @@ -499,7 +526,7 @@ package object introspection {
| isDeprecated
| deprecationReason
| }
| inputFields {
| inputFields(includeDeprecated: true) {
| ...InputValue
| }
| interfaces {
Expand All @@ -520,6 +547,8 @@ package object introspection {
| description
| type { ...TypeRef }
| defaultValue
| isDeprecated
| deprecationReason
|}
|fragment TypeRef on __Type {
| kind
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
description = inputFieldDescription(definition),
fieldType = tpe,
defaultValue = defaultValue,
deprecationReason = inputValueDeprecationReason(definition),
astDirectives = definition.directives,
astNodes = Vector(definition)
))
Expand Down Expand Up @@ -718,6 +719,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
argumentType = tpe,
defaultValue = defaultValue,
fromInput = argumentFromInput(typeDefinition, fieldDefinition, definition),
deprecationReason = inputValueDeprecationReason(definition),
astDirectives = definition.directives,
astNodes = Vector(definition)
))
Expand Down Expand Up @@ -851,12 +853,15 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
None

def enumValueDeprecationReason(definition: ast.EnumValueDefinition): Option[String] =
deprecationReason(definition.directives.toList)
deprecationReason(definition.directives)

def fieldDeprecationReason(definition: ast.FieldDefinition): Option[String] =
deprecationReason(definition.directives.toList)
deprecationReason(definition.directives)

def deprecationReason(dirs: List[ast.Directive]): Option[String] =
def inputValueDeprecationReason(definition: ast.InputValueDefinition): Option[String] =
deprecationReason(definition.directives)

def deprecationReason(dirs: Iterable[ast.Directive]): Option[String] =
dirs.find(_.name == DeprecatedDirective.name).flatMap { d =>
d.arguments.find(_.name == ReasonArg.name) match {
case Some(reason) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ class DefaultIntrospectionSchemaBuilder[Ctx] extends IntrospectionSchemaBuilder[
description = inputFieldDescription(definition),
fieldType = tpe,
defaultValue = defaultValue,
deprecationReason = inputFieldDeprecationReason(definition),
astDirectives = Vector.empty,
astNodes = Vector.empty
))
Expand All @@ -279,6 +280,7 @@ class DefaultIntrospectionSchemaBuilder[Ctx] extends IntrospectionSchemaBuilder[
argumentType = tpe,
defaultValue = defaultValue,
fromInput = argumentFromInput(fieldDefinition, definition),
deprecationReason = inputValueDeprecationReason(definition),
astDirectives = Vector.empty,
astNodes = Vector.empty
))
Expand Down Expand Up @@ -381,6 +383,14 @@ class DefaultIntrospectionSchemaBuilder[Ctx] extends IntrospectionSchemaBuilder[
definition.deprecationReason.orElse(
if (definition.isDeprecated) Some(DefaultDeprecationReason) else None)

def inputFieldDeprecationReason(definition: IntrospectionInputValue): Option[String] =
definition.deprecationReason.orElse(
if (definition.isDeprecated) Some(DefaultDeprecationReason) else None)

def inputValueDeprecationReason(definition: IntrospectionInputValue): Option[String] =
definition.deprecationReason.orElse(
if (definition.isDeprecated) Some(DefaultDeprecationReason) else None)

def enumValueDeprecationReason(definition: IntrospectionEnumValue): Option[String] =
definition.deprecationReason.orElse(
if (definition.isDeprecated) Some(DefaultDeprecationReason) else None)
Expand Down
39 changes: 32 additions & 7 deletions modules/core/src/main/scala/sangria/schema/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,7 @@ trait InputValue[T] {
def inputValueType: InputType[_]
def description: Option[String]
def defaultValue: Option[(_, ToInput[_, _])]
def deprecationReason: Option[String]
}

/** @param description
Expand All @@ -719,10 +720,12 @@ 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 HasDeprecation
with HasAstInfo {

def withDirective(directive: ast.Directive): Argument[T] =
Expand Down Expand Up @@ -751,6 +754,7 @@ object Argument {
Some(description),
Some(defaultValue -> toInput),
fromInput,
None,
Vector.empty,
Vector.empty)

Expand All @@ -764,26 +768,35 @@ 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)
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,
argumentType: InputType[T],
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,
Expand All @@ -799,6 +812,7 @@ object Argument {
description,
Some(defaultValue -> toInput),
fromInput,
None,
Vector.empty,
Vector.empty)
}
Expand Down Expand Up @@ -1102,10 +1116,12 @@ 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 HasDeprecation
with HasAstInfo {
def withDirective(directive: ast.Directive): InputField[T] =
copy(astDirectives = astDirectives :+ directive)
Expand All @@ -1129,23 +1145,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](
Expand All @@ -1159,14 +1183,15 @@ object InputField {
fieldType,
description,
Some(defaultValue -> toInput),
None,
Vector.empty,
Vector.empty).asInstanceOf[InputField[res.Res]]

def createFromMacroWithoutDefault[T](
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]]
}

Expand Down
6 changes: 5 additions & 1 deletion modules/core/src/main/scala/sangria/schema/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,11 @@ 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.ArgumentDefinition,
DirectiveLocation.InputFieldDefinition,
DirectiveLocation.EnumValue),
shouldInclude = ctx => !ctx.arg(IfArg)
)

Expand Down
Loading

0 comments on commit 12765c3

Please sign in to comment.