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

Make cask.QueryParams work for JSON endpoints, and form endpoints, replace subpath = true with cask.RemainingPathSegments #109

Merged
merged 1 commit into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cask/src/cask/endpoints/JsonEndpoint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ object JsReader{
implicit def paramReader[T: ParamReader]: JsReader[T] = new JsReader[T] {
override def arity = 0

override def unknownQueryParams: Boolean = implicitly[ParamReader[T]].unknownQueryParams
override def remainingPathSegments: Boolean = implicitly[ParamReader[T]].remainingPathSegments
override def read(ctx: cask.model.Request, label: String, v: ujson.Value) = {
implicitly[ParamReader[T]].read(ctx, label, Nil)
}
Expand Down
20 changes: 20 additions & 0 deletions cask/src/cask/endpoints/ParamReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,24 @@ object ParamReader{
implicit object CookieParam extends NilParam[Cookie]((ctx, label) =>
Cookie.fromUndertow(ctx.exchange.getRequestCookies().get(label))
)

implicit object QueryParams extends ParamReader[cask.model.QueryParams] {
def arity: Int = 0

override def unknownQueryParams = true

def read(ctx: cask.model.Request, label: String, v: Unit) = {
cask.model.QueryParams(ctx.queryParams)
}
}

implicit object RemainingPathSegments extends ParamReader[cask.model.RemainingPathSegments] {
def arity: Int = 0

override def remainingPathSegments = true

def read(ctx: cask.model.Request, label: String, v: Unit) = {
cask.model.RemainingPathSegments(ctx.remainingPathSegments)
}
}
}
9 changes: 2 additions & 7 deletions cask/src/cask/endpoints/WebEndpoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,8 @@ abstract class QueryParamReader[T]
def read(ctx: cask.model.Request, label: String, v: Seq[String]): T
}
object QueryParamReader{
implicit object QueryParams extends QueryParamReader[cask.model.QueryParams]{
def arity: Int = 0

override def unknownQueryParams = true
def read(ctx: cask.model.Request, label: String, v: Seq[String]) = {
cask.model.QueryParams(ctx.queryParams)
}

}
class SimpleParam[T](f: String => T) extends QueryParamReader[T]{
def arity = 1
def read(ctx: cask.model.Request, label: String, v: Seq[String]): T = f(v.head)
Expand Down Expand Up @@ -92,6 +85,8 @@ object QueryParamReader{
implicit def paramReader[T: ParamReader]: QueryParamReader[T] = new QueryParamReader[T] {
override def arity = 0

override def unknownQueryParams: Boolean = implicitly[ParamReader[T]].unknownQueryParams
override def remainingPathSegments: Boolean = implicitly[ParamReader[T]].remainingPathSegments
override def read(ctx: cask.model.Request, label: String, v: Seq[String]) = {
implicitly[ParamReader[T]].read(ctx, label, v)
}
Expand Down
6 changes: 5 additions & 1 deletion cask/src/cask/main/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,11 @@ object Main{
val segments = Util.splitPath(metadata.endpoint.path)
val methods = metadata.endpoint.methods.map(_ -> (routes, metadata: EndpointMetadata[_]))
val methodMap = methods.toMap[String, (Routes, EndpointMetadata[_])]
(segments, methodMap, metadata.endpoint.subpath)
val subpath =
metadata.endpoint.subpath ||
metadata.entryPoint.argSignatures.exists(_.exists(_.reads.remainingPathSegments))

(segments, methodMap, subpath)
}

val dispatchInputs = flattenedRoutes.groupBy(_._1).map { case (segments, values) =>
Expand Down
1 change: 1 addition & 0 deletions cask/src/cask/model/Params.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.undertow.server.HttpServerExchange
import io.undertow.server.handlers.CookieImpl

case class QueryParams(value: Map[String, collection.Seq[String]])
case class RemainingPathSegments(value: Seq[String])

case class Request(exchange: HttpServerExchange, remainingPathSegments: Seq[String])
extends geny.ByteData with geny.Readable {
Expand Down
2 changes: 2 additions & 0 deletions cask/src/cask/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package object cask {
val Request = model.Request
type QueryParams = model.QueryParams
val QueryParams = model.QueryParams
type RemainingPathSegments = model.RemainingPathSegments
val RemainingPathSegments = model.RemainingPathSegments

// endpoints
type websocket = endpoints.websocket
Expand Down
1 change: 1 addition & 0 deletions cask/src/cask/router/Misc.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ case class ArgSig[I, -T, +V, -C](name: String,
trait ArgReader[I, +T, -C]{
def arity: Int
def unknownQueryParams: Boolean = false
def remainingPathSegments: Boolean = false
def read(ctx: C, label: String, input: I): T
}
10 changes: 3 additions & 7 deletions docs/pages/1 - Cask: a Scala HTTP micro-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,13 @@ take
least one value
* `param: Seq[T] = Nil` for repeated params such as `?param=hello&param=world` allowing
zero values
* `queryParams: cask.QueryParams` if you want your route to be able to handle arbitrary
* `params: cask.QueryParams` if you want your route to be able to handle arbitrary
query params without needing to list them out as separate arguments
* `segments: cask.RemainingPathSegments` if you want to allow the endpoint to handle
arbitrary sub-paths of the given path
* `request: cask.Request` which provides lower level access to the things that the HTTP
request provides

If you need to capture the entire sub-path of the request, you can set the flag
`subpath=true` and ask for a `request: cask.Request` (the name of the param doesn't
matter). This will make the route match any sub-path of the prefix given to the
`@cask` decorator, and give you the remainder to use in your endpoint logic
as `request.remainingPathSegments`

## Multi-method Routes

$$$httpMethods
Expand Down
9 changes: 9 additions & 0 deletions example/formJsonPost/app/src/FormJsonPost.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,14 @@ object FormJsonPost extends cask.MainRoutes{
image.fileName
}


@cask.postJson("/json-extra")
def jsonEndpointExtra(value1: ujson.Value,
value2: Seq[Int],
params: cask.QueryParams,
segments: cask.RemainingPathSegments) = {
"OK " + value1 + " " + value2 + " " + params.value + " " + segments.value
}

initialize()
}
6 changes: 6 additions & 0 deletions example/formJsonPost/app/test/src/ExampleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ object ExampleTests extends TestSuite{
)
)
response5.text() ==> "my-best-image.txt"


val response6 = requests.post(
s"$host/json-extra/omg/wtf/bbq?iam=cow&hearme=moo",
data = """{"value1": true, "value2": [3]}"""
)
}
}
}
16 changes: 8 additions & 8 deletions example/variableRoutes/app/src/VariableRoutes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,18 @@ object VariableRoutes extends cask.MainRoutes{
}

@cask.get("/user2/:userName") // allow unknown query params
def getUserProfileAllowUnknown(userName: String, queryParams: cask.QueryParams) = {
s"User $userName " + queryParams.value
def getUserProfileAllowUnknown(userName: String, params: cask.QueryParams) = {
s"User $userName " + params.value
}

@cask.get("/path", subpath = true)
def getSubpath(request: cask.Request) = {
s"Subpath ${request.remainingPathSegments}"
@cask.get("/path")
def getSubpath(remainingPathSegments: cask.RemainingPathSegments) = {
s"Subpath ${remainingPathSegments.value}"
}

@cask.post("/path", subpath = true)
def postArticleSubpath(request: cask.Request) = {
s"POST Subpath ${request.remainingPathSegments}"
@cask.post("/path")
def postArticleSubpath(remainingPathSegments: cask.RemainingPathSegments) = {
s"POST Subpath ${remainingPathSegments.value}"
}

initialize()
Expand Down
2 changes: 0 additions & 2 deletions example/variableRoutes/app/test/src/ExampleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,6 @@ object ExampleTests extends TestSuite{
res3 == "User lihaoyi Map(unknown1 -> WrappedArray(123), unknown2 -> WrappedArray(abc))" ||
res3 == "User lihaoyi Map(unknown1 -> ArraySeq(123), unknown2 -> ArraySeq(abc))"
)

}

}
}