Skip to content

Commit cdf98d1

Browse files
committed
Add samples to README
1 parent 2f51425 commit cdf98d1

File tree

7 files changed

+139
-10
lines changed

7 files changed

+139
-10
lines changed

README.md

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ The library addresses the following aspects:
1818
- ease implementation of cross cutting concerns for handlers
1919
- ease (local) testing of REST handlers
2020

21-
## Getting Started
21+
## Reference
22+
23+
### Getting Started
2224

2325
To use the core module we need the following:
2426

@@ -51,5 +53,64 @@ class MyRequestHandler : RequestHandler() {
5153
}
5254
```
5355

56+
### Content Negotiation
57+
58+
The router DSL allows for configuration of the content types a handler
59+
- produces (according to the request's `Accept` header)
60+
- consumes (according to the request's `Content-Type` header)
61+
62+
The router itself carries a default for both values.
63+
64+
```kotlin
65+
var defaultConsuming = setOf("application/json")
66+
var defaultProducing = setOf("application/json")
67+
```
68+
69+
These defaults can be overridden on the router level or on the handler level to specify the content types most of your handlers consume and produce.
70+
71+
```kotlin
72+
router {
73+
defaultConsuming = setOf("application/json")
74+
defaultProducing = setOf("application/json")
75+
}
76+
```
77+
78+
Exceptions from this default can be configured on a handler level.
79+
80+
```kotlin
81+
router {
82+
POST("/some") { r: Request<String> -> ResponseEntity.ok(MyResponse(r.body)) }
83+
.producing("application/json")
84+
.consuming("application/json")
85+
}
86+
```
87+
88+
### Filters
89+
90+
Filters are a means to add cross-cutting concerns to your request handling logic outside a handler function.
91+
Multiple filters can be used by composing them.
92+
93+
```kotlin
94+
override val router = router {
95+
filter = loggingFilter().then(mdcFilter())
96+
97+
GET("/some", controller::get)
98+
}
99+
100+
private fun loggingFilter() = Filter { next -> {
101+
request ->
102+
log.info("Handling request ${request.apiRequest.httpMethod} ${request.apiRequest.path}")
103+
next(request) }
104+
}
105+
106+
private fun mdcFilter() = Filter { next -> {
107+
request ->
108+
MDC.put("requestId", request.apiRequest.requestContext?.requestId)
109+
next(request) }
110+
}
111+
}
112+
```
113+
114+
54115

55116

router-protobuf/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ dependencies {
2626
testImplementation("org.slf4j:slf4j-simple:1.7.26")
2727
}
2828

29+
2930
protobuf {
3031
protoc {
3132
// The artifact spec for the Protobuf Compiler

router-protobuf/src/test/kotlin/io/moia/router/proto/RequestHandlerTest.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import io.moia.router.Request
77
import io.moia.router.ResponseEntity
88
import io.moia.router.Router.Companion.router
99
import io.moia.router.bodyAsBytes
10+
import io.moia.router.proto.sample.SampleOuterClass.Sample
1011
import io.mockk.mockk
1112
import org.junit.jupiter.api.Test
12-
import com.github.mduesterhoeft.router.proto.sample.SampleOuterClass.Sample
1313
import java.util.Base64
1414

1515
class RequestHandlerTest {
@@ -67,9 +67,12 @@ class RequestHandlerTest {
6767
class TestRequestHandler : ProtoEnabledRequestHandler() {
6868

6969
override val router = router {
70+
defaultProducing = setOf("application/x-protobuf")
71+
defaultConsuming = setOf("application/x-protobuf")
72+
7073
GET("/some-proto") { _: Request<Unit> ->
7174
ResponseEntity.ok(Sample.newBuilder().setHello("Hello").build())
72-
}
75+
}.producing("application/x-protobuf", "application/json")
7376
POST("/some-proto") { r: Request<Sample> ->
7477
ResponseEntity.ok(r.body)
7578
}

router-protobuf/src/test/proto/Sample.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
syntax = "proto3";
22

3-
package com.github.mduesterhoeft.router.proto.sample;
3+
package io.moia.router.proto.sample;
44

55
message Sample {
66
string hello = 1;

router/src/main/kotlin/io/moia/router/Router.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ class Router {
66

77
val routes = mutableListOf<RouterFunction<*, *>>()
88

9-
var defaultConsuming = setOf("application/json", "application/x-protobuf")
10-
var defaultProducing = setOf("application/json", "application/x-protobuf")
9+
var defaultConsuming = setOf("application/json")
10+
var defaultProducing = setOf("application/json")
1111

1212
var filter: Filter = Filter.NoOp
1313

router/src/test/kotlin/io/moia/router/RouterTest.kt

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.moia.router
22

33
import assertk.assert
4-
import assertk.assertions.containsAll
54
import assertk.assertions.hasSize
65
import assertk.assertions.isEmpty
76
import assertk.assertions.isEqualTo
@@ -24,7 +23,47 @@ class RouterTest {
2423
assert(method).isEqualTo("GET")
2524
assert(pathPattern).isEqualTo("/some")
2625
assert(consumes).isEmpty()
27-
assert(produces).containsAll("application/json", "application/x-protobuf")
26+
assert(produces).isEqualTo(setOf("application/json"))
27+
}
28+
}
29+
30+
@Test
31+
fun `should register get route with specific content types`() {
32+
val router = router {
33+
POST("/some") { r: Request<Unit> ->
34+
ResponseEntity.ok("""{"hello": "world", "request":"${r.body}"}""")
35+
}
36+
.producing("text/plain")
37+
.consuming("text/plain")
38+
}
39+
40+
assert(router.routes).hasSize(1)
41+
with(router.routes.first().requestPredicate) {
42+
assert(method).isEqualTo("POST")
43+
assert(pathPattern).isEqualTo("/some")
44+
assert(consumes).isEqualTo(setOf("text/plain"))
45+
assert(produces).isEqualTo(setOf("text/plain"))
46+
}
47+
}
48+
49+
@Test
50+
fun `should register get route with custom default content types`() {
51+
val router = router {
52+
53+
defaultConsuming = setOf("text/plain")
54+
defaultProducing = setOf("text/plain")
55+
56+
POST("/some") { r: Request<Unit> ->
57+
ResponseEntity.ok("""{"hello": "world", "request":"${r.body}"}""")
58+
}
59+
}
60+
61+
assert(router.routes).hasSize(1)
62+
with(router.routes.first().requestPredicate) {
63+
assert(method).isEqualTo("POST")
64+
assert(pathPattern).isEqualTo("/some")
65+
assert(consumes).isEqualTo(setOf("text/plain"))
66+
assert(produces).isEqualTo(setOf("text/plain"))
2867
}
2968
}
3069

@@ -37,9 +76,7 @@ class RouterTest {
3776
}
3877
assert(router.routes).hasSize(1)
3978
with(router.routes.first().requestPredicate) {
40-
assert(method).isEqualTo("POST")
4179
assertTrue(UriTemplate.from(pathPattern).matches("/some/sub/sub/sub/path"))
42-
assert(produces).containsAll("application/json", "application/x-protobuf")
4380
}
4481
}
4582
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,45 @@
11
package io.moia.router.sample
22

3+
import io.moia.router.Filter
34
import io.moia.router.Request
45
import io.moia.router.RequestHandler
56
import io.moia.router.ResponseEntity
67
import io.moia.router.Router.Companion.router
8+
import io.moia.router.getHeaderCaseInsensitive
9+
import io.moia.router.then
10+
import org.slf4j.Logger
11+
import org.slf4j.LoggerFactory
12+
import org.slf4j.MDC
13+
import kotlin.concurrent.timer
14+
import kotlin.system.measureTimeMillis
715

816
class MyRequestHandler : RequestHandler() {
917
private val controller = SomeController()
1018

1119
override val router = router {
20+
//use filters to add cross-cutting concerns to each request
21+
filter = loggingFilter().then(mdcFilter())
22+
1223
// functions can be externalized...
1324
GET("/some", controller::get)
1425

1526
// simple handlers can also be declared inline
1627
POST("/some") { r: Request<Sample> -> ResponseEntity.ok(r.body) }
1728
}
29+
30+
private fun loggingFilter() = Filter { next -> {
31+
request ->
32+
log.info("Handling request ${request.apiRequest.httpMethod} ${request.apiRequest.path}")
33+
next(request) }
34+
}
35+
36+
private fun mdcFilter() = Filter { next -> {
37+
request ->
38+
MDC.put("requestId", request.apiRequest.requestContext?.requestId)
39+
next(request) }
40+
}
41+
42+
companion object {
43+
val log: Logger = LoggerFactory.getLogger(MyRequestHandler::class.java)
44+
}
1845
}

0 commit comments

Comments
 (0)