Skip to content

Instantly share code, notes, and snippets.

@TomTriple
Created October 23, 2023 17:34
Show Gist options
  • Save TomTriple/398b8c2641790bc712209396e7f5552e to your computer and use it in GitHub Desktop.
Save TomTriple/398b8c2641790bc712209396e7f5552e to your computer and use it in GitHub Desktop.
QueryParam.scala
package zio.http
import zio.ZIO
sealed trait QueryParam[+A] { self =>
def flatMap[B](f: A => QueryParam[B]):QueryParam[B] = QueryParam.FlatMap(self, f)
def map[B](f: A => B):QueryParam[B] = self.flatMap(a => QueryParam.succeed(f(a)))
def zipWith[B, C](that: => QueryParam[B])(f: (A, B) => C):QueryParam[C] =
for {
a <- self
b <- that
} yield f(a, b)
def <*>[B](that: => QueryParam[B]):QueryParam[(A, B)] = self.zip(that)
def zip[B](that: => QueryParam[B]):QueryParam[(A, B)] = self.zipWith(that)((a, b) => a -> b)
def zipString(name:String):QueryParam[(A, String)] = QueryParam.zipAll(self, QueryParam.string(name))
def zipInt(name:String):QueryParam[(A, Int)] = QueryParam.zipAll(self, QueryParam.int(name))
def run(request: Request):Either[String, A] = QueryParam.run(self, request)
def runZIO(request: Request): ZIO[Any, String, A] = QueryParam.runZIO(self, request)
}
object QueryParam {
private case class Succeed[A](value:A) extends QueryParam[A]
private case class Attempt[A](name:String, f: String => Either[String, A]) extends QueryParam[A]
private case class Fail(message:String) extends QueryParam[Nothing]
private case class FlatMap[A, B](self: QueryParam[A], f: A => QueryParam[B]) extends QueryParam[B]
private def succeed[A](value:A):QueryParam[A] = Succeed(value)
def attempt[A](name:String, f: String => Either[String, A]):QueryParam[A] = Attempt(name, f)
def string(name:String):QueryParam[String] = attempt(name, in => Right(in))
def fromEither[A](e: Either[String, A]): QueryParam[A] = e match {
case Left(value) => fail(value)
case Right(value) => succeed(value)
}
def zipAll[A, B](a: QueryParam[A], b: QueryParam[B]): QueryParam[(A, B)] =
a.zipWith(b) { case (a, b) => (a, b)}
def zipAllWith[A, B, C](a: QueryParam[A], b: QueryParam[B])(f: (A, B) => C): QueryParam[C] =
a.zipWith(b) { case (a, b) => f(a, b)}
def zipAll[A, B, C](a: QueryParam[A], b: QueryParam[B], c: QueryParam[C]):QueryParam[(A, B, C)] = {
zipAll(a, b).zip(c).map { case ((a, b), c) => (a, b, c) }
}
def zipAllWith[A, B, C, D](a: QueryParam[A], b: QueryParam[B], c: QueryParam[C])(f: (A, B, C) => D): QueryParam[D] = {
zipAll(a, b).zipWith(c) { case ((a, b), c) => f(a, b, c) }
}
def fail(message:String):QueryParam[Nothing] = Fail(message)
def int(name:String):QueryParam[Int] =
string(name).flatMap(in => fromEither(in.toIntOption.toRight(s"not an int: $in")))
def run[A](self:QueryParam[A], request: Request):Either[String, A] = {
self match {
case Succeed(value) => Right(value)
case Fail(value) => Left(value)
case FlatMap(self, f) =>
run(self, request) match {
case Left(value) => Left(value)
case Right(value) =>
run(f(value), request)
}
}
}
def runZIO[A](self: QueryParam[A], request: Request): ZIO[Any, String, A] =
ZIO.from(run(self, request))
}
@TomTriple
Copy link
Author

Would result in an API like:

    case class Person(name:String, age:Int, height:Int)

    QueryParam.string("name").zipInt("age").zipInt("height")

    QueryParam.zipAllWith(
      QueryParam.string("name"),
      QueryParam.int("age"),
      QueryParam.int("height")
    )(Person.apply)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment