Skip to content

Commit

Permalink
Separate query param docs from variable routes (#112)
Browse files Browse the repository at this point in the history
  • Loading branch information
lihaoyi authored Jan 4, 2024
1 parent 469dd0c commit 76fe751
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 111 deletions.
5 changes: 5 additions & 0 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import $file.example.todoApi.build
import $file.example.todoDb.build
import $file.example.twirl.build
import $file.example.variableRoutes.build
import $file.example.queryParams.build
import $file.example.websockets.build
import $file.example.websockets2.build
import $file.example.websockets3.build
Expand Down Expand Up @@ -172,6 +173,9 @@ object example extends Module{
trait VariableRoutesModule extends millbuild.example.variableRoutes.build.AppModule with LocalModule
object variableRoutes extends Cross[VariableRoutesModule](scalaVersions)

trait QueryParamsModule extends millbuild.example.variableRoutes.build.AppModule with LocalModule
object queryParams extends Cross[QueryParamsModule](scalaVersions)

trait WebsocketsModule extends millbuild.example.websockets.build.AppModule with LocalModule
object websockets extends Cross[WebsocketsModule](scalaVersions)

Expand Down Expand Up @@ -228,6 +232,7 @@ def uploadToGithub() = T.command{
millbuild.example.todoDb.build.millSourcePath,
millbuild.example.twirl.build.millSourcePath,
millbuild.example.variableRoutes.build.millSourcePath,
millbuild.example.queryParams.build.millSourcePath,
millbuild.example.websockets.build.millSourcePath,
millbuild.example.websockets2.build.millSourcePath,
millbuild.example.websockets3.build.millSourcePath,
Expand Down
20 changes: 14 additions & 6 deletions docs/pages/1 - Cask: a Scala HTTP micro-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,19 @@ and pass them all into `cask.Main`.
$$$variableRoutes
You can bind variables to endpoints by declaring them as parameters: these are
either taken from a path-segment matcher of the same name (e.g. `postId` above),
or from query-parameters of the same name (e.g. `param` above). You can make your route
take
You can bind path segments to endpoint parameters by declaring them as parameters. these are
either:
* A parameter of the same name as the variable path segment of the same name as you
(e.g. `postId` above),
* A parameter of type `segments: cask.RemainingPathSegments`, if you want to allow
the endpoint to handle arbitrary sub-paths of the given path
## Query Params
$$$queryParams
You can bind query parameters to your endpoint method via parameters of the form:
* `param: String` to match `?param=hello`
* `param: Int` for `?param=123`. Other valid types include `Boolean`, `Byte`, `Short`, `Long`,
Expand All @@ -153,8 +162,7 @@ take
zero values
* `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
Expand Down
35 changes: 35 additions & 0 deletions example/queryParams/app/src/QueryParams.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package app
object QueryParams extends cask.MainRoutes{

@cask.get("/article/:articleId") // Mandatory query param, e.g. HOST/article/foo?param=bar
def getArticle(articleId: Int, param: String) = {
s"Article $articleId $param"
}

@cask.get("/article2/:articleId") // Optional query param
def getArticleOptional(articleId: Int, param: Option[String] = None) = {
s"Article $articleId $param"
}

@cask.get("/article3/:articleId") // Optional query param with default
def getArticleDefault(articleId: Int, param: String = "DEFAULT VALUE") = {
s"Article $articleId $param"
}

@cask.get("/article4/:articleId") // 1-or-more param, e.g. HOST/article/foo?param=bar&param=qux
def getArticleSeq(articleId: Int, param: Seq[String]) = {
s"Article $articleId $param"
}

@cask.get("/article5/:articleId") // 0-or-more query param
def getArticleOptionalSeq(articleId: Int, param: Seq[String] = Nil) = {
s"Article $articleId $param"
}

@cask.get("/user2/:userName") // allow unknown params, e.g. HOST/article/foo?foo=bar&qux=baz
def getUserProfileAllowUnknown(userName: String, params: cask.QueryParams) = {
s"User $userName " + params.value
}

initialize()
}
95 changes: 95 additions & 0 deletions example/queryParams/app/test/src/ExampleTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package app
import io.undertow.Undertow

import utest._

object ExampleTests extends TestSuite{
def withServer[T](example: cask.main.Main)(f: String => T): T = {
val server = Undertow.builder
.addHttpListener(8081, "localhost")
.setHandler(example.defaultHandler)
.build
server.start()
val res =
try f("http://localhost:8081")
finally server.stop()
res
}

val tests = Tests{
test("QueryParams") - withServer(QueryParams){ host =>
val noIndexPage = requests.get(host, check = false)
noIndexPage.statusCode ==> 404

assert(
requests.get(s"$host/article/123?param=xyz").text() ==
"Article 123 xyz"
)

requests.get(s"$host/article/123", check = false).text() ==>
"""Missing argument: (param: String)
|
|Arguments provided did not match expected signature:
|
|getArticle
| articleId Int
| param String
|
|""".stripMargin

assert(
requests.get(s"$host/article2/123?param=xyz").text() ==
"Article 123 Some(xyz)"
)

assert(
requests.get(s"$host/article2/123").text() ==
"Article 123 None"
)

assert(
requests.get(s"$host/article3/123?param=xyz").text() ==
"Article 123 xyz"
)

assert(
requests.get(s"$host/article3/123").text() ==
"Article 123 DEFAULT VALUE"
)


val res1 = requests.get(s"$host/article4/123?param=xyz&param=abc").text()
assert(
res1 == "Article 123 ArraySeq(xyz, abc)" ||
res1 == "Article 123 ArrayBuffer(xyz, abc)"
)

requests.get(s"$host/article4/123", check = false).text() ==>
"""Missing argument: (param: Seq[String])
|
|Arguments provided did not match expected signature:
|
|getArticleSeq
| articleId Int
| param Seq[String]
|
|""".stripMargin

val res2 = requests.get(s"$host/article5/123?param=xyz&param=abc").text()
assert(
res2 == "Article 123 ArraySeq(xyz, abc)" ||
res2 == "Article 123 ArrayBuffer(xyz, abc)"
)
assert(
requests.get(s"$host/article5/123").text() == "Article 123 List()"
)

val res3 = requests.get(s"$host/user2/lihaoyi?unknown1=123&unknown2=abc", check = false).text()
assert(
res3 == "User lihaoyi Map(unknown1 -> ArrayBuffer(123), unknown2 -> ArrayBuffer(abc))" ||
res3 == "User lihaoyi Map(unknown1 -> WrappedArray(123), unknown2 -> WrappedArray(abc))" ||
res3 == "User lihaoyi Map(unknown1 -> ArraySeq(123), unknown2 -> ArraySeq(abc))"
)
}
}
}
14 changes: 14 additions & 0 deletions example/queryParams/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import mill._, scalalib._

trait AppModule extends CrossScalaModule{

def ivyDeps = Agg[Dep](
)
object test extends ScalaTests with TestModule.Utest{

def ivyDeps = Agg(
ivy"com.lihaoyi::utest::0.8.1",
ivy"com.lihaoyi::requests::0.8.0",
)
}
}
36 changes: 3 additions & 33 deletions example/variableRoutes/app/src/VariableRoutes.scala
Original file line number Diff line number Diff line change
@@ -1,46 +1,16 @@
package app
object VariableRoutes extends cask.MainRoutes{
@cask.get("/user/:userName")
@cask.get("/user/:userName") // variable path segment, e.g. HOST/user/lihaoyi
def getUserProfile(userName: String) = {
s"User $userName"
}

@cask.get("/article/:articleId")
def getArticle(articleId: Int, param: String) = { // Mandatory query param
s"Article $articleId $param"
}

@cask.get("/article2/:articleId") // Optional query param
def getArticleOptional(articleId: Int, param: Option[String] = None) = {
s"Article $articleId $param"
}

@cask.get("/article3/:articleId") // Optional query param with default
def getArticleDefault(articleId: Int, param: String = "DEFAULT VALUE") = {
s"Article $articleId $param"
}

@cask.get("/article4/:articleId") // 1-or-more query param
def getArticleSeq(articleId: Int, param: Seq[String]) = {
s"Article $articleId $param"
}

@cask.get("/article5/:articleId") // 0-or-more query param
def getArticleOptionalSeq(articleId: Int, param: Seq[String] = Nil) = {
s"Article $articleId $param"
}

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

@cask.get("/path")
@cask.get("/path") // GET allowing arbitrary sub-paths, e.g. HOST/path/foo/bar/baz
def getSubpath(remainingPathSegments: cask.RemainingPathSegments) = {
s"Subpath ${remainingPathSegments.value}"
}

@cask.post("/path")
@cask.post("/path") // POST allowing arbitrary sub-paths, e.g. HOST/path/foo/bar/baz
def postArticleSubpath(remainingPathSegments: cask.RemainingPathSegments) = {
s"POST Subpath ${remainingPathSegments.value}"
}
Expand Down
72 changes: 0 additions & 72 deletions example/variableRoutes/app/test/src/ExampleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,70 +25,6 @@ object ExampleTests extends TestSuite{

requests.get(s"$host/user", check = false).statusCode ==> 404


assert(
requests.get(s"$host/article/123?param=xyz").text() ==
"Article 123 xyz"
)

requests.get(s"$host/article/123", check = false).text() ==>
"""Missing argument: (param: String)
|
|Arguments provided did not match expected signature:
|
|getArticle
| articleId Int
| param String
|
|""".stripMargin

assert(
requests.get(s"$host/article2/123?param=xyz").text() ==
"Article 123 Some(xyz)"
)

assert(
requests.get(s"$host/article2/123").text() ==
"Article 123 None"
)

assert(
requests.get(s"$host/article3/123?param=xyz").text() ==
"Article 123 xyz"
)

assert(
requests.get(s"$host/article3/123").text() ==
"Article 123 DEFAULT VALUE"
)


val res1 = requests.get(s"$host/article4/123?param=xyz&param=abc").text()
assert(
res1 == "Article 123 ArraySeq(xyz, abc)" ||
res1 == "Article 123 ArrayBuffer(xyz, abc)"
)

requests.get(s"$host/article4/123", check = false).text() ==>
"""Missing argument: (param: Seq[String])
|
|Arguments provided did not match expected signature:
|
|getArticleSeq
| articleId Int
| param Seq[String]
|
|""".stripMargin

val res2 = requests.get(s"$host/article5/123?param=xyz&param=abc").text()
assert(
res2 == "Article 123 ArraySeq(xyz, abc)" ||
res2 == "Article 123 ArrayBuffer(xyz, abc)"
)
assert(
requests.get(s"$host/article5/123").text() == "Article 123 List()"
)

requests.get(s"$host/path/one/two/three").text() ==>
"Subpath List(one, two, three)"

Expand All @@ -104,14 +40,6 @@ object ExampleTests extends TestSuite{
| userName String
|
|""".stripMargin


val res3 = requests.get(s"$host/user2/lihaoyi?unknown1=123&unknown2=abc", check = false).text()
assert(
res3 == "User lihaoyi Map(unknown1 -> ArrayBuffer(123), unknown2 -> ArrayBuffer(abc))" ||
res3 == "User lihaoyi Map(unknown1 -> WrappedArray(123), unknown2 -> WrappedArray(abc))" ||
res3 == "User lihaoyi Map(unknown1 -> ArraySeq(123), unknown2 -> ArraySeq(abc))"
)
}
}
}

0 comments on commit 76fe751

Please sign in to comment.