Akka-http: Type safety for path directives

Created on 10 Feb 2020  路  2Comments  路  Source: akka/akka-http

Today, it's possible to build routing DSLs with the path directives that result in unreachable code.
For example

path("users" / Segment) { userId =>
  get {
    complete(userService.getUser(userId)
  } ~
  path("friends") { 
    get {
      complete(userService.listFriends(userId)
    }
  }
}

Noticing this requires an attention to detail that it would be great if the compiler could exert for us.
The general idea would be to create a trait which indicates if a route or directive is path-sensitive and if so, don't allow it to be passed to an inner directive of path or pathEnd.
I could see the scope of this going a little crazy, so a first step could be to just to try to cover use cases with the routing DSL, and say it's fair to lose type safety when flat mapping and such.

1 - triaged discuss server routing

Most helpful comment

I ran into that trap often enough myself, so I can feel the pain and see how that would be useful.

That said, a compile-time solution is very unlikely. The routing DSL is by design very dynamic. This has various drawbacks but the main benefit is that the DSL is consistent. There are basically only two types, Directive and Route that can be used to implement any kind of behavior you need. This is probably not going to change. If you think about it, it would have to be a massively invasive change just to support the type-safety for path. What if we would like to introduce type-safe variants of other directives like get/post etc?

I can see different ways of improvement:

  • a scalafix style tooling that tries to analyze routes and gives hints
  • Right now the routing DSL is more dynamic that needed in most cases (because of monadic extraction). This prevents a runtime analysis of problems like the one here because most of the routing tree is only constructed when a request comes in. We might be able to pull off a change to the API that would remove dynamic execution for most routes so that we could inspect the route tree. In that case we could offer runtime hints at the start of the problem when we find invalid routes.
  • This problem does not seem to be a runtime problem but more of a debugging problem. We could just bail and say that this a simple thing to find in tests so it's inconvenient but not too serious. Still when it happens, it keeps you scratching your head so it would be good if we could offer more guidance especially to new users so they can trace route execution and understand why a route didn't match.

All 2 comments

I ran into that trap often enough myself, so I can feel the pain and see how that would be useful.

That said, a compile-time solution is very unlikely. The routing DSL is by design very dynamic. This has various drawbacks but the main benefit is that the DSL is consistent. There are basically only two types, Directive and Route that can be used to implement any kind of behavior you need. This is probably not going to change. If you think about it, it would have to be a massively invasive change just to support the type-safety for path. What if we would like to introduce type-safe variants of other directives like get/post etc?

I can see different ways of improvement:

  • a scalafix style tooling that tries to analyze routes and gives hints
  • Right now the routing DSL is more dynamic that needed in most cases (because of monadic extraction). This prevents a runtime analysis of problems like the one here because most of the routing tree is only constructed when a request comes in. We might be able to pull off a change to the API that would remove dynamic execution for most routes so that we could inspect the route tree. In that case we could offer runtime hints at the start of the problem when we find invalid routes.
  • This problem does not seem to be a runtime problem but more of a debugging problem. We could just bail and say that this a simple thing to find in tests so it's inconvenient but not too serious. Still when it happens, it keeps you scratching your head so it would be good if we could offer more guidance especially to new users so they can trace route execution and understand why a route didn't match.

it would have to be a massively invasive change just to support the type-safety for path

This is what I meant by "the scope of this going a little crazy". I was hopeful that a minimally invasive solution could be brainstormed. One that doesn't make things completely type-safe, but covers the common gotchas. But,

Being able to inspect the route tree would be almost as good, for me. Especially the ability to write a test to check the structure itself without having to send a request to hit every end route.
The ability to inspect the route tree would also unlock some other interesting features like listing endpoints for documentation generation.

This problem does not seem to be a runtime problem but more of a debugging problem

I think this point is fair, but optimistic. Being confident that the code is correct after it compiles is always going to be better than code that requires even one short test, which is always going to be better than code that requires many short tests. I think people are likely to be less rigorous the latter down that ladder they go (self included). I think being able to say "just write a test like

val violations: Seq[RouteViolation] = myRoute.check
violations shouldBe empty

would be preferable to a way to trace a specific execution.

Was this page helpful?
0 / 5 - 0 ratings