summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-08-13 01:12:07 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-08-13 01:12:07 +0800
commit974dcbe09830785f202f54959ae5397e9084f7a0 (patch)
treed127d2aff446b8c50debfae3823ebba7e2b457ea
parenteb32d8d4a1e2bd50e5416fcfefd72dfe6da1a7bb (diff)
downloadcask-974dcbe09830785f202f54959ae5397e9084f7a0.tar.gz
cask-974dcbe09830785f202f54959ae5397e9084f7a0.tar.bz2
cask-974dcbe09830785f202f54959ae5397e9084f7a0.zip
add docs on writing custom endpoints
-rw-r--r--build.sc6
-rw-r--r--docs/pages/1 - Cask: a Scala HTTP micro-framework .md37
-rw-r--r--example/endpoints/app/src/Endpoints.scala32
-rw-r--r--example/endpoints/app/test/src/ExampleTests.scala27
-rw-r--r--example/endpoints/build.sc18
5 files changed, 117 insertions, 3 deletions
diff --git a/build.sc b/build.sc
index 58ae60f..ae4f097 100644
--- a/build.sc
+++ b/build.sc
@@ -7,6 +7,7 @@ import $file.example.compress3.build
import $file.example.cookies.build
import $file.example.decorated.build
import $file.example.decorated2.build
+import $file.example.endpoints.build
import $file.example.formJsonPost.build
import $file.example.httpMethods.build
import $file.example.minimalApplication.build
@@ -17,7 +18,6 @@ import $file.example.staticFiles.build
import $file.example.todo.build
import $file.example.todoApi.build
import $file.example.todoDb.build
-import $file.example.twirl.build
import $file.example.variableRoutes.build
object cask extends ScalaModule{
@@ -58,6 +58,7 @@ object example extends Module{
object cookies extends $file.example.cookies.build.AppModule with LocalModule
object decorated extends $file.example.decorated.build.AppModule with LocalModule
object decorated2 extends $file.example.decorated2.build.AppModule with LocalModule
+ object endpoints extends $file.example.endpoints.build.AppModule with LocalModule
object formJsonPost extends $file.example.formJsonPost.build.AppModule with LocalModule
object httpMethods extends $file.example.httpMethods.build.AppModule with LocalModule
object minimalApplication extends $file.example.minimalApplication.build.AppModule with LocalModule
@@ -68,7 +69,6 @@ object example extends Module{
object todo extends $file.example.todo.build.AppModule with LocalModule
object todoApi extends $file.example.todoApi.build.AppModule with LocalModule
object todoDb extends $file.example.todoDb.build.AppModule with LocalModule
- object twirl extends $file.example.twirl.build.AppModule with LocalModule
object variableRoutes extends $file.example.variableRoutes.build.AppModule with LocalModule
}
@@ -99,6 +99,7 @@ def uploadToGithub(authKey: String) = T.command{
$file.example.cookies.build.millSourcePath,
$file.example.decorated.build.millSourcePath,
$file.example.decorated2.build.millSourcePath,
+ $file.example.endpoints.build.millSourcePath,
$file.example.formJsonPost.build.millSourcePath,
$file.example.httpMethods.build.millSourcePath,
$file.example.minimalApplication.build.millSourcePath,
@@ -109,7 +110,6 @@ def uploadToGithub(authKey: String) = T.command{
$file.example.todo.build.millSourcePath,
$file.example.todoApi.build.millSourcePath,
$file.example.todoDb.build.millSourcePath,
- $file.example.twirl.build.millSourcePath,
$file.example.variableRoutes.build.millSourcePath,
)
for(example <- examples){
diff --git a/docs/pages/1 - Cask: a Scala HTTP micro-framework .md b/docs/pages/1 - Cask: a Scala HTTP micro-framework .md
index b41fbe6..db35e6b 100644
--- a/docs/pages/1 - Cask: a Scala HTTP micro-framework .md
+++ b/docs/pages/1 - Cask: a Scala HTTP micro-framework .md
@@ -243,6 +243,43 @@ This is convenient for cases where you want a set of decorators to apply broadly
across your web application, and do not want to repeat them over and over at
every single endpoint.
+### Custom Endpoints
+
+$$$endpoints
+
+When you need more flexibility than decorators allow, you can define your own
+custom `cask.Endpoint`s to replace the default set that Cask provides. This
+allows you to
+
+- Change the expected return type of the annotated function, and how allows you
+ to that type gets processed: the above example trivially expects an allows you
+ to `Int` which becomes the status code, but you could make it e.g.
+ automatically serialize returned objects to JSON responses via your favorite
+ library, or serialize them to bytes via protobufs
+
+- Change where the first parameter list's params are taken from: `@cask.get`
+ takes them from query params, `@cask.postForm` takes them from the
+ form-encoded POST body, and you can write your own endpoint to take the params
+ from where-ever you like: perhaps from the request headers, or a protobuf-
+ encoded request body
+
+- Change how parameters are deserialized: e.g. `@cask.postJson` de-serializes
+ parameters using the [uPickle](https://github.com/lihaoyi/upickle) JSON
+ library, and your own custom endpoint could change that to use another library
+ like [Circe](https://github.com/circe/circe) or
+ [Jackson](https://github.com/FasterXML/jackson-module-scala)
+
+- DRY up common sets of decorators: if all your endpoint functions use the same
+ decorators, you can extract that functionality into a single `cask.Endpoint`
+ to do the job.
+
+Generally you should not be writing custom `cask.Endpoint`s every day, but if
+you find yourself trying to standardize on a way of doing things across your web
+application, it might make sense to write a custom endpoint decorator: to DRY
+things up , separate business logic (inside the annotated function) from
+plumbing (in the endpoint function and decorators), and enforcing a standard of
+how endpoint functions are written.
+
### Gzip & Deflated Responses
diff --git a/example/endpoints/app/src/Endpoints.scala b/example/endpoints/app/src/Endpoints.scala
new file mode 100644
index 0000000..1960b93
--- /dev/null
+++ b/example/endpoints/app/src/Endpoints.scala
@@ -0,0 +1,32 @@
+package app
+
+
+class custom(val path: String, val methods: Seq[String]) extends cask.Endpoint{
+ type Output = Int
+ def wrapFunction(ctx: cask.ParamContext, delegate: Delegate): Returned = {
+ delegate(Map()) match{
+ case cask.internal.Router.Result.Success(num) =>
+ cask.internal.Router.Result.Success(
+ cask.Response("Echo " + num, statusCode = num)
+ )
+ case e: cask.internal.Router.Result.Error => e
+ }
+ }
+
+ // Change this if you want to change
+ def wrapPathSegment(s: String) = Seq(s)
+
+ type Input = Seq[String]
+ type InputParser[T] = cask.endpoints.QueryParamReader[T]
+}
+
+object Endpoints extends cask.MainRoutes{
+
+
+ @custom("/echo/:status", methods = Seq("get"))
+ def echoStatus(status: String) = {
+ status.toInt
+ }
+
+ initialize()
+}
diff --git a/example/endpoints/app/test/src/ExampleTests.scala b/example/endpoints/app/test/src/ExampleTests.scala
new file mode 100644
index 0000000..1302d85
--- /dev/null
+++ b/example/endpoints/app/test/src/ExampleTests.scala
@@ -0,0 +1,27 @@
+package app
+import io.undertow.Undertow
+
+import utest._
+
+object ExampleTests extends TestSuite{
+ def test[T](example: cask.main.BaseMain)(f: String => T): T = {
+ val server = Undertow.builder
+ .addHttpListener(8080, "localhost")
+ .setHandler(example.defaultHandler)
+ .build
+ server.start()
+ val res =
+ try f("http://localhost:8080")
+ finally server.stop()
+ res
+ }
+
+ val tests = Tests{
+ 'Endpoints - test(Endpoints){ host =>
+ requests.get(s"$host/echo/200").text() ==> "Echo 200"
+ requests.get(s"$host/echo/200").statusCode ==> 200
+ requests.get(s"$host/echo/400").text() ==> "Echo 400"
+ requests.get(s"$host/echo/400").statusCode ==> 400
+ }
+ }
+}
diff --git a/example/endpoints/build.sc b/example/endpoints/build.sc
new file mode 100644
index 0000000..2794393
--- /dev/null
+++ b/example/endpoints/build.sc
@@ -0,0 +1,18 @@
+import mill._, scalalib._
+
+
+trait AppModule extends ScalaModule{
+ def scalaVersion = "2.12.6"
+ def ivyDeps = Agg(
+ ivy"com.lihaoyi::cask:0.0.1",
+ )
+
+ object test extends Tests{
+ def testFrameworks = Seq("utest.runner.Framework")
+
+ def ivyDeps = Agg(
+ ivy"com.lihaoyi::utest::0.6.3",
+ ivy"com.lihaoyi::requests::0.1.2",
+ )
+ }
+} \ No newline at end of file