Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
lihaoyi committed Jan 4, 2024
1 parent 264779e commit 299cc43
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 107 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
8 changes: 6 additions & 2 deletions docs/pages/1 - Cask: a Scala HTTP micro-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,17 @@ $$$minimalApplication2
You can split up your routes into separate `cask.Routes` objects as makes sense
and pass them all into `cask.Main`.
## Variable Routes
## Variable Routes & Query Params
$$$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
or from query-parameters of the same name (e.g. `param` below).
$$$queryParams
You can make your route
take
* `param: String` to match `?param=hello`
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
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 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
}

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
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 299cc43

Please sign in to comment.