-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: add basic capability to encode a GraphQL program and generat…
…e an execution plan
- Loading branch information
1 parent
74731ed
commit ebf3768
Showing
9 changed files
with
163 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
rules = [ | ||
RemoveUnused | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.1") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.tusharmath.compose | ||
import zio.schema.{DeriveSchema, DynamicValue} | ||
|
||
sealed trait ExecutionPlan {} | ||
|
||
object ExecutionPlan { | ||
|
||
implicit val schema = DeriveSchema.gen[ExecutionPlan] | ||
|
||
def fromGraphQL[A, B](graphQL: GraphQL[A, B]): ExecutionPlan = | ||
graphQL match { | ||
case GraphQL.Pipe(f, g) => Sequence(fromGraphQL(f), fromGraphQL(g)) | ||
case GraphQL.Zip2(g, f) => Combine(fromGraphQL(g), fromGraphQL(f)) | ||
case GraphQL.FromMap(i, source, o) => Dictionary(source.map { case (k, v) => (i.toDynamic(k), o.toDynamic(v)) }) | ||
case GraphQL.Select(input, path, output) => Select(path) | ||
case GraphQL.Constant(b, schema) => Constant(schema.toDynamic(b)) | ||
case GraphQL.Identity() => Identity | ||
} | ||
|
||
case class Constant(value: DynamicValue) extends ExecutionPlan | ||
case class Combine(left: ExecutionPlan, right: ExecutionPlan) extends ExecutionPlan | ||
case class Sequence(first: ExecutionPlan, second: ExecutionPlan) extends ExecutionPlan | ||
case class Dictionary(value: Map[DynamicValue, DynamicValue]) extends ExecutionPlan | ||
case class Select(path: List[String]) extends ExecutionPlan | ||
case object Identity extends ExecutionPlan | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,61 @@ | ||
package com.tusharmath.compose | ||
|
||
import zio.schema.Schema | ||
import com.tusharmath.compose.GraphQL.{Pipe, Zip2} | ||
import zio.schema.{AccessorBuilder, DynamicValue, Schema} | ||
import zio.prelude.NonEmptyList | ||
|
||
sealed trait GraphQL[-A, +B] {} | ||
sealed trait GraphQL[A, B] { self => | ||
def <<<[X](other: GraphQL[X, A]): GraphQL[X, B] = self compose other | ||
def >>>[C](other: GraphQL[B, C]): GraphQL[A, C] = self pipe other | ||
|
||
def compose[X](other: GraphQL[X, A]): GraphQL[X, B] = Pipe(other, self) | ||
def pipe[C](other: GraphQL[B, C]): GraphQL[A, C] = Pipe(self, other) | ||
def zip[C](other: GraphQL[A, C]): GraphQL[A, (B, C)] = Zip2(self, other) | ||
def &&[C](other: GraphQL[A, C]): GraphQL[A, (B, C)] = self zip other | ||
} | ||
|
||
object GraphQL { | ||
case class Constant[B](b: B, schema: Schema[B]) extends GraphQL[Any, B] | ||
case class Identity[A](schema: Schema[A]) extends GraphQL[A, A] | ||
case class Compose[A, B, C](g: GraphQL[B, C], f: GraphQL[A, B]) extends GraphQL[A, C] | ||
case class Zip2[A, B, C](g: GraphQL[A, B], f: GraphQL[A, C]) extends GraphQL[A, (B, C)] | ||
case class Load[A](endpoint: Endpoint, schema: Schema[A]) extends GraphQL[Any, A] | ||
def constant[B](a: B)(implicit schema: Schema[B]): GraphQL[Unit, B] = Constant(a, schema) | ||
|
||
def fromMap[A, B](source: Map[A, B])(implicit input: Schema[A], output: Schema[B]): GraphQL[A, B] = | ||
FromMap(input, source, output) | ||
|
||
final case class FromMap[A, B](input: Schema[A], source: Map[A, B], output: Schema[B]) extends GraphQL[A, B] | ||
final case class Constant[B](b: B, schema: Schema[B]) extends GraphQL[Unit, B] | ||
final case class Identity[A]() extends GraphQL[A, A] | ||
final case class Pipe[A, B, C](f: GraphQL[A, B], g: GraphQL[B, C]) extends GraphQL[A, C] | ||
final case class Zip2[A, B, C](g: GraphQL[A, B], f: GraphQL[A, C]) extends GraphQL[A, (B, C)] | ||
final case class Select[A, B](input: Schema[A], path: NonEmptyList[String], output: Schema[B]) extends GraphQL[A, B] | ||
|
||
def execute[A, B](graphQL: GraphQL[A, B])(a: A): Either[String, B] = | ||
graphQL match { | ||
case Constant(b, _) => Right(b) | ||
case Identity() => Right(a.asInstanceOf[B]) | ||
case Pipe(f, g) => execute(f)(a).flatMap(execute(g)) | ||
case FromMap(_, map, _) => map.get(a).toRight("No value found for " + a) | ||
case Zip2(g, f) => | ||
for { | ||
a <- execute(g)(a) | ||
b <- execute(f)(a) | ||
} yield (a, b) | ||
|
||
case Select(input, path, output) => | ||
input.toDynamic(a) match { | ||
case record @ DynamicValue.Record(_) => Left("TODO") | ||
case _ => Left(s"Cannot select field}") | ||
} | ||
} | ||
|
||
object Accessors extends AccessorBuilder { | ||
override type Lens[S, A] = GraphQL[S, A] | ||
override type Prism[S, A] = Unit | ||
override type Traversal[S, A] = Unit | ||
|
||
override def makeLens[S, A](product: Schema.Record[S], term: Schema.Field[A]): Lens[S, A] = | ||
GraphQL.Select(product, NonEmptyList(term.label), term.schema) | ||
|
||
override def makePrism[S, A](sum: Schema.Enum[S], term: Schema.Case[A, S]): Prism[S, A] = () | ||
|
||
def constant[A](a: A)(implicit schema: Schema[A]): GraphQL[Any, A] = Constant(a, schema) | ||
override def makeTraversal[S, A](collection: Schema.Collection[S, A], element: Schema[A]): Traversal[S, A] = () | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,67 @@ | ||
package com.tusharmath.compose | ||
|
||
import zio.schema.DeriveSchema | ||
import zio.schema.codec.JsonCodec | ||
|
||
object Main extends App { | ||
def hello(): Unit = { | ||
println("Hello!") | ||
|
||
case class Round(name: String, id: Round.Id) | ||
object Round { | ||
case class Id(id: Long) | ||
implicit val schema = DeriveSchema.gen[Round] | ||
implicit val matchId = DeriveSchema.gen[Id] | ||
val accessors = schema.makeAccessors(GraphQL.Accessors) | ||
} | ||
|
||
case class Contest( | ||
name: String, | ||
id: Contest.Id, | ||
entryFee: Double, | ||
size: Long, | ||
// users: List[User.Id], | ||
roundId: Round.Id, | ||
) | ||
|
||
object Contest { | ||
|
||
case class Id(id: Long) | ||
|
||
implicit val schema = DeriveSchema.gen[Contest] | ||
implicit val contestId = DeriveSchema.gen[Id] | ||
|
||
val (name, id, entryFee, size, roundId) = schema.makeAccessors(GraphQL.Accessors) | ||
} | ||
|
||
case class User(name: String, id: User.Id, age: Int) | ||
object User { | ||
case class Id(id: Long) | ||
|
||
implicit val schema = DeriveSchema.gen[User] | ||
implicit val userId = DeriveSchema.gen[Id] | ||
|
||
val (name, id, age) = schema.makeAccessors(GraphQL.Accessors) | ||
} | ||
|
||
val getUser1 = GraphQL.constant(User("Tushar Mathur", User.Id(1), 30)) | ||
|
||
val getUser2 = GraphQL.constant(User("Aiswarya Prakasan", User.Id(2), 90)) | ||
|
||
val getRound = GraphQL.fromMap { | ||
Map( | ||
Round.Id(1) -> Round("Round 1", Round.Id(1)), | ||
Round.Id(2) -> Round("Round 2", Round.Id(2)), | ||
Round.Id(3) -> Round("Round 3", Round.Id(3)), | ||
) | ||
} | ||
|
||
val getContest = GraphQL.constant(Contest("Contest 1", Contest.Id(1), 100.0, 10, Round.Id(1))) | ||
|
||
// From contest prepare round details | ||
val program = getContest >>> Contest.roundId >>> getRound && getContest | ||
|
||
val plan = ExecutionPlan.fromGraphQL(program) | ||
|
||
val encoded = new String(JsonCodec.encode(ExecutionPlan.schema)(plan).toArray) | ||
|
||
println("Encoded: " + encoded) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters