Skip to content

Commit

Permalink
API for setting custom directives
Browse files Browse the repository at this point in the history
Prototype for #913
In this prototype, we check how we can apply custom directives much easier.
Furthermore, we check if we can prove type safety, to that a custom directive
that can be applied on some elements cannot be applied on others.

Limitations:
- a directive can be applied on a field definition for example. With that current approach,
we cannot formulate that with the type system as a field definition lives in sangria.ast, and
we only handle sangria.schema types.
- we are introducing new types to mark on which elements a directive can be applied. Those types
are kind of duplication of the current [sangria.schema.DirectiveLocation values](https://github.com/sangria-graphql/sangria/blob/f339b5df97bd89c2a24fcfc977a1f20191ffd7fc/modules/core/src/main/scala/sangria/schema/Schema.scala#L1136-L1158).
  • Loading branch information
yanns committed Oct 6, 2022
1 parent f339b5d commit 8fe1f86
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 49 deletions.
5 changes: 4 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@ 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.Argument.apply"),
ProblemFilters.exclude[DirectMissingMethodProblem]("sangria.schema.Field.subs"),
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
63 changes: 40 additions & 23 deletions modules/core/src/main/scala/sangria/schema/AstSchemaBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,8 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
description = definition.flatMap(_.description.map(_.value)),
directives = directives,
astDirectives =
definition.fold(Vector.empty[ast.Directive])(_.directives) ++ extensions.flatMap(
_.directives),
(definition.fold(Vector.empty[ast.Directive])(_.directives) ++ extensions.flatMap(
_.directives)).asInstanceOf[Vector[ast.Directive with OnSchema]],
astNodes = Vector(mat.document) ++ extensions ++ definition.toVector
)

Expand All @@ -346,7 +346,8 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
directives = directives,
description = originalSchema.description,
validationRules = originalSchema.validationRules,
astDirectives = originalSchema.astDirectives ++ extensions.flatMap(_.directives),
astDirectives = (originalSchema.astDirectives ++ extensions.flatMap(_.directives))
.asInstanceOf[Vector[ast.Directive with OnSchema]],
astNodes = {
val (docs, other) = originalSchema.astNodes.partition(_.isInstanceOf[ast.Document])
val newDoc = ast.Document.merge(docs.asInstanceOf[Vector[ast.Document]] :+ mat.document)
Expand Down Expand Up @@ -374,7 +375,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
interfaces = interfaces,
instanceCheck =
(value: Any, clazz: Class[_], _: ObjectType[Ctx, Any]) => fn(value, clazz),
astDirectives = directives,
astDirectives = directives.asInstanceOf[Vector[ast.Directive with OnObjectType]],
astNodes = (definition +: extensions).toVector
)
case None =>
Expand All @@ -384,7 +385,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
fieldsFn = fields,
interfaces = interfaces,
instanceCheck = ObjectType.defaultInstanceCheck[Ctx, Any],
astDirectives = directives,
astDirectives = directives.asInstanceOf[Vector[ast.Directive with OnObjectType]],
astNodes = (definition +: extensions).toVector
)
}
Expand All @@ -404,15 +405,17 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
existing.copy(
fieldsFn = fields,
interfaces = interfaces,
astDirectives = existing.astDirectives ++ extensions.flatMap(_.directives),
astDirectives = existing.astDirectives ++ extensions.flatMap(
_.directives.asInstanceOf[Vector[ast.Directive with OnObjectType]]),
astNodes = existing.astNodes ++ extensions,
instanceCheck = (value: Any, clazz: Class[_], _: ObjectType[Ctx, Any]) => fn(value, clazz)
)(ClassTag(existing.valClass))
case None =>
existing.copy(
fieldsFn = fields,
interfaces = interfaces,
astDirectives = existing.astDirectives ++ extensions.flatMap(_.directives),
astDirectives = existing.astDirectives ++ extensions.flatMap(
_.directives.asInstanceOf[Vector[ast.Directive with OnObjectType]]),
astNodes = existing.astNodes ++ extensions,
instanceCheck =
existing.instanceCheck.asInstanceOf[(Any, Class[_], ObjectType[Ctx, _]) => Boolean]
Expand All @@ -430,7 +433,8 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
name = typeName(definition),
description = typeDescription(definition),
fieldsFn = fields,
astDirectives = definition.directives ++ extensions.flatMap(_.directives),
astDirectives = (definition.directives ++ extensions.flatMap(_.directives))
.asInstanceOf[Vector[ast.Directive with OnInputObjectType]],
astNodes = definition +: extensions
))

Expand All @@ -449,7 +453,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
fieldsFn = fields,
interfaces = Nil,
manualPossibleTypes = () => Nil,
astDirectives = directives,
astDirectives = directives.asInstanceOf[Vector[ast.Directive with OnInterfaceType]],
astNodes = (definition +: extensions).toVector
))
}
Expand All @@ -464,7 +468,9 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
fieldsFn = fields,
manualPossibleTypes = () => Nil,
interfaces = Nil,
astDirectives = existing.astDirectives ++ extensions.flatMap(_.directives),
astDirectives = existing.astDirectives ++ extensions
.flatMap(_.directives)
.asInstanceOf[scala.collection.IterableOnce[ast.Directive with OnInterfaceType]],
astNodes = existing.astNodes ++ extensions
)

Expand All @@ -479,7 +485,8 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
name = typeName(definition),
description = typeDescription(definition),
types = types,
astDirectives = definition.directives ++ extensions.flatMap(_.directives),
astDirectives = (definition.directives ++ extensions.flatMap(_.directives))
.asInstanceOf[Vector[ast.Directive with OnUnionType]],
astNodes = definition +: extensions
))

