diff --git a/core/src/main/scala/caliban/parsing/adt/Directive.scala b/core/src/main/scala/caliban/parsing/adt/Directive.scala index 31534cfa6c..c7dfbe86b4 100644 --- a/core/src/main/scala/caliban/parsing/adt/Directive.scala +++ b/core/src/main/scala/caliban/parsing/adt/Directive.scala @@ -1,5 +1,21 @@ package caliban.parsing.adt -import caliban.InputValue +import caliban.{ InputValue, Value } case class Directive(name: String, arguments: Map[String, InputValue] = Map.empty, index: Int = 0) + +object Directives { + def isDeprecated(directives: List[Directive]): Boolean = + directives.exists(_.name == "deprecated") + + def deprecationReason(directives: List[Directive]): Option[String] = + directives.collectFirst { + case f if f.name == "deprecated" => + f.arguments + .get("reason") + .flatMap(_ match { + case Value.StringValue(value) => Some(value) + case _ => None + }) + }.flatten +} diff --git a/core/src/main/scala/caliban/schema/Schema.scala b/core/src/main/scala/caliban/schema/Schema.scala index 41c99a7f3c..7cd9b66fd5 100644 --- a/core/src/main/scala/caliban/schema/Schema.scala +++ b/core/src/main/scala/caliban/schema/Schema.scala @@ -5,7 +5,7 @@ import caliban.ResponseValue._ import caliban.Value._ import caliban.execution.Field import caliban.introspection.adt._ -import caliban.parsing.adt.Directive +import caliban.parsing.adt.{ Directive, Directives } import caliban.schema.Step._ import caliban.schema.Types._ import caliban.uploads.Upload @@ -144,7 +144,6 @@ trait GenericSchema[R] extends SchemaDerivation[R] with TemporalSchema { directives: List[Directive] = List.empty ): Schema[R1, A] = new Schema[R1, A] { - override def toType(isInput: Boolean, isSubscription: Boolean): __Type = if (isInput) { makeInputObject( @@ -612,7 +611,9 @@ abstract class PartiallyAppliedFieldBase[V](name: String, description: Option[St () => if (ev.optional) ev.toType_(ft.isInput, ft.isSubscription) else Types.makeNonNull(ev.toType_(ft.isInput, ft.isSubscription)), - directives = Some(directives).filter(_.nonEmpty) + isDeprecated = Directives.isDeprecated(directives), + deprecationReason = Directives.deprecationReason(directives), + directives = Some(directives.filter(_.name != "deprecated")).filter(_.nonEmpty) ) } @@ -635,7 +636,15 @@ case class PartiallyAppliedFieldLazy[V](name: String, description: Option[String case class PartiallyAppliedFieldWithArgs[V, A](name: String, description: Option[String], directives: List[Directive]) { def apply[R, V1](fn: V => (A => V1))(implicit ev1: Schema[R, A => V1], fa: FieldAttributes): (__Field, V => Step[R]) = ( - __Field(name, description, ev1.arguments, () => ev1.toType_(fa.isInput, fa.isSubscription)), + __Field( + name, + description, + ev1.arguments, + () => ev1.toType_(fa.isInput, fa.isSubscription), + isDeprecated = Directives.isDeprecated(directives), + deprecationReason = Directives.deprecationReason(directives), + directives = Some(directives.filter(_.name != "deprecated")).filter(_.nonEmpty) + ), (v: V) => ev1.resolve(fn(v)) ) } diff --git a/core/src/test/scala/caliban/execution/ExecutionSpec.scala b/core/src/test/scala/caliban/execution/ExecutionSpec.scala index 08bd89392a..fcff4030ea 100644 --- a/core/src/test/scala/caliban/execution/ExecutionSpec.scala +++ b/core/src/test/scala/caliban/execution/ExecutionSpec.scala @@ -945,6 +945,64 @@ object ExecutionSpec extends DefaultRunnableSpec { ) ) }, + testM("directives on hand-rolled schema") { + import Schema._ + import caliban.parsing.adt.Directive + + case class Foo(fieldA: String => String = _ => "foo", fieldB: String = "foo") + + implicit lazy val fooSchema: Schema[Any, Foo] = obj("Foo", None)(implicit ft => + List( + fieldWithArgs( + "fieldA", + Some("Description"), + List( + Directive( + "deprecated", + Map( + "reason" -> Value.StringValue("due to reasons") + ) + ) + ) + )(_.fieldA), + field( + "fieldB", + Some("Description"), + List( + Directive( + "deprecated", + Map( + "reason" -> Value.StringValue("due to reasons") + ) + ) + ) + )(_.fieldB) + ) + ) + + case class Queries(foo: Foo) + + val queries: Queries = Queries(Foo()) + + val api: GraphQL[Any] = GraphQL.graphQL(RootResolver(queries)) + val interpreter = api.interpreter + + val query = gqldoc("""{ + __type(name: "Foo") { + name + fields(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } + } + }""") + + val expected = + """{"__type":{"name":"Foo","fields":[{"name":"fieldA","isDeprecated":true,"deprecationReason":"due to reasons"},{"name":"fieldB","isDeprecated":true,"deprecationReason":"due to reasons"}]}}""" + + assertM(interpreter.flatMap(_.execute(query)).map(_.data.toString))(equalTo(expected)) + }, testM("union redirect") { sealed trait Foo