tower-web icon indicating copy to clipboard operation
tower-web copied to clipboard

Consider `mount("/foo", resource)` API

Open carllerche opened this issue 7 years ago • 4 comments

Currently, all resources are implicitly mounted at the root. It would be worthwhile to allow mounting resources at sub paths.

The two options are to either:

a) Add ServiceBuilder::mount("/foo", resource) b) Replace ServiceBuilder::resource(resource) with a mount API.

carllerche avatar Aug 04 '18 18:08 carllerche

Is there the possibility of an API like

ServiceBuilder::new()
  .resource(MountBuilder.new().mount("/foo", resource))

That is, Using Resource as the primary abstraction and having mounting reside atop that?

shepmaster avatar Aug 04 '18 21:08 shepmaster

Well, that could work... it is much more verbose though. Conceptually though that strategy would work, assuming we can figure out a shorthand.

carllerche avatar Aug 04 '18 22:08 carllerche

it is much more verbose though

For builder-heavy APIs, there are two primary tricks I use to reduce verbosity:

  1. Add aliases for constructors — mount() instead of MountBuilder.new(), for example.

    service()
        .resource({
            mount()
                .at("/foo", resource)
                .at("/foo", resource))
        })
        .go()
    

    You could also go a step further by creating an alias for the whole shebang, if chaining isn't useful for the inner case:

    service()
        .resource(mount("/foo", resource))
    
  2. Have methods take builders instead of making the user call build. I snuck that into the code example already, but basically it's just <R: Into<Resource>> and implement From on the builder. (oh, I wish there was TryFrom...)

A Rust-specific addition to these ideas is to have extension traits for ServiceBuilder:

trait MountExt {
    fn mount(&self, route: &str, resource: Res);
}

impl MountExt for ServiceBuilder { ... }

This latter allows the extremely succinct syntax while still offering the builders for more complicated possibilities. It's a bit annoying because the docs get a bit scattered about. For the purposes of tower-web, you can of course skip these and implement them as inherent methods, but it would allow clean 3rd party integration.

shepmaster avatar Aug 04 '18 22:08 shepmaster

Yeah, I think we can go one of those routes.

Another option I thought of.. add the mount fn on IntoResource?

ServiceBuilder::new()
    .resource(users_resource.mount("/users"))
    .build()

The disadvantage there is it reads backwards (IMO). I would rather see mount before users_resource.

carllerche avatar Aug 04 '18 22:08 carllerche