def getUser(userId: Int) = new Service[HttpRequest, HttpResponse] = {
def apply(req: HttpRequest): Future[HttpResponse] = {
val n = req.params.get("name").getOrElse("n/a")
val rep = Response(Status.Ok)
rep.setContentTypeJson()
rep.setContentString(s"""{"name":"$n", "id": $id}""")
Future.value(rep)
}
}
def getUser(userId: Int) = new Service[HttpRequest, HttpResponse] = {
def apply(req: HttpRequest): Future[HttpResponse] = {
val n = req.params.get("name").getOrElse("n/a")
val rep = Response(Status.Ok)
rep.setContentTypeJson()
rep.setContentString(s"""{"name":"$n", "id": $id}""")
Future.value(rep)
}
}
def getUser(userId: Int) = new Service[HttpRequest, HttpResponse] = {
def apply(req: HttpRequest): Future[HttpResponse] = {
val n = req.params.get("name").getOrElse("n/a")
val rep = Response(Status.Ok)
rep.setContentTypeJson()
rep.setContentString(s"""{"name":"$n", "id": $id}""")
Future.value(rep)
}
}
val router: Service[HttpRequest, HttpResponse] =
RoutingService.byPathObject {
case Root / "users" / Integer(id) => getUser(id)
// case Root / "tickets" / id => getTicket(id)
}
Http.serve("8080", router)
val router: Service[HttpRequest, HttpResponse] =
RoutingService.byPathObject {
case Root / "users" / Integer(id) => getUser(id)
// case Root / "tickets" / id => getTicket(id)
}
Http.serve("8080", router)
trait Endpoint[A, B] {
def route: PartialFunction[(HttpMethod, Path), Service[A, B]]
def orElse(that: Endpoint[A, B]): Endpoint[A, B] = ???
}
val users = Endpoint[HttpRequest, HttResponse] {
case Get -> Root / "users" => ???
}
val tickets = Endpoint[HttpRequest, HttResponse] {
case Get -> Root / "tickets" => ???
}
val router: Endpoint[HttpRequest, HttpResponse] =
users orElse tickets
trait RequestReader[A] {
def apply(req: HttpRequest): Future[A]
def flatMap[B](fn: A => RequestReader[B]): RequestReader[B]
def map[B](fn: A => B): RequestReader[B]
}
def RequiredParam(name: String): RequestReader[String] = ???
def OptionalParam(name: String): RequestReader[Option[String]] = ???
def RequeredIntParam(name: String): RequestReader[Int] = ???
def OptionalItParam(name: String): RequestReader[Int] = ???
....
case class User(id: Int, name: String)
val user: RequestReader[User] = for {
id <- RequiredIntParam("id")
name <- OptionalParam("name")
} yield User(id, name.getOrElse("n/a"))
val response: Future[HttpResponse] = user(request).flatMap { u =>
// TODO map user to a function User => Future[HttResponse]
}
def ValidationRule(rule: String)(predicate: => Boolean) =
new RequestReader[Unit] {
def apply(req: HttpRequest) =
if (predicate) Future.Done
else Future.exception(ValidationFailed(rule))
}
case class User(age: Int)
val user: RequestReader[User] = RequiredIntParam("age").map(User)
val adult: RequestReader[User] = for {
u <- user
_ <- ValidationRule("age should be greater than 21") { u.age > 21 }
} yield u
case class User(age: Int)
val user: RequestReader[User] = RequiredIntParam("age").map(User)
val adult: RequestReader[User] = for {
u <- user
_ <- ValidationRule("age should be greater than 21") { u.age > 21 }
} yield u
case class ResponseBuilder(status: HttpResponseStatus) {
def apply(plain: String): HttpResponse = ???
def apply(json: JSONType): HttpResponse = ???
def apply(): HttpResponse = ???
}...
object Ok extends ResponseBuilder(Status.Ok)
object Created extends ResponseBuilder(Status.Created)
object NoContent extends ResponseBuilder(Status.NoContent)
...
val ok: HttpResponse = Ok("plain/text")
val notFound: HttpResponse = NotFound(JSONObject(Map("id" -> 100)))
val noContent: HttpResponse = NoContent()
import argonaut._, Argonaut._
val created: HttpResponse = Created(Json.obj("id" -> jNumber(100)))
trait EncodeResponse[-A] {
def apply(rep: A): String
def contentType: String
}
object EncodeResponse {
def apply[A](ct: String)(f: String => A): EncodeResponse[A] = ???
implicit val encodeString: EncodeResponse[String] =
EncodeResponse("plain/text")(s => s)
}
case class ResponseBuilder(status: Status) {
def apply[A: EncodeResponse](body: A): HttpResponse = ???
}
implicit val e: EncodeResponse[JSONType] =
EncodeResponse("application/json")(_.toString)
val rep: HttpResponse =
Ok(JSONObject(Map("id" -> 100, "name" -> "Cheburashka")))
import io.finch.argonaut._, argonaut._, Argonaut._
val a: HttpResponse = Ok(Json.obj("id" -> jNumber(100)))
import io.finch.jackson._
case class User(id: Int, name: String)
val b: HttpResponse = Ok(User(100, "Ivan"))
val RequiredBody: RequestReader[String] = ???
val OptionalBody: RequestReader[Option[String]] = ???
val user: RequestReader[User] = RequiredBody.map { body =>
// TODO parse User from String
}
trait DecodeRequest[+A] {
def apply(req: String): Try[A]
}
object DecodeRequest {
def apply[A](f: String => Try[A]): DecodeRequest[A] = ???
implicit val decodeString: DecodeRequest[String] =
DecodeRequest(s => Return(s))
}
def RequiredBody[A: DecodeRequest]: RequestReader[A] = ???
def OptionalBody[A: DecodeRequest]: RequestReader[Option[A]] = ???
implicit val e: DecodeRequest[JSONType] =
DecodeRequest(Try(JSON.parseFull(_)))
val json = RequiredBody[JSONType]
import io.finch.argonaut._, argonaut._, Argonaut._
val a: RequestReader[Json] = RequiredBody[Json]
import io.finch.jackson._
case class User(id: Int, name: String)
val b: RequestReader[Option[User]] = OptionalBody[User]
implicit val decodeInt: DecodeRequest[Int] =
DecodeRequest { s => Try(s.toInt) }
implicit val decodeLong: DecodeRequest[Long] =
DecodeRequest { s => Try(s.toLong) }
implicit val decodeBoolean: DecodeRequest[Boolean] =
DecodeRequest { s => Try(s.toBoolean) }
...
implicit class RROps[R](val rr: RequestReader[String]) {
def as[A: DecodeRequest]: RequestReader[A] = ???
}
val a = RequiredIntParam("a")
val b = RequiredBooleanParam("b")
val c = RequredBody[Json]
implicit class RROps[R](val rr: RequestReader[String]) {
def as[A: DecodeRequest]: RequestReader[A] = ???
}
val a = RequiredIntParam("a")
val b = RequiredBooleanParam("b")
val c = RequredBody[Json]
implicit class RROps[R](val rr: RequestReader[String]) {
def as[A: DecodeRequest]: RequestReader[A] = ???
}
val a = RequiredParam("a").as[Int]
val b = RequiredParam("b").as[Boolean]
val c = RequredBody.as[Json]
implicit class RROps[R](val rr: RequestReader[String]) {
def as[A: DecodeRequest]: RequestReader[A] = ???
}
val a = param("a").as[Int]
val b = param("b").as[Boolean]
val c = body.as[Json]
case class User(age: Int, name: String, city: Option[String])
val user: RequestReader[User] = for {
age <- param("age").as[Int]
name <- param("name")
city <- paramOption("city").withDefault("San Francisco")
} yield User(age, name, city)
case class ~[A, B](a: A, b: B)
trait RequestReader[A] {
def ~[B](that: RequestReader[B]): RequestReader[A ~ B] = ???
}
case class User(age: Int, name: String, city: Option[String])
val a: RequestReader[Int ~ String ~ String] =
param("age").as[Int] ~
param("name") ~
paramOption("city").withDefault("San Francisco")
val b: RequestReader[User] = a.map {
case age ~ name ~ city => User(age, name, city)
}
case class User(age: Int, name: String, city: Option[String])
val a: RequestReader[Int ~ String ~ String] =
param("age").as[Int] ~
param("name") ~
paramOption("city").withDefault("San Francisco")
val b: RequestReader[User] = a.map {
case age ~ name ~ city => User(age, name, city)
}
case class User(age: Int, name: String, city: Option[String])
val a: RequestReader[Int ~ String ~ String] =
param("age").as[Int] ~
param("name") ~
paramOption("city").withDefault("San Francisco")
val b: RequestReader[User] = a ~> User
Can we treat A ~ B ~ .. ~ Z => \_(ツ)_/
as (A, B, .., Z) => \_(ツ)_/
?
implicit class Arrow26(rr: RequestReader[A ~ B ~ .. ~ Z]) {
def ~>[A0](f: (A, B, .., Z) => A0): RequestReader[A0] =
rr.map {
case a ~ b ~ .. ~ z => f(a, b, ..., z)
}
}
case class /[A, B](a: A, b: B)
trait Router[+A] {
def apply(r: Route): Option[(Route, A)]
def map[B](f: A => B): Router[B] // or />
def andThen[B](that: Router[B]): Router[A / B] // or /
def orElse[B >: A](that: Router[B]): Router[B] // or |
}
object Get extends Router[Nothing] { ... }
object Post extends Router[Nothing] { ... }
object Patch extends Router[Nothing] { ... }
object * extends Router[Nothing] { ... }
...
object int extends Router[Int] { ... }
object string extends Router[String] { ... }
object boolean extends Router[Boolean] { ... }
...
implicit def intToRouter(i: Int): Router[Nothing] = ???
implicit def stringToRouter(s: String): Router[Nothing] = ???
implicit def booleanToRouter(b: Boolean): Router[Nothing] = ???
case class Ticket(id: Int)
val a: Router[Ticket] = Get / "tickets" / 10 /> Ticket(10)
case class User(id: Int)
val a: Router[User] = Get / ("users" | "user") / int("id") /> User
val b: Router[HttpResponse] = * / "health" /> Ok()
val ab: Router[?] = a | b // does not compile
type Endpoint[A, B] = Router[Service[A, B]]
type Api = Endpoint[HttRequest, HttResponse]
val a: Api = Get / "users" / int /> GetUserService
val b: Api = Get / "tickets" / int /> GetTicketsService
val ab: Api = a | b
Router
s (powered by Shapeless)Router
s and RequestReader
s Get / "users" ? param("id")
)RequestReader
s and Router
s into a single abstraction