roby
04/17/2023, 10:19 PM"/boards" / idLens / "lists" meta ....
(api/boards/:id/lists) this is my route where idLens should be an integer,
before implementing the SPA code, a test like this would pass:
@Test
fun `create list with invalid board ID`() {
val list = BoardList(1337, 1, "Test List", 1)
val req = client(Request(POST, "$uri/boards/abc/lists").body(Json.encodeToString(list)))
assertEquals(Status.NOT_FOUND, req.status) // req should return NOT_FOUND because abc is not an integer
}
however, after adding the spa into my routes
val handler = routes(
"/api" bind filter.then(apiContract),
singlePageApp(ResourceLoader.Directory("static-content")),
Now the test fails, because the request returns OK because it returns a html page???
How should I fix this?Andrew O'Hara
04/17/2023, 10:28 PMuri
? Your request url must include the /api
path to be routed to the apiContract
.roby
04/17/2023, 10:28 PMprivate val uri = "<http://localhost>:${server.port()}/api"
roby
04/17/2023, 10:29 PMAndrew O'Hara
04/17/2023, 10:30 PM/api
handler would have precedence in routes
. What happens if you reverse the order of the api and spa in routes
?roby
04/17/2023, 10:32 PMroby
04/17/2023, 10:33 PMsinglePageApp(ResourceLoader.Directory("static-content"))
roby
04/17/2023, 10:33 PMAndrew O'Hara
04/17/2023, 10:34 PMroby
04/17/2023, 10:34 PM"/site" bind singlePageApp(ResourceLoader.Directory("static-content")),
but still same result 🤔Andrew O'Hara
04/17/2023, 10:38 PMAndrew O'Hara
04/17/2023, 10:38 PMclass RoutingTest {
@Test
fun `test greedy spa`() {
val handler = routes(
"/api" bind routes(
"/bar" bind Method.GET to {
Response(Status.OK).body("hi")
}
),
singlePageApp(ResourceLoader.Classpath())
)
val resp = handler(Request(Method.GET, "/api/bar"))
resp.shouldHaveStatus(Status.OK)
resp.shouldHaveBody("hi")
}
}
Andrew O'Hara
04/17/2023, 10:39 PMAndrew O'Hara
04/17/2023, 10:43 PMroby
04/17/2023, 10:49 PMAndrew O'Hara
04/17/2023, 10:52 PMAndrew O'Hara
04/17/2023, 10:52 PMroby
04/17/2023, 10:53 PMval contractRoutes: List<ContractRoute> = listOf(
"/foo" / idLens / "bar" bindContract Method.GET to { id, _ -> {
req -> Response(Status.OK)
} },
)
private val apiContract = contract {
routes += contractRoutes
}
class RoutingTest {
@Test
fun `test greedy spa`() {
val handler = routes(
//singlePageApp(ResourceLoader.Directory("static-content")),
"/api" bind apiContract,
)
val resp = handler(Request(Method.GET, "/api/foo/abc/bar"))
println(resp)
assertEquals(Status.OK, resp.status)
}
}
roby
04/17/2023, 10:53 PMAndrew O'Hara
04/17/2023, 10:55 PMidLens
?roby
04/17/2023, 10:55 PMval idLens = <http://Path.int|Path.int>().of("id")
roby
04/17/2023, 10:56 PMResourceLoader.Directory("static-content")
and ResourceLoader.Classpath
might interfere in the final resultAndrew O'Hara
04/17/2023, 10:59 PMidLens
is the issue here. If your lens requires an Int
, then /api/foo/123/bar
will match the api route. However, /api/foo/abc/bar
doesn't match the route, so the router will fall back to the next thing that does: the SPA.
I think the only solution you have is to modify the lens to accept a string, and then perform the validation inside the handler.Andrew O'Hara
04/17/2023, 11:00 PMroby
04/17/2023, 11:02 PMroby
04/17/2023, 11:05 PMAndrew O'Hara
04/17/2023, 11:08 PMroby
04/17/2023, 11:15 PMroby
04/17/2023, 11:15 PMroby
04/17/2023, 11:16 PMdave
04/18/2023, 4:16 PMI'm not certain if this behaviour is intentional, but I can see the benefit: have multiple routes that all accept a different lens type.Yes - this is exactly working as intended. It's the same with all of the routing logic - overall if the request cannot be matched, then the request falls through to the next layer. If you think about it, you can use things like accept headers in the same way so that you can service requests for different content types with different handlers