Circular refs when trying to model a Rust codegen for Swagger


#1

I’m trying to make a rust codegen frontend for swagger, and I’m stuck at polishing the generated API.

Swagger API calls require a configuration, which is, basically a URL basePath and a http client (using hyper.rs there). Everything works just fine as long as my api looks like:

pub fn add_pet<C: hyper::client::Connect>(
    prefix: &str,
    cli: &hyper::client::Client<C>,
    pet: &models::Pet,
    ) -> Box<Future<Item = (), Error = Error>>

all api calls are functions and you pass in the configuration explicitly. Now, on the consumer side, that sucks. What I want to have is something like:

let api = apis::Client::new(
    apis::Configuration::new(
        Client::configure().connector(http_connector).build(&handle)));

let work = api.pet_api().add_pet(&new_pet)

For that I have this semi-working code:

pub struct Configuration<C: hyper::client::Connect> {
  pub base_path: String,
  pub client: hyper::client::Client<C>,
}

pub trait PetAPI {
    fn add_pet(&self, pet: &models::Pet) -> Box<Future<Item = (), Error = Error>>;
}

pub struct PetAPIImpl<'a, C: hyper::client::Connect> {
    configuration: &'a configuration::Configuration<C>
    // api implementation needs to access the configuration implicitly to keep the ags list clean
}

pub struct APIClient<C: hyper::client::Connect> {
    configuration: Configuration<C>,
    pet_api: Box<pet_api::PetAPI>, // preferably shouldn't be box-ed somehow
    // it's a trait to allow for easier mocking.
}

impl<C: hyper::client::Connect> APIClient<C> {
  pub fn new(configuration: Configuration<C>) -> APIClient<C> {
    let cli = APIClient {
        configuration: configuration,
        pet_api: Box::new(pet_api::PetAPIImpl::new(&cli.configuration))
        // doesn't work at all, how do I pass in the cli's configuration in there?
    };
    cli
  }

How do I solve the circular-reference on Configuration without RefCell? It that at all possible given there is a strong guarantee that pet_api will not outlive the client?


#2

Note that as written, there would be nothing preventing safe code from doing either of

  • moving the entire APIClient, including the contained Configuration, thereby invalidating the pointer passed to PetAPIImpl; or
  • swapping out an APIClient's pet_api with another one, then holding on to the original after destroying the APIClient.

In fact, your code does the former itself, when moving the APIClient from APIClient::new to its caller.

Easiest solution is to just use Rc, but if you want to explore more advanced borrowing stuff, check out the rental crate. However, I’m not sure that can actually save you any overhead in this scenario.