Hello I am implementing a Rust application with Leptos. I am experimenting a lot, and learning at the same time, so I am doing things that probably are not the best, but I want to solve problems I see. The question is pure theoretical. I am not using the below client in my production. It's just interesting case for me.
I have the following implementation
use serde::{Deserialize, de::DeserializeOwned, Serialize};
pub trait ApiResourceQuery {
fn path(&self) -> &str;
}
pub struct ApiRequestBuilder<'a, Q, H, H2> {
query: Q,
client: Option<&'a reqwest::Client>, // client is marked as optional only for this example
token: Option<&'a str>,
on_begin_func: Option<H>,
on_finish_func: Option<H2>,
}
impl<'a, Q, H, H2> ApiRequestBuilder<'a, Q, H, H2>
where
Q: ApiResourceQuery + Serialize,
H: FnOnce(),
H2: FnOnce(),
{
pub fn new(query: Q) -> Self {
ApiRequestBuilder {
query,
client: None,
token: None,
on_begin_func: None,
on_finish_func: None,
}
}
pub fn with_token(mut self, token: &'a str) -> Self {
self.token = Some(token);
self
}
pub fn with_client(mut self, client: &'a reqwest::Client) -> Self {
self.client = Some(client);
self
}
pub fn on_begin(mut self, func: H) -> Self {
self.on_begin_func = Some(func);
self
}
pub fn on_finish(mut self, func: H2) -> Self {
self.on_finish_func = Some(func);
self
}
pub /* async */ fn execute<R>(self, on_successful: impl FnOnce(R))
where
R: DeserializeOwned + Default, // default is temporary only for this example
{
if let Some(handler) = self.on_begin_func {
handler();
}
let res: Result<R, ()> = Ok(R::default());
on_successful(res.unwrap()); // unwrap only for the example purposes
if let Some(handler) = self.on_finish_func {
handler();
}
}
}
#[derive(Serialize, Deserialize)]
struct GetUserQuery {}
impl ApiResourceQuery for GetUserQuery {
fn path(&self) -> &str {
"/api/resource"
}
}
#[derive(Serialize, Deserialize, Debug)]
struct GetUserResponse {
pub username: String
}
impl Default for GetUserResponse {
fn default() -> Self {
Self {
username: "default username".into(),
}
}
}
And I can use it in the following way:
fn main() {
// Usecase 1
ApiRequestBuilder::new(GetUserQuery{})
.on_begin(||println!("On begin handler1"))
.on_finish(||println!("On finish handler1"))
.execute(|user: GetUserResponse| println!("On successful1: {:?}", user));
// Usecase 2
// <'a, Q, H, H2>
ApiRequestBuilder::<'_, _, _, fn()>::new(GetUserQuery{})
.on_begin(||println!("On begin handler2"))
.execute(|user: GetUserResponse| println!("On successful2: {:?}", user));
// Usecase 3 - won't compile
// <'a, Q, H, H2>
ApiRequestBuilder::new(GetUserQuery{})
.on_begin(||println!("On begin handler3"))
.execute(|user: GetUserResponse| println!("On successful3: {:?}", user));
}
The problem is in use-case 3 it won't compile because of the following error:
error[E0284]: type annotations needed
--> src/main.rs:111:5
|
111 | ApiRequestBuilder::new(GetUserQuery{})
| ^^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `H2` declared on the struct `ApiRequestBuilder`
|
= note: cannot satisfy `<_ as FnOnce<()>>::Output == ()`
note: required by a bound in `ApiRequestBuilder::<'a, Q, H, H2>::new`
--> src/main.rs:19:9
|
19 | H2: FnOnce(),
| ^^^^^^^^ required by this bound in `ApiRequestBuilder::<'a, Q, H, H2>::new`
20 | {
21 | pub fn new(query: Q) -> Self {
| --- required by a bound in this associated function
help: consider specifying the generic arguments
|
111 | ApiRequestBuilder::<GetUserQuery, _, H2>(GetUserQuery{})
| ~~~~~~~~~~~~~~~~~~~~~~~
Is there a simple way I can provide default values when I am want to skip one callback? Can they be defined during struct definition? Here example is simple but what if I have 2 more callback, let's say:
on_expected_error
on_unexpected_error
It is not very intuitive to annotation with 5 types when I want to call.
You can do it by specifying a default type for new()
:
pub struct ApiRequestBuilder<'a, Q, H, H2> {
query: Q,
client: Option<&'a reqwest::Client>, // client is marked as optional only for this example
token: Option<&'a str>,
on_begin_func: Option<H>,
on_finish_func: Option<H2>,
}
impl<'a, Q> ApiRequestBuilder<'a, Q, fn(), fn()>
where
Q: ApiResourceQuery + Serialize,
{
pub fn new(query: Q) -> Self {
ApiRequestBuilder {
query,
client: None,
token: None,
on_begin_func: None,
on_finish_func: None,
}
}
}
impl<'a, Q, H, H2> ApiRequestBuilder<'a, Q, H, H2>
where
Q: ApiResourceQuery + Serialize,
H: FnOnce(),
H2: FnOnce(),
{
pub fn with_token(mut self, token: &'a str) -> Self {
self.token = Some(token);
self
}
pub fn with_client(mut self, client: &'a reqwest::Client) -> Self {
self.client = Some(client);
self
}
pub fn on_begin<NewH: FnOnce()>(self, func: NewH) -> ApiRequestBuilder<'a, Q, NewH, H2> {
ApiRequestBuilder {
query: self.query,
client: self.client,
token: self.token,
on_begin_func: Some(func),
on_finish_func: self.on_finish_func,
}
}
pub fn on_finish<NewH2: FnOnce()>(self, func: NewH2) -> ApiRequestBuilder<'a, Q, H, NewH2> {
ApiRequestBuilder {
query: self.query,
client: self.client,
token: self.token,
on_begin_func: self.on_begin_func,
on_finish_func: Some(func),
}
}
pub fn execute<R>(self, on_successful: impl FnOnce(R))
where
R: DeserializeOwned + Default, // default is temporary only for this example
{
if let Some(handler) = self.on_begin_func {
handler();
}
let res: Result<R, ()> = Ok(R::default());
on_successful(res.unwrap()); // unwrap only for the example purposes
if let Some(handler) = self.on_finish_func {
handler();
}
}
}