Expand All @@ -491,8 +498,10 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
mat: AstSchemaMaterializer[Ctx]): UnionType[Ctx] =
existing.copy(
typesFn = () => types,
astDirectives = existing.astDirectives ++ extensions.flatMap(_.directives),
astNodes = existing.astNodes ++ extensions)
astDirectives = (existing.astDirectives ++ extensions.flatMap(_.directives))
.asInstanceOf[Vector[ast.Directive with OnUnionType]],
astNodes = existing.astNodes ++ extensions
)

def extendScalarAlias[T, ST](
origin: MatOrigin,
Expand All @@ -516,7 +525,8 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
coerceInput = scalarCoerceInput(definition),
complexity = scalarComplexity(definition),
scalarInfo = scalarValueInfo(definition),
astDirectives = definition.directives ++ extensions.flatMap(_.directives),
astDirectives = (definition.directives ++ extensions.flatMap(_.directives))
.asInstanceOf[Vector[ast.Directive with OnScalarType]],
astNodes = definition +: extensions
))

Expand All @@ -531,7 +541,8 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
name = typeName(definition),
description = typeDescription(definition),
values = values,
astDirectives = definition.directives ++ extensions.flatMap(_.directives),
astDirectives = (definition.directives ++ extensions.flatMap(_.directives))
.asInstanceOf[Vector[ast.Directive with OnEnumType]],
astNodes = definition +: extensions
))

Expand All @@ -547,7 +558,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
description = enumValueDescription(definition),
value = enumValue(typeDefinition, definition),
deprecationReason = enumValueDeprecationReason(definition),
astDirectives = definition.directives,
astDirectives = definition.directives.asInstanceOf[Vector[ast.Directive with OnEnumValue]],
astNodes = Vector(definition)
))

Expand All @@ -570,7 +581,7 @@ 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 OnField]],
astNodes = Vector(definition)
))

Expand Down Expand Up @@ -655,7 +666,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
description = inputFieldDescription(definition),
fieldType = tpe,
defaultValue = defaultValue,
astDirectives = definition.directives,
astDirectives = definition.directives.asInstanceOf[Vector[ast.Directive with OnInputField]],
astNodes = Vector(definition)
))

Expand Down Expand Up @@ -683,7 +694,7 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
argumentType = tpe,
defaultValue = defaultValue,
fromInput = argumentFromInput(typeDefinition, fieldDefinition, definition),
astDirectives = definition.directives,
astDirectives = definition.directives.asInstanceOf[Vector[ast.Directive with OnArgument]],
astNodes = Vector(definition)
))

Expand Down Expand Up @@ -720,8 +731,10 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
mat: AstSchemaMaterializer[Ctx]): InputObjectType[T] =
existing.copy(
fieldsFn = fields,
astDirectives = existing.astDirectives ++ extensions.flatMap(_.directives),
astNodes = existing.astNodes ++ extensions)
astDirectives = (existing.astDirectives ++ extensions.flatMap(_.directives))
.asInstanceOf[Vector[ast.Directive with OnInputObjectType]],
astNodes = existing.astNodes ++ extensions
)

def transformEnumType[T](
origin: MatOrigin,
Expand All @@ -731,7 +744,9 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
val dirs = existing.astDirectives ++ extensions.flatMap(_.directives)

if (dirs.nonEmpty)
existing.copy(astDirectives = dirs, astNodes = existing.astNodes ++ extensions)
existing.copy(
astDirectives = dirs.asInstanceOf[Vector[ast.Directive with OnEnumType]],
astNodes = existing.astNodes ++ extensions)
else existing
}

Expand All @@ -743,7 +758,9 @@ class DefaultAstSchemaBuilder[Ctx] extends AstSchemaBuilder[Ctx] {
val dirs = existing.astDirectives ++ extensions.flatMap(_.directives)

if (dirs.nonEmpty)
existing.copy(astDirectives = dirs, astNodes = existing.astNodes ++ extensions)
existing.copy(
astDirectives = dirs.asInstanceOf[Vector[ast.Directive with OnScalarType]],
astNodes = existing.astNodes ++ extensions)
else existing
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,8 @@ class AstSchemaMaterializer[Ctx] private (
def extendEnumType(origin: MatOrigin, tpe: EnumType[_]) = {
val extensions = findEnumExtensions(tpe.name)
val extraValues = extensions.flatMap(_.values)
val extraDirs = extensions.flatMap(_.directives)
val extraDirs =
extensions.flatMap(_.directives).asInstanceOf[Vector[ast.Directive with OnEnumType]]

val ev = extraValues.flatMap(buildEnumValue(origin, Right(tpe), _, extensions))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ class ResolverBasedAstSchemaBuilder[Ctx](val resolvers: Seq[AstSchemaResolver[Ct
description = definition.flatMap(_.description.map(_.value)),
directives = directives,
astDirectives =
definition.fold(Vector.empty[ast.Directive])(_.directives) ++ extensions.flatMap(
_.directives),
(definition.fold(Vector.empty[ast.Directive])(_.directives) ++ extensions.flatMap(
_.directives)).asInstanceOf[Vector[ast.Directive with OnSchema]],
astNodes = Vector(mat.document) ++ extensions ++ definition.toVector,
validationRules = SchemaValidationRule.default :+ new ResolvedDirectiveValidationRule(
this.directives.filterNot(_.repeatable).map(_.name).toSet)
Expand Down
Loading

0 comments on commit 8fe1f86

Please sign in to comment.