Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better support for custom directives #915

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ lazy val core = project
ProblemFilters.exclude[MissingTypesProblem]("sangria.schema.Directive$"),
ProblemFilters.exclude[MissingTypesProblem]("sangria.schema.MappedAbstractType"),
ProblemFilters.exclude[IncompatibleMethTypeProblem](
"sangria.execution.Resolver.resolveSimpleListValue")
"sangria.execution.Resolver.resolveSimpleListValue"),
ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.schema.Field.apply")
),
Test / testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-oF"),
libraryDependencies ++= Seq(
Expand Down
2 changes: 1 addition & 1 deletion modules/ast/src/main/scala/sangria/ast/QueryAst.scala
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ sealed trait WithDirectives extends AstNode {

case class Directive(
name: String,
arguments: Vector[Argument],
arguments: Vector[Argument] = Vector.empty,
comments: Vector[Comment] = Vector.empty,
location: Option[AstLocation] = None)
extends AstNode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package sangria.schema
import sangria.ast
import sangria.execution.FieldTag
import sangria.marshalling.{FromInput, MarshallerCapability, ScalarValueInfo, ToInput}
import sangria.schema.DirectiveLocationValue.On
import sangria.schema.InputObjectType.DefaultInput
import sangria.util.tag.@@
import sangria.validation.Violation

import scala.reflect.ClassTag
Expand Down Expand Up @@ -570,7 +572,8 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
deprecationReason = fieldDeprecationReason(definition),
complexity = fieldComplexity(typeDefinition, definition),
manualPossibleTypes = () => Nil,
astDirectives = definition.directives,
astDirectives = definition.directives
.asInstanceOf[Vector[ast.Directive with On[DirectiveLocationValue.Field.type]]],
astNodes = Vector(definition)
))

Expand Down
24 changes: 20 additions & 4 deletions modules/core/src/main/scala/sangria/schema/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import sangria.{ast, introspection}
import sangria.validation._
import sangria.introspection._
import sangria.renderer.{QueryRenderer, SchemaFilter, SchemaRenderer}
import sangria.schema.DirectiveLocationValue.On
import sangria.schema.InputObjectType.DefaultInput
import sangria.streaming.SubscriptionStreamLike

