diff options
-rw-r--r-- | build.sc | 6 | ||||
-rw-r--r-- | docs/pages/1 - Cask: a Scala HTTP micro-framework .md | 37 | ||||
-rw-r--r-- | example/endpoints/app/src/Endpoints.scala | 32 | ||||
-rw-r--r-- | example/endpoints/app/test/src/ExampleTests.scala | 27 | ||||
-rw-r--r-- | example/endpoints/build.sc | 18 |
5 files changed, 117 insertions, 3 deletions
@@ -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 |