-
-
Notifications
You must be signed in to change notification settings - Fork 59
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
[RFC] Parameterize context type of decorators #137
Conversation
@@ -14,10 +15,10 @@ import cask.model.{Request, Response} | |||
* to `wrapFunction`, which takes a `Map` representing any additional argument | |||
* lists (if any). | |||
*/ | |||
trait Decorator[OuterReturned, InnerReturned, Input] extends scala.annotation.Annotation { | |||
trait Decorator[OuterReturned, InnerReturned, Input, InputContext] extends scala.annotation.Annotation { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the main change here, along with adding an extra parameter to delegate
and changing invocation to accommodate these changes
package app | ||
|
||
case class Context( | ||
session: Session | ||
) | ||
|
||
case class Session(data: collection.mutable.Map[String, String]) | ||
|
||
trait CustomParser[T] extends cask.router.ArgReader[Any, T, Context] | ||
object CustomParser: | ||
given CustomParser[Context] with | ||
def arity = 0 | ||
def read(ctx: Context, label: String, input: Any): Context = ctx | ||
given CustomParser[Session] with | ||
def arity = 0 | ||
def read(ctx: Context, label: String, input: Any): Session = ctx.session | ||
given literal[Literal]: CustomParser[Literal] with | ||
def arity = 1 | ||
def read(ctx: Context, label: String, input: Any): Literal = input.asInstanceOf[Literal] | ||
|
||
object DecoratedContext extends cask.MainRoutes{ | ||
|
||
class custom extends cask.router.Decorator[cask.Response.Raw, cask.Response.Raw, Any, Context]{ | ||
|
||
override type InputParser[T] = CustomParser[T] | ||
|
||
def wrapFunction(req: cask.Request, delegate: Delegate) = { | ||
// Create a custom context out of the request. Custom contexts are useful | ||
// to group an expensive operation that may be used by multiple | ||
// parameter readers or that carry state. This example focuses on carrying | ||
// state. | ||
val ctx = Context(Session(collection.mutable.Map.empty)) // this would typically be populated from a signed cookie | ||
|
||
delegate(ctx, Map("user" -> 1337)).map{ response => | ||
val extraCookies = ctx.session.data.map( | ||
(k, v) => cask.Cookie(k, v) | ||
) | ||
|
||
response.copy( | ||
cookies = response.cookies ++ extraCookies | ||
) | ||
} | ||
|
||
} | ||
} | ||
|
||
@custom() | ||
@cask.get("/hello/:world") | ||
def hello(world: String, req: cask.Request)(session: Session, user: Int) = { | ||
session.data("hello") = "world" | ||
world + user | ||
} | ||
|
||
initialize() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See this as a motivating example
I think this looks reasonable |
7253b61
to
cbfe275
Compare
a4e9441
to
1f7549c
Compare
@lihaoyi, I ported the changes to also work with scala 2, and included the example in the published ones. Let me know if you think this is now good to merge. |
@jodersky looks good, you have commit access so go ahead and merge this and the other PRs when you're happy and tag a release |
This allows customizing the way by-type parameters can be read: instead of always reading data from a
cask.Request
, this allows a decorator to parameterize the context type and pass it in explicitly from thewrapFunction
method.Essentially, this makes the context parameter of
ArgReader
s (which are used to translate data from a HTTP request to a scala parameter) customizable for every decorator.Motivation
The idea behind this proposal is to uniformize the way "named" and "by-type" parameters within the same endpoint are handled. By "named" parameters I mean parameters which are set from the
Map[String, Input]
in the delegate function, and byby-type
parameters I mean parameters which are computed from an arity zeroArgReader
, and thus use thecask.Request
context to compute their value.E.g.
In this case,
foo
andbar
and "named" andcookie1
andreq
are "by-type".Right now, the
wrapFunction
method handles how named parameters are set, and therefore can centrally do arbitrary pre- and post-processing. However, by-type parameters must always use acask.Request
to compute any values, which can be problematic if the computation is expensive or if any kind of state is maintained.This pull request removes this asymmetry, by allowing the
wrapFunction
to compute a custom context which is computed once before being passed to allInputReader
sImplementation approach
InputContext
type parameter to decoratorsInputContext
todelegate
.type Delegate = (InputContext, Map[String, Input)) => ...
Decorator.invoke()
to carry a list of input contexts, one for each parameter listEntrypoint
macros to use these input contexts rather than hardcodingcask.Request
(currently I've only done the work to support Scala 3 for this)This is a proof of concept PR. As of this writing, the code only works for Scala 3.