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

feat: schema directives #277

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
143 changes: 115 additions & 28 deletions modules/core/src/main/scala/gql/Directive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,47 @@ package gql
import gql.parser.{QueryAst => QA}
import gql.preparation.MergedFieldInfo
import gql.parser.QueryAst
import fs2.Pure

/** A [[Directive]] takes an argument A and performs some context specific ast transformation.
*
* The [[Directive]] structure defines executable directives that modify execution
* https://spec.graphql.org/draft/#ExecutableDirectiveLocation.
*/
final case class Directive[A](
name: String,
isRepeatable: Boolean,
arg: EmptyableArg[A] = EmptyableArg.Empty
)
) {
def repeatable = copy(isRepeatable = true)
def unrepeatable = copy(isRepeatable = false)
}

/** Consider taking a look at the skip and include directives as an example.
*/
object Directive {
val skipDirective = Directive("skip", EmptyableArg.Lift(gql.dsl.input.arg[Boolean]("if")))
val skipDirective = Directive("skip", false, EmptyableArg.Lift(gql.dsl.input.arg[Boolean]("if")))

def skipPositions[F[_]]: List[Position[F, ?]] = {
val field = Position.Field(
def skipPositions[F[_]]: List[QueryPosition[F, ?]] = {
val field: QueryPosition[F, ?] = Position.Field(
skipDirective,
new Position.FieldHandler[F, Boolean] {
override def apply[I, C](
override def apply[F2[x] >: F[x], I, C](
a: Boolean,
field: ast.Field[F, I, ?],
mfa: MergedFieldInfo[F, C]
): Either[String, List[(ast.Field[F, I, ?], MergedFieldInfo[F, C])]] =
field: ast.Field[F2, I, ?],
mfa: MergedFieldInfo[F2, C]
): Either[String, List[(ast.Field[F2, I, ?], MergedFieldInfo[F2, C])]] =
if (a) Right(Nil) else Right(List((field, mfa)))
}
)
val fragmentSpread = Position.FragmentSpread[Boolean](
val fragmentSpread: QueryPosition[F, ?] = Position.FragmentSpread[Boolean](
skipDirective,
new Position.QueryHandler[QA.FragmentSpread, Boolean] {
override def apply[C](a: Boolean, query: QueryAst.FragmentSpread[C]): Either[String, List[QueryAst.FragmentSpread[C]]] =
if (a) Right(Nil) else Right(List(query))
}
)
val inlineFragmentSpread = Position.InlineFragmentSpread[Boolean](
val inlineFragmentSpread: QueryPosition[F, ?] = Position.InlineFragmentSpread[Boolean](
skipDirective,
new Position.QueryHandler[QA.InlineFragment, Boolean] {
override def apply[C](a: Boolean, query: QueryAst.InlineFragment[C]): Either[String, List[QueryAst.InlineFragment[C]]] =
Expand All @@ -61,28 +69,28 @@ object Directive {
List(field, fragmentSpread, inlineFragmentSpread)
}

val includeDirective = Directive("include", EmptyableArg.Lift(gql.dsl.input.arg[Boolean]("if")))
val includeDirective = Directive("include", false, EmptyableArg.Lift(gql.dsl.input.arg[Boolean]("if")))

def includePositions[F[_]]: List[Position[F, ?]] = {
val field = Position.Field(
def includePositions[F[_]]: List[QueryPosition[F, ?]] = {
val field: QueryPosition[F, ?] = Position.Field(
includeDirective,
new Position.FieldHandler[F, Boolean] {
override def apply[I, C](
override def apply[F2[x] >: F[x], I, C](
a: Boolean,
field: ast.Field[F, I, ?],
mfa: MergedFieldInfo[F, C]
): Either[String, List[(ast.Field[F, I, ?], MergedFieldInfo[F, C])]] =
field: ast.Field[F2, I, ?],
mfa: MergedFieldInfo[F2, C]
): Either[String, List[(ast.Field[F2, I, ?], MergedFieldInfo[F2, C])]] =
if (a) Right(List((field, mfa))) else Right(Nil)
}
)
val fragmentSpread = Position.FragmentSpread[Boolean](
val fragmentSpread: QueryPosition[F, ?] = Position.FragmentSpread[Boolean](
includeDirective,
new Position.QueryHandler[QA.FragmentSpread, Boolean] {
override def apply[C](a: Boolean, query: QueryAst.FragmentSpread[C]): Either[String, List[QueryAst.FragmentSpread[C]]] =
if (a) Right(List(query)) else Right(Nil)
}
)
val inlineFragmentSpread = Position.InlineFragmentSpread[Boolean](
val inlineFragmentSpread: QueryPosition[F, ?] = Position.InlineFragmentSpread[Boolean](
includeDirective,
new Position.QueryHandler[QA.InlineFragment, Boolean] {
override def apply[C](a: Boolean, query: QueryAst.InlineFragment[C]): Either[String, List[QueryAst.InlineFragment[C]]] =
Expand All @@ -92,34 +100,113 @@ object Directive {

List(field, fragmentSpread, inlineFragmentSpread)
}

val deprecatedDirective = Directive(
"deprecated",
false,
EmptyableArg.Lift(
gql.dsl.input.arg[String]("reason", gql.dsl.input.value.scalar("No longer supported"))
)
)

def deprecated[F[_], Struct[_[_], _]]: Position.Schema[F, String, Struct] =
Position.Schema[F, String, Struct](
deprecatedDirective,
new Position.SchemaHandler[F, String, Struct] {
def apply[F2[x] >: F[x], B](a: String, struct: Struct[F2,B]): Either[String,Struct[F2,B]] = Right(struct)

}
)

val deprecatedEnum: Position.Schema.Enum[String] =
deprecated[fs2.Pure, Position.PureStruct[ast.Enum]#T]
val deprecatedType: Position.Schema.Type[fs2.Pure, String] =
deprecated[fs2.Pure, ast.Type]
val deprecatedInterface: Position.Schema.Interface[fs2.Pure, String] =
deprecated[fs2.Pure, ast.Interface]
val deprecatedInput: Position.Schema.Input[String] =
deprecated[fs2.Pure, Position.PureStruct[ast.Input]#T]
val deprecatedUnion: Position.Schema.Union[fs2.Pure, String] =
deprecated[fs2.Pure, ast.Union]
val deprecatedScalar =
deprecated[fs2.Pure, Position.PureStruct[ast.Scalar]#T]
val deprecatedField = Position.Field[fs2.Pure, String](
deprecatedDirective,
new Position.FieldHandler[fs2.Pure, String] {
def apply[F2[x] >: fs2.Pure[x], I, C](
a: String,
field: ast.Field[F2, I, ?],
mfa: MergedFieldInfo[F2, C]
): Either[String, List[(ast.Field[F2, I, ?], MergedFieldInfo[F2, C])]] = Right(List((field, mfa)))
}
)
}

// Add as necessary
sealed trait Position[+F[_], A] {
def directive: Directive[A]
}
sealed trait QueryPosition[+F[_], A] extends Position[F, A]
sealed trait SchemaPosition[+F[_], A] extends Position[F, A]
object Position {
trait FieldHandler[F[_], A] {
def apply[I, C](
trait FieldHandler[+F[_], A] {
def apply[F2[x] >: F[x], I, C](
a: A,
field: ast.Field[F, I, ?],
mfa: MergedFieldInfo[F, C]
): Either[String, List[(ast.Field[F, I, ?], MergedFieldInfo[F, C])]]
field: ast.Field[F2, I, ?],
mfa: MergedFieldInfo[F2, C]
): Either[String, List[(ast.Field[F2, I, ?], MergedFieldInfo[F2, C])]]
}
final case class Field[F[_], A](
final case class Field[+F[_], A](
directive: Directive[A],
handler: FieldHandler[F, A]
) extends Position[F, A]
) extends QueryPosition[F, A] with SchemaPosition[F, A]

trait QueryHandler[Struct[_], A] {
def apply[C](a: A, query: Struct[C]): Either[String, List[Struct[C]]]
}
final case class FragmentSpread[A](
directive: Directive[A],
handler: QueryHandler[QA.FragmentSpread, A]
) extends Position[Nothing, A]
) extends QueryPosition[Nothing, A]
final case class InlineFragmentSpread[A](
directive: Directive[A],
handler: QueryHandler[QA.InlineFragment, A]
) extends Position[Nothing, A]
) extends QueryPosition[Nothing, A]

trait SchemaHandler[+F[_], A, Struct[_[_], _]] {
def apply[F2[x] >: F[x], B](a: A, struct: Struct[F2, B]): Either[String, Struct[F2, B]]
}

final case class Schema[+F[_], A, Struct[_[_], _]](
directive: Directive[A],
handler: SchemaHandler[F, A, Struct]
) extends SchemaPosition[F, A]

type PureStruct[Struct[_]] = {
type T[F[_], B] = Struct[B]
}

type PureSchema[A, Struct[_]] = Schema[fs2.Pure, A, PureStruct[Struct]#T]
object PureSchema {
def apply[A, Struct[_]](
directive: Directive[A],
handler: SchemaHandler[fs2.Pure, A, PureStruct[Struct]#T]
): PureSchema[A, Struct] =
Schema(directive, handler)
}
trait PureHandler[A, Struct[_]] extends SchemaHandler[fs2.Pure, A, PureStruct[Struct]#T] {
def pureApply[B](a: A, struct: Struct[B]): Either[String, Struct[B]]

def apply[F2[x] >: Pure[x], B](a: A, struct: Struct[B]): Either[String,Struct[B]] =
pureApply(a, struct)
}

object Schema {
type Enum[A] = PureSchema[A, ast.Enum]
type Scalar[A] = PureSchema[A, ast.Scalar]
type Input[A] = PureSchema[A, ast.Input]
type Interface[+F[_], A] = Schema[F, A, ast.Interface]
type Type[+F[_], A] = Schema[F, A, ast.Type]
type Union[+F[_], A] = Schema[F, A, ast.Union]
}
}
Loading
Loading