Hi,
I am writing some API wrapper for which there are already alternatives, but just as a way to practice Rust, I wonder how it could be built so that the usage becomes as such:
It's certainly doable with some sort of code generator fed with an openapi spec or something like that. There are probably many tools out there that may fit your need.
..::info.get() is not legal Rust. It could be either ..::info::get() or ..::info().get() or ..::info{..}.get()
You'd need to map (hardcode) all possible url parts to modules. There is no handler for "some module path user imported" like you could do in ie. Python.
Modules do not need to reflect fs. You can write
mod Example {
mod api {
mod v1 { pub fn info() {...} ... }
}
}
this looks good, thanks.
But say Example would be a struct, and I would need the info function to mutate a field in Example, would that still be possible with this solution .3?
i mean say you would have it like : let client = Example::new(key);
and then be able to use it as such:
client.api.v1.info().get();
I know most api wrappers just have their methods listed flat. But, this API i'm using is strange, has overlaps, has methods that return different types, some endpoints are the same name but use put or post.
The motivation is the call chain to be 1:1 like the endpoint path.
..::info.get() is not legal Rust. It could be either ..::info::get() or ..::info().get() or ..::info{..}.get()
It is totally legal rust and is even used extensively for diesel DSLs. You just need to use struct info: playground:
use core::future::Future;
use std::io;
extern crate tokio;
pub mod api {
use core::future::Future;
use std::io;
use super::Request;
#[allow(non_camel_case_types)]
pub struct info;
impl Request for info {
type GetResult = ();
fn get(self) -> impl Future<Output = io::Result<Self::GetResult>> {
async {
Ok(())
}
}
}
}
trait Request {
type GetResult;
fn get(self) -> impl Future<Output = io::Result<Self::GetResult>>;
}
#[tokio::main]
async fn main() -> io::Result<()> {
api::info.get().await?;
Ok(())
}
However I do not know how to make ::api::v1() and ::api::v1::... work at the same time, so Example::api::v1().info(params).get().await needs to be given up.
However if state is indeed needed this approach is not a good idea. client.api.v1.info().get(); variant is definitely theoretically possible, but practical variant should probably be something like either Example::api::v1::info.get(client) or client.api().v1().info().get().
I've prepared this version, which I think is close to what you're asking. I think the code is a little bit unwieldy and I'd prefer simpler alternatives, but here you go:
thanks, this looks very good. I wonder if we had the API_KEY only stored in Client could Info still access it? Because now api_key is a field in two structs.
I would have implemented such API by dragging reference to the key (or, better, Client) everywhere and not the key itself, but your options here are basically:
Some kind of task-local variable. E.g. this. I have not used it, but it does not look very convenient.
Drag something everywhere: reference if you want key (or the whole client) be reusable, owned value if not.
Since that extra field is not visible to the user dragging it everywhere should be OK. You can also do something like this to have reference in one struct only.