oxidecomputer / dropshot

expose REST APIs from a Rust program
Apache License 2.0
843 stars 74 forks source link

Add a way to combine ApiDescription instances #1069

Open sunshowers opened 2 months ago

sunshowers commented 2 months ago

Currently, Dropshot doesn't provide a first class way to split up an API into "components" or "parts". With function-based servers this isn't a huge issue because you can just write functions of the form:

fn register_component(description: &mut ApiDescription<MyContext>) -> Result<(), ApiDescriptionRegisterError> {
    // ...
}

And then call these register_component functions in a higher-level constructor.

But with API traits, the proc macro generates functions (api_description and stub_api_description) that create ApiDescription instances and register all associated endpoints in one go. So it's not possible to write functions of the form register_component.

How can we address this? Well, one option is to also generate functions of the form register_component. But that leads to some confusion. For example, how would tag_config (https://github.com/oxidecomputer/dropshot/pull/1059) work in this world?

It seems like a better solution would be to provide a way to merge or combine two ApiDescription instances into one. An ApiDescription is a route handler trie with some additional information, and it should certainly be possible to combine two tries into one.

Here's what I'm generally imagining:

impl<C: ServerContext> ApiDescription<C> {
    pub fn extend(&mut self, other: ApiDescription<C>) -> Result<(), ApiDescriptionRegisterError> { }

    /// Merges an `ApiDescription` into `self`, applying a prefix to all endpoints in `other`.
    pub fn extend_with_prefix(&mut self, other: ApiDescription<C>, prefix: &str) -> Result<(), ApiDescriptionRegisterError> { }

    // ... could also provide an extend method with a callback that transforms endpoint
    // paths in `other`, so that e.g. a component can be inserted in the middle
}

This opens up some flexibility:

There are some downsides, though:

If we decide to do this, we'll definitely want to think about it in conjunction with multi-version support (https://github.com/oxidecomputer/dropshot/issues/869). For example, should each version be its own trait?