Expand Down Expand Up @@ -594,7 +595,7 @@ case class Field[Ctx, Val](
tags: List[FieldTag],
complexity: Option[(Ctx, Args, Double) => Double],
manualPossibleTypes: () => List[ObjectType[_, _]],
astDirectives: Vector[ast.Directive],
astDirectives: Vector[ast.Directive with On[DirectiveLocationValue.Field.type]],
astNodes: Vector[ast.AstNode])
extends Named
with HasArguments
Expand All @@ -618,8 +619,9 @@ object Field {
possibleTypes: => List[PossibleObject[_, _]] = Nil,
tags: List[FieldTag] = Nil,
complexity: Option[(Ctx, Args, Double) => Double] = None,
deprecationReason: Option[String] = None)(implicit
ev: ValidOutType[Res, Out]): Field[Ctx, Val] =
deprecationReason: Option[String] = None,
astDirectives: Vector[ast.Directive with On[DirectiveLocationValue.Field.type]] = Vector.empty
)(implicit ev: ValidOutType[Res, Out]): Field[Ctx, Val] =
Field[Ctx, Val](
name,
fieldType,
Expand All @@ -630,7 +632,7 @@ object Field {
tags,
complexity,
() => possibleTypes.map(_.objectType),
Vector.empty,
astDirectives,
Vector.empty)

def subs[Ctx, Val, StreamSource, Res, Out](
Expand Down Expand Up @@ -1133,6 +1135,20 @@ sealed trait HasArguments {
def arguments: List[Argument[_]]
}

sealed trait DirectiveLocationValue {
def spec: String
}
object DirectiveLocationValue {
trait On[V <: DirectiveLocationValue]

case object ArgumentDefinition extends DirectiveLocationValue {
override val spec: String = "ARGUMENT_DEFINITION"
}
case object Field extends DirectiveLocationValue {
override val spec: String = "FIELD"
}
}

object DirectiveLocation extends Enumeration {
val ArgumentDefinition: Value = Value
val Enum: Value = Value
Expand Down
145 changes: 145 additions & 0 deletions modules/core/src/test/scala/sangria/schema/CustomDirectiveSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package sangria.schema

import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import sangria.ast
import sangria.renderer.QueryRenderer
import sangria.schema.DirectiveLocationValue.On
import sangria.util.tag.@@

class CustomDirectiveSpec extends AnyWordSpec with Matchers {

case class Domain(value: Int)

private val AllDirective = new ast.Directive("field-directive")
with On[DirectiveLocationValue.Field.type with DirectiveLocationValue.ArgumentDefinition.type]

// with OnField
// with OnArgument
// with OnObjectType
// with OnInterfaceType

private val FieldDirective: ast.Directive with On[DirectiveLocationValue.Field.type] =
new ast.Directive("field-directive") with On[DirectiveLocationValue.Field.type]

// private val ArgumentDirective = new ast.Directive("arg-directive") with OnArgument
//
// private val ObjectDirective = new ast.Directive("object-directive") with OnObjectType
//
// private val InterfaceDirective = new ast.Directive("interface-directive") with OnInterfaceType
//
// private val CustomDirective = ast.Directive("custom-directive")

private val resolve: Context[Unit, Domain] => Action[Unit, Int] = _.value.value

"custom directive" when {
"in context of a Field" should {
"be applied if marked with OnField" in {
fields[Unit, Domain](
Field("field", IntType, resolve = resolve, astDirectives = Vector(FieldDirective)))

fields[Unit, Domain](
Field("field", IntType, resolve = resolve, astDirectives = Vector(AllDirective)))

fields[Unit, Domain](
Field(
"field",
IntType,
resolve = resolve,
astDirectives = Vector(FieldDirective, AllDirective)))

Field("field", IntType, resolve = resolve, astDirectives = Vector(FieldDirective)): Field[
Unit,
Domain]

// val field = (Field("field", IntType, resolve = resolve): Field[Unit, Domain])
// .withDirective(FieldDirective)
// .withDirectives(FieldDirective, AllDirective)
// field.astDirectives should be(Vector(FieldDirective, FieldDirective, AllDirective))
}

"not be applied if not marked with OnField" in {
assertTypeError("""
|fields[Unit, Domain](
| Field("field", IntType, resolve = resolve, astDirectives = Vector(CustomDirective)))
|""".stripMargin)

assertTypeError("""
|val field: Field[Unit, Domain] =
| Field("field", IntType, resolve = resolve, astDirectives = Vector(CustomDirective))
|""".stripMargin)
}

// "be combined with the @deprecated directive" in {
// val field = (Field(
// "field",
// IntType,
// resolve = resolve,
// deprecationReason = Some("use field2")): Field[Unit, Domain])
// .withDirective(FieldDirective)
//
// field.astDirectives should be(Vector(FieldDirective))
// QueryRenderer.renderPretty(field.toAst) should equal(
// """field: Int! @field-directive @deprecated(reason: "use field2")""")
// }
}
}

// "in context of an Argument" should {
// "be applied if marked with OnArgument" in {
// Argument("name", IntType, 42, astDirectives = Vector(ArgumentDirective))
// Argument("name", IntType, 42, astDirectives = Vector(AllDirective))
// Argument("name", IntType, 42).withDirective(ArgumentDirective)
// val arg = Argument("name", IntType, 42)
// .withDirective(AllDirective)
// .withDirectives(ArgumentDirective, ArgumentDirective)
// arg.astDirectives should be(Vector(AllDirective, ArgumentDirective, ArgumentDirective))
// }
//
// "not be applied if not marked with OnArgument" in {
// assertTypeError("""
// |Argument("name", IntType, 42, astDirectives = Vector(FieldDirective))
// |""".stripMargin)
// assertTypeError("""
// |Argument("name", IntType, 42).withDirective(FieldDirective)
// |""".stripMargin)
// }
// }
//
// "in context of an ObjectType" should {
// "be applied if marked with OnObjectType" in {
// val obj = ObjectType[Unit, Domain]("name", fields[Unit, Domain]())
// .withDirective(ObjectDirective)
// .withDirectives(AllDirective, ObjectDirective)
// obj.astDirectives should be(Vector(ObjectDirective, AllDirective, ObjectDirective))
// }
//
// "not be applied if not marked with OnObjectType" in {
// assertTypeError("""
// |ObjectType[Unit, Domain]("name", fields[Unit, Domain]()).withDirective(CustomDirective)
// |""".stripMargin)
// assertTypeError("""
// |ObjectType[Unit, Domain]("name", fields[Unit, Domain]()).withDirective(FieldDirective)
// |""".stripMargin)
// }
// }
//
// "in context of an InterfaceType" should {
// "be applied if marked with OnInterfaceType" in {
// val interface = InterfaceType[Unit, Domain]("name", fields[Unit, Domain]())
// .withDirective(InterfaceDirective)
// .withDirectives(AllDirective, InterfaceDirective)
// interface.astDirectives should be(
// Vector(InterfaceDirective, AllDirective, InterfaceDirective))
// }
//
// "not be applied if not marked with OnObjectType" in {
// assertTypeError("""
// |InterfaceType[Unit, Domain]("name", fields[Unit, Domain]()).withDirective(CustomDirective)
// |""".stripMargin)
// assertTypeError("""
// |InterfaceType[Unit, Domain]("name", fields[Unit, Domain]()).withDirective(FieldDirective)
// |""".stripMargin)
// }
// }
}