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

Provide context to multiple routes (#2488) #2553

Closed
wants to merge 2 commits into from

Conversation

987Nabil
Copy link
Contributor

@987Nabil 987Nabil commented Dec 8, 2023

This PR is not good to go, but shows that I found a way how to provide a context to multiple routes without any more explicit typing or extremely strange syntax.

RFC and I happily accept suggestions on the syntax.

/claim #2488

@987Nabil 987Nabil force-pushed the providing-routes-constructor branch from 86c387b to 5dfa064 Compare December 8, 2023 20:02
@987Nabil 987Nabil marked this pull request as draft December 8, 2023 20:05
@codecov-commenter
Copy link

codecov-commenter commented Dec 8, 2023

Codecov Report

Attention: Patch coverage is 48.07692% with 27 lines in your changes are missing coverage. Please review.

Project coverage is 64.41%. Comparing base (b1da91b) to head (957a088).
Report is 6 commits behind head on main.

Files Patch % Lines
...io-http/shared/src/main/scala/zio/http/Route.scala 43.75% 27 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2553      +/-   ##
==========================================
- Coverage   64.56%   64.41%   -0.16%     
==========================================
  Files         148      148              
  Lines        8649     8660      +11     
  Branches     1573     1563      -10     
==========================================
- Hits         5584     5578       -6     
- Misses       3065     3082      +17     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@vigoo
Copy link
Contributor

vigoo commented Dec 13, 2023

Some random thoughts, only by looking at the code, did not play with it:

  • it can be confusing what middleware you have to pass to this Routes constructor an what not (or what happens if you pass something that is not providing any context there? etc)
  • also introducing a new route type with a different operator (partial route / -->) although I understand why, I'm afraid that it makes the API hard - when to use -> and when -->? What if I already have a Route value and want to mix it with partial routes? etc. I'm sure most of these have valid answers and maybe we can make it feel seamless, but it is not clear to me yet

On the other hand, I this proposal solves one special case when you want one context-providing middleware to provide context to a set of routes - which I think is a quite common case so maybe worth to introduce something "special" like this to help that use case.

@987Nabil
Copy link
Contributor Author

987Nabil commented Dec 13, 2023

@vigoo The middleware at the beginning is already the case if you define it per route. If you provide something without a context, nothing happens. You define just the routes like it would not be there.

To the second point, I agree. That is why this is a POC. What I try to proof is, that you can construct routes without a lot of type annotations and still provide a context. The API design / syntax is something different. But I can't imagine there is no way to find a solution for that. How beautiful it will be, I can't tell. But I think we are a step closer to something usable.

@987Nabil
Copy link
Contributor Author

987Nabil commented Jan 6, 2024

@jdegoes ping! You wanted to give me some feedback.

@@ -64,6 +64,13 @@ final case class RoutePattern[A](method: Method, pathCodec: PathCodec[A]) { self
): Route[Env, Err] =
Route.route(Route.Builder(self, HandlerAspect.identity))(handler)(zippable.zippable, trace)

def -->[Env, Err, I, Ctx, In](handler: Handler[Env, Err, I, Response])(implicit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are going to use --> for this, then we should make sure to be consistent across all of ZIO HTTP.

In addition: We should give this a named variant too, rather than just the symbolic operator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you be more specific on being consistent with what? I don't even say we should use it, but it was not possible to simply overload ->

@987Nabil 987Nabil force-pushed the providing-routes-constructor branch 3 times, most recently from cd791d9 to 456a8cb Compare February 25, 2024 06:47
@987Nabil 987Nabil force-pushed the providing-routes-constructor branch from 456a8cb to 957a088 Compare February 25, 2024 07:27
@987Nabil
Copy link
Contributor Author

987Nabil commented Feb 25, 2024

@jdegoes I took a look again after letting intentionally some time pass. Which was good, since I managed to remove the new Route type.

I did this by restructuring the code for the Route.Builder. Instead of taking RoutePattern and HandlerAspect to build a route later on with a Handler, it takes now a RoutePattern and a Handler to build a route with a HandlerAspect.
This leads to the fact that the pattern -> aspect -> handler syntax is gone and is now instead pattern -> handler @@ aspect.

I provided a method currently called Routes.build

def build[Env, Err, Ctx](
    route: Route.Builder[Env, _, Ctx, _, Err],
    routes: Route.Builder[Env, _, Ctx, _, Err]*,
  ): Routes.Builder[Env, Ctx, Err] =
    Routes.Builder[Env, Ctx, Err](route +: routes)

And added a builder

  case class Builder[Env, Ctx, Err](routes: Seq[Route.Builder[Env, _, Ctx, _, Err]]){
   def @@(aspect: HandlerAspect[Env, Ctx]): Routes[Env, Err] =
     new Routes(Chunk.fromIterable(routes.map(_ @@ aspect)))
 }

Which leads to the syntax

Routes.build(
         Method.GET / "a"                  -> handler((ctx: AuthContext, _: Request) => Response.text(ctx.value)),
         Method.GET / "b" / int("id")      -> handler((id: Int, ctx: AuthContext, _: Request) =>
           Response.text(s"for id: $id: ${ctx.value}"),
         ),
         Method.GET / "c" / string("name") -> handler((name: String, ctx: AuthContext, _: Request) =>
           Response.text(s"for name: $name: ${ctx.value}"),
         ),
       ) @@ basicAuthContextM

Now we have basically two choices:

We leave the code structure wise as it is right now

Which means constructing routes inside of the two constructors Routes.apply and Routes.build works just fine. You can use for both pattern -> handler with the same requirements as on the main branch, which means explicit types for the handler input.
What does not work anymore is

val route = Method.GET / "endpoint" -> handler { (_: Request) =>
  ZIO.fail(new Exception("hmm..."))
}

This would need an explicit type to resolve to the right version of ->

val route: Route[Any, Exception] = Method.GET / "endpoint" -> handler { (_: Request) =>
  ZIO.fail(new Exception("hmm..."))
}
val route: Route.Builder[Any, (Int, AuthContext), AuthContext, (Int, AuthContext, Request), Nothing] = Method.GET / "b" / int("id") ->
  handler((id: Int, ctx: AuthContext, _: Request) =>  Response.text(s"for id: $id: ${ctx.value}"),
)

Have two different operators
Constructing routes will use -> for no context and another operator (maybe --> like in the prev version) for handlers with context.

Maybe the first solution could be more attractive, if we would have a way of simplifying the type parameters of Route.Builder[-Env, PC, Ctx, In, +Err] ?

Anyhow, I am sure this is a step forward overall.

@adamgfraser @vigoo Maybe you have some thoughts on this too.

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

Successfully merging this pull request may close these issues.

4 participants