Skip to content

Commit

Permalink
Continue refactoring Middleware (zio#1642)
Browse files Browse the repository at this point in the history
* Add handle method in MiddlwareSpec

* Add toServiceSpec in API and handle method in Middleware trying out the spec usage

* Fix formatting and compilation

* Fix compile error

* Rename to implement for middlewarespec

* Fix compile errors in examples

* Fix compile time errors in example

* Try to implement toServiceSpec

* Implement combine

* More changes to Middleware

* Fix compile time errors for status

* Reformat core

* Format benchmarks

* Fix incoming of middleware

* Fix the need of asInstanceOf

* Fix peekrequest

* Make sure toHttpMiddleware works
  • Loading branch information
afsalthaj authored Oct 18, 2022
1 parent adddc66 commit f4d57df
Show file tree
Hide file tree
Showing 19 changed files with 364 additions and 160 deletions.
4 changes: 2 additions & 2 deletions project/Debug.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object Debug {
/**
* Sets the main application to execute in the example project.
*/
//val Main = "zio.http.api.APIExamples"
// val Main = "zio.http.api.APIExamples"
val Main = "zio.http.api.BasicAuthAPIExample"
//val Main = "example.BasicAuth"
// val Main = "example.BasicAuth"
}
2 changes: 1 addition & 1 deletion project/metals.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

// This file enables sbt-bloop to create bloop config files.

addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.3")
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.4")

Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class ApiBenchmark {
.out[ExampleData]

val handledUsersPosts =
usersPosts.handle { case (userId, postId, limit) =>
usersPosts.implement { case (userId, postId, limit) =>
ZIO.succeed(ExampleData(userId, postId, limit))
}

Expand Down Expand Up @@ -211,7 +211,7 @@ class ApiBenchmark {
RouteCodec.literal("first") /
RouteCodec.int / "second" / RouteCodec.int / "third" / RouteCodec.int / "fourth" / RouteCodec.int / "fifth" / RouteCodec.int / "sixth" / RouteCodec.int / "seventh" / RouteCodec.int,
)
.handle { _ =>
.implement { _ =>
ZIO.unit
}
.toHttpApp
Expand Down Expand Up @@ -325,57 +325,58 @@ class ApiBenchmark {

// API DSL

val broadUsers = API.get(RouteCodec.literal("users")).handle { _ => ZIO.unit }
val broadUsersId = API.get(RouteCodec.literal("users") / RouteCodec.int).handle { _ => ZIO.unit }
val boardUsersPosts =
API.get(RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("posts")).handle { _ => ZIO.unit }
val boardUsersPostsId =
API.get(RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("posts") / RouteCodec.int).handle { _ =>
ZIO.unit
val broadUsers = API.get(RouteCodec.literal("users")).implement { _ => ZIO.unit }
val broadUsersId = API.get(RouteCodec.literal("users") / RouteCodec.int).implement { _ => ZIO.unit }
val boardUsersPosts =
API.get(RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("posts")).implement { _ => ZIO.unit }
val boardUsersPostsId =
API.get(RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("posts") / RouteCodec.int).implement {
_ =>
ZIO.unit
}
val boardUsersPostsComments =
val boardUsersPostsComments =
API
.get(
RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("posts") / RouteCodec.int / RouteCodec
.literal("comments"),
)
.handle { _ =>
.implement { _ =>
ZIO.unit
}
val boardUsersPostsCommentsId =
val boardUsersPostsCommentsId =
API
.get(
RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("posts") / RouteCodec.int / RouteCodec
.literal("comments") / RouteCodec.int,
)
.handle { _ =>
.implement { _ =>
ZIO.unit
}
val broadPosts = API.get(RouteCodec.literal("posts")).handle { _ => ZIO.unit }
val broadPostsId = API.get(RouteCodec.literal("posts") / RouteCodec.int).handle { _ => ZIO.unit }
val boardPostsComments =
API.get(RouteCodec.literal("posts") / RouteCodec.int / RouteCodec.literal("comments")).handle { _ => ZIO.unit }
val boardPostsCommentsId =
API.get(RouteCodec.literal("posts") / RouteCodec.int / RouteCodec.literal("comments") / RouteCodec.int).handle {
val broadPosts = API.get(RouteCodec.literal("posts")).implement { _ => ZIO.unit }
val broadPostsId = API.get(RouteCodec.literal("posts") / RouteCodec.int).implement { _ => ZIO.unit }
val boardPostsComments =
API.get(RouteCodec.literal("posts") / RouteCodec.int / RouteCodec.literal("comments")).implement { _ => ZIO.unit }
val boardPostsCommentsId =
API.get(RouteCodec.literal("posts") / RouteCodec.int / RouteCodec.literal("comments") / RouteCodec.int).implement {
_ => ZIO.unit
}
val broadComments = API.get(RouteCodec.literal("comments")).handle { _ => ZIO.unit }
val broadCommentsId = API.get(RouteCodec.literal("comments") / RouteCodec.int).handle { _ => ZIO.unit }
val broadUsersComments =
API.get(RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("comments")).handle { _ => ZIO.unit }
val broadUsersCommentsId =
API.get(RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("comments") / RouteCodec.int).handle {
val broadComments = API.get(RouteCodec.literal("comments")).implement { _ => ZIO.unit }
val broadCommentsId = API.get(RouteCodec.literal("comments") / RouteCodec.int).implement { _ => ZIO.unit }
val broadUsersComments =
API.get(RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("comments")).implement { _ => ZIO.unit }
val broadUsersCommentsId =
API.get(RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("comments") / RouteCodec.int).implement {
_ => ZIO.unit
}
val boardUsersPostsCommentsReplies =
val boardUsersPostsCommentsReplies =
API
.get(
RouteCodec.literal("users") / RouteCodec.int / RouteCodec.literal("posts") / RouteCodec.int / RouteCodec
.literal("comments") / RouteCodec.int / RouteCodec.literal(
"replies",
),
)
.handle { _ =>
.implement { _ =>
ZIO.unit
}
val boardUsersPostsCommentsRepliesId =
Expand All @@ -386,7 +387,7 @@ class ApiBenchmark {
"replies",
) / RouteCodec.int,
)
.handle { _ =>
.implement { _ =>
ZIO.unit
}

Expand Down
15 changes: 11 additions & 4 deletions zio-http-example/src/main/scala/example/APIExamples.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ object APIExamples extends ZIOAppDefault {
API.get(literal("users") / int).out[Int]

val getUsersService =
getUser.handle[Any, Nothing] { case (id: Int) =>
getUser.implement[Any, Nothing] { case (id: Int) =>
ZIO.succeedNow(id)
}

Expand All @@ -23,13 +23,20 @@ object APIExamples extends ZIOAppDefault {
.in(query("name"))

val getUserPostsService =
getUserPosts.handle[Any, Nothing] { case (id1, query, id2) =>
getUserPosts.implement[Any, Nothing] { case (id1, query, id2) =>
ZIO.debug(s"API2 RESULT parsed: users/$id1/posts/$id2?name=$query")
}

val serviceSpec = (getUser ++ getUserPosts).middleware(MiddlewareSpec.auth)
val middleware =
MiddlewareSpec.auth

val app = serviceSpec.toHttpApp(getUsersService ++ getUserPostsService, Middleware.fromFunction(_ => ()))
// just like api.handle
val middlewareImpl =
middleware.implement(_ => ZIO.unit)

val serviceSpec = (getUser ++ getUserPosts).middleware(middleware)

val app = serviceSpec.toHttpApp(getUsersService ++ getUserPostsService, middlewareImpl)

val request = Request.get(url = URL.fromString("/users/1").toOption.get)
println(s"Looking up $request")
Expand Down
27 changes: 24 additions & 3 deletions zio-http-example/src/main/scala/example/BasicAuthAPIExample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package zio.http.api

import zio._
import zio.http._
import zio.http.middleware.Auth

object BasicAuthAPIExample extends ZIOAppDefault {

Expand All @@ -11,12 +12,32 @@ object BasicAuthAPIExample extends ZIOAppDefault {
val getUser =
API.get(literal("users") / int).out[Int]

val getUsersService =
getUser.handle[Any, Nothing] { case (id: Int) =>
val getUserImpl =
getUser.implement { case (id: Int) =>
ZIO.succeed(id)
}

val app = getUsersService.toHttpApp
val authMiddleware = MiddlewareSpec.auth
val correlationId = MiddlewareSpec.addCorrelationId

val middleware: MiddlewareSpec[Auth.Credentials, String] =
MiddlewareSpec.auth ++ MiddlewareSpec.addCorrelationId

val authMiddlewareHandler: api.Middleware[Any, Nothing, Auth.Credentials, Unit] =
authMiddleware.implement(_ => ZIO.unit)

val correlationIdHandler: api.Middleware[Any, Nothing, Unit, String] =
correlationId.implement(_ => ZIO.succeed("xyz"))

val middlewareImpl: api.Middleware[Any, Nothing, Auth.Credentials, String] = {
// FIXME: Discuss Can also be implemented through `middleware.implement(cred => ZIO.succeed(cred.uname))` in
// which correlation id becomes username. Do we support this?
authMiddlewareHandler ++ correlationIdHandler
}

val serviceSpec = getUser.toServiceSpec.middleware(middleware)

val app = serviceSpec.toHttpApp(getUserImpl, middlewareImpl)

val run = Server.serve(app).provide(Server.default)

Expand Down
38 changes: 27 additions & 11 deletions zio-http/src/main/scala/zio/http/api/API.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package zio.http.api

import zio._
import zio.http.api.CodecType.Route
import zio.http.model.{Header, Headers, Method}
import zio.schema._
import zio.stream.ZStream
Expand All @@ -23,17 +24,20 @@ import zio.stacktracer.TracingImplicits.disableAutoTrace // scalafix:ok;
* client libraries in other programming languages.
*/
final case class API[Input, Output](
method: Method,
input: HttpCodec[CodecType.Route with CodecType.Header with CodecType.Body with CodecType.Query, Input],
output: BodyCodec[Output],
input: HttpCodec[
CodecType.RequestType,
Input,
],
output: HttpCodec[CodecType.ResponseType, Output],
doc: Doc,
) { self =>
type Id

/**
* Combines this API with another API.
*/
def ++(that: API[_, _]): ServiceSpec[Unit, Unit, Id with that.Id] = ServiceSpec(self).++[that.Id](ServiceSpec(that))
def ++(that: API[_, _]): ServiceSpec[Unit, Unit, Id with that.Id] =
ServiceSpec(self).++[that.Id](ServiceSpec(that))

def apply(input: Input): Invocation[Id, Input, Output] =
Invocation(self, input)
Expand Down Expand Up @@ -90,7 +94,7 @@ final case class API[Input, Output](
* convert an API into a service, you must specify a function which handles
* the input, and returns the output.
*/
def handle[R, E](f: Input => ZIO[R, E, Output]): Service[R, E, Id] =
def implement[R, E](f: Input => ZIO[R, E, Output]): Service[R, E, Id] =
Service.HandledAPI[R, E, Input, Output, Id](self, f).withAllIds[Id]

/**
Expand All @@ -109,18 +113,29 @@ final case class API[Input, Output](
* headers of the request.
*/
def in[Input2](
in2: HttpCodec[CodecType.Route with CodecType.Header with CodecType.Body with CodecType.Query, Input2],
in2: HttpCodec[CodecType.RequestType, Input2],
)(implicit
combiner: Combiner[Input, Input2],
): API.WithId[combiner.Out, Output, Id] =
copy(input = self.input ++ in2).withId[Id]

/**
* Convert API to a ServiceSpec.
*/
def toServiceSpec: ServiceSpec[Unit, Unit, Id] =
ServiceSpec(self).middleware(MiddlewareSpec.none)

/**
* Changes the output type of the endpoint to the specified output type.
*/
def out[Output2: Schema]: API.WithId[Input, Output2, Id] =
copy(output = HttpCodec.Body(implicitly[Schema[Output2]])).withId[Id]

def out[Output2](out2: HttpCodec[CodecType.ResponseType, Output2])(implicit
combiner: Combiner[Output, Output2],
): API.WithId[Input, combiner.Out, Id] =
copy(output = output ++ out2).withId[Id]

/**
* Changes the output type of the endpoint to be a stream of the specified
* output type.
Expand All @@ -141,8 +156,9 @@ object API {
* `API#in` method can be used to incrementally append additional input to the
* definition of the API.
*/
def delete[Input](route: RouteCodec[Input]): API[Input, Unit] =
API(Method.DELETE, route, HttpCodec.empty, Doc.empty)
def delete[Input](route: RouteCodec[Input]): API[Input, Unit] = {
API(route ++ MethodCodec.delete, HttpCodec.empty, Doc.empty)
}

/**
* Constructs an API for a GET endpoint, given the specified input. It is not
Expand All @@ -151,7 +167,7 @@ object API {
* definition of the API.
*/
def get[Input](route: RouteCodec[Input]): API[Input, Unit] =
API(Method.GET, route, HttpCodec.empty, Doc.empty)
API(route ++ MethodCodec.get, HttpCodec.empty, Doc.empty)

/**
* Constructs an API for a POST endpoint, given the specified input. It is not
Expand All @@ -160,7 +176,7 @@ object API {
* definition of the API.
*/
def post[Input](route: RouteCodec[Input]): API[Input, Unit] =
API(Method.POST, route, HttpCodec.empty, Doc.empty)
API(route ++ MethodCodec.post, HttpCodec.empty, Doc.empty)

/**
* Constructs an API for a PUT endpoint, given the specified input. It is not
Expand All @@ -169,5 +185,5 @@ object API {
* definition of the API.
*/
def put[Input](route: RouteCodec[Input]): API[Input, Unit] =
API(Method.PUT, route, HttpCodec.empty, Doc.empty)
API(route ++ MethodCodec.put, HttpCodec.empty, Doc.empty)
}
6 changes: 6 additions & 0 deletions zio-http/src/main/scala/zio/http/api/CodecType.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package zio.http.api

sealed trait CodecType

object CodecType {
type RequestType <: Route with Body with Query with Header with Method
type ResponseType <: Body with Header with Status

type Route <: CodecType
type Body <: CodecType
type Query <: CodecType
type Header <: CodecType
type Method <: CodecType
type Status <: CodecType
}
17 changes: 13 additions & 4 deletions zio-http/src/main/scala/zio/http/api/HttpCodec.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package zio.http.api

import scala.language.implicitConversions
import zio.http.URL

import scala.language.implicitConversions
import zio.stream.ZStream
import zio.schema.Schema
import zio.stacktracer.TracingImplicits.disableAutoTrace // scalafix:ok;
Expand Down Expand Up @@ -55,6 +56,12 @@ sealed trait HttpCodec[-AtomTypes, Input] {
def transform[Input2](f: Input => Input2, g: Input2 => Input): HttpCodec[AtomTypes, Input2] =
HttpCodec.TransformOrFail[AtomTypes, Input, Input2](self, in => Right(f(in)), output => Right(g(output)))

def transformOrFail[Input2](
f: Input => Either[String, Input2],
g: Input2 => Either[String, Input],
): HttpCodec[AtomTypes, Input2] =
HttpCodec.TransformOrFail[AtomTypes, Input, Input2](self, f, g)

def transformOrFailLeft[Input2](
f: Input => Either[String, Input2],
g: Input2 => Input,
Expand All @@ -77,12 +84,14 @@ object HttpCodec extends HeaderCodecs with QueryCodecs with RouteCodecs {

private[api] sealed trait Atom[-AtomTypes, Input0] extends HttpCodec[AtomTypes, Input0]

private[api] case object Empty extends Atom[Any, Unit]
private[api] final case class Route[A](textCodec: TextCodec[A]) extends Atom[CodecType.Route, A]
private[api] final case class Body[A](input: Schema[A]) extends Atom[CodecType.Body, A]
private[api] case object Empty extends Atom[Any, Unit]
private[api] final case class Status[A](textCodec: TextCodec[A]) extends Atom[CodecType.Status, A]
private[api] final case class Route[A](textCodec: TextCodec[A]) extends Atom[CodecType.Route, A]
private[api] final case class Body[A](input: Schema[A]) extends Atom[CodecType.Body, A]
private[api] final case class BodyStream[A](element: Schema[A])
extends Atom[CodecType.Body, ZStream[Any, Throwable, A]] // and delete Out
private[api] final case class Query[A](name: String, textCodec: TextCodec[A]) extends Atom[CodecType.Query, A]
private[api] final case class Method[A](methodCodec: TextCodec[A]) extends Atom[CodecType.Method, A]
private[api] final case class Header[A](name: String, textCodec: TextCodec[A]) extends Atom[CodecType.Header, A]
private[api] final case class IndexedAtom[AtomType, A](atom: Atom[AtomType, A], index: Int) extends Atom[AtomType, A]
private[api] final case class WithDoc[AtomType, A](in: HttpCodec[AtomType, A], doc: Doc)
Expand Down
3 changes: 3 additions & 0 deletions zio-http/src/main/scala/zio/http/api/MethodCodec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package zio.http.api

object MethodCodec extends MethodCodecs
14 changes: 14 additions & 0 deletions zio-http/src/main/scala/zio/http/api/MethodCodecs.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package zio.http.api

import zio.http.api.CodecType.Method

private[api] trait MethodCodecs {
def method(method: zio.http.model.Method): HttpCodec[CodecType.Method, Unit] =
HttpCodec.Method(TextCodec.constant(method.toString()))

def get: HttpCodec[Method, Unit] = method(zio.http.model.Method.GET)
def put: HttpCodec[Method, Unit] = method(zio.http.model.Method.PUT)
def post: HttpCodec[Method, Unit] = method(zio.http.model.Method.POST)
def delete: HttpCodec[Method, Unit] = method(zio.http.model.Method.DELETE)

}
Loading

0 comments on commit f4d57df

Please sign in to comment.