Skip to content

Commit b9eb2dd

Browse files
committed
Rewrite ApiClient & Sendable with Request & Response traits
1 parent f4c0413 commit b9eb2dd

File tree

4 files changed

+181
-91
lines changed

4 files changed

+181
-91
lines changed

src/build/client_mod.hbs

+137-65
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11

22
pub mod client \{
3-
use futures::\{Future, future};
4-
use futures::stream::Stream;
3+
use failure::Fail;
4+
use futures::\{Stream, Future};
5+
use futures_preview::compat::Future01CompatExt;
56
use parking_lot::Mutex;
6-
use reqwest::r#async::\{Decoder, Response};
7-
use serde::de::DeserializeOwned;
7+
8+
use std::fmt::Debug;
89

910
/// Common API errors.
1011
#[derive(Debug, Fail)]
11-
pub enum ApiError \{
12+
pub enum ApiError<R: Debug + Send + 'static> \{
1213
#[fail(display = "API request failed for path: \{} (code: \{})", _0, _1)]
13-
Failure(String, reqwest::StatusCode, Mutex<Response>),
14+
Failure(String, http::status::StatusCode, Mutex<R>),
1415
#[fail(display = "Unsupported media type in response: \{}", _0)]
15-
UnsupportedMediaType(String, Mutex<Response>),
16+
UnsupportedMediaType(String, Mutex<R>),
1617
#[fail(display = "An error has occurred while performing the API request: \{}", _0)]
1718
Reqwest(reqwest::Error),
1819
{{- for coder in media_coders }}
@@ -21,39 +22,130 @@ pub mod client \{
2122
{{- endfor }}
2223
}
2324
25+
/// HTTP Request.
26+
pub trait Request \{
27+
/// Sets the header with the given key and value.
28+
fn header(self, name: &'static str, value: &str) -> Self;
29+
30+
/// Sets body using the given vector of bytes.
31+
///
32+
/// **NOTE:** Appropriate `Content-Type` header must be set
33+
/// after calling this method.
34+
fn body_bytes(self, body: Vec<u8>) -> Self;
35+
36+
/// Sets JSON body based on the given value.
37+
fn json<T: serde::Serialize>(self, value: &T) -> Self;
38+
39+
/// Sets/adds query parameters based on the given value.
40+
///
41+
/// **NOTE:** This method must be called only once. It's unspecified
42+
/// as to whether this appends/replaces query parameters.
43+
fn query<T: serde::Serialize>(self, params: &T) -> Self;
44+
}
45+
46+
impl Request for reqwest::r#async::RequestBuilder \{
47+
fn header(self, name: &'static str, value: &str) -> Self \{
48+
reqwest::r#async::RequestBuilder::header(self, name, value)
49+
}
50+
51+
fn body_bytes(self, body: Vec<u8>) -> Self \{
52+
self.body(body)
53+
}
54+
55+
fn json<T: serde::Serialize>(self, value: &T) -> Self \{
56+
reqwest::r#async::RequestBuilder::json(self, value)
57+
}
58+
59+
fn query<T: serde::Serialize>(self, params: &T) -> Self \{
60+
reqwest::r#async::RequestBuilder::query(self, params)
61+
}
62+
}
63+
64+
/// HTTP Response.
65+
#[async_trait::async_trait]
66+
pub trait Response: Debug + Send + Sized \{
67+
type Bytes: AsRef<[u8]>;
68+
69+
/// Gets the value for the given header name, if any.
70+
fn header(&self, name: &'static str) -> Option<&str>;
71+
72+
/// Status code for this response.
73+
fn status(&self) -> http::status::StatusCode;
74+
75+
/// Media type for this response body (if any).
76+
fn media_type(&self) -> Option<mime::MediaType>;
77+
78+
/// Vector of bytes from the response body.
79+
async fn body_bytes(self) -> Result<(Self, Self::Bytes), ApiError<Self>>;
80+
}
81+
82+
#[async_trait::async_trait]
83+
impl Response for reqwest::r#async::Response \{
84+
type Bytes = reqwest::r#async::Chunk;
85+
86+
fn header(&self, name: &'static str) -> Option<&str> \{
87+
self.headers().get(name).and_then(|v| v.to_str().ok())
88+
}
89+
90+
fn status(&self) -> http::status::StatusCode \{
91+
reqwest::r#async::Response::status(self)
92+
}
93+
94+
fn media_type(&self) -> Option<mime::MediaType> \{
95+
self.header(http::header::CONTENT_TYPE.as_str())
96+
.and_then(|v| v.parse().ok())
97+
}
98+
99+
async fn body_bytes(mut self) -> Result<(Self, Self::Bytes), ApiError<Self>> \{
100+
let body = std::mem::replace(self.body_mut(), reqwest::r#async::Decoder::empty());
101+
let bytes = body.concat2().map_err(ApiError::Reqwest).compat().await?;
102+
Ok((self, bytes))
103+
}
104+
}
105+
24106
/// Represents an API client.
107+
#[async_trait::async_trait]
25108
pub trait ApiClient \{
109+
type Request: Request + Send;
110+
type Response: Response;
111+
26112
/// Consumes a method and a relative path and produces a request builder for a single API call.
27-
fn request_builder(&self, method: reqwest::Method, rel_path: &str) -> reqwest::r#async::RequestBuilder;
113+
fn request_builder(&self, method: http::Method, rel_path: &str) -> Self::Request;
28114

29115
/// Performs the HTTP request using the given `Request` object
30116
/// and returns a `Response` future.
31-
fn make_request(&self, req: reqwest::r#async::Request)
32-
-> Box<dyn Future<Item=Response, Error=reqwest::Error> + Send>;
117+
async fn make_request(&self, req: Self::Request) -> Result<Self::Response, ApiError<Self::Response>>;
33118
}
34119

120+
#[async_trait::async_trait]
35121
impl ApiClient for reqwest::r#async::Client \{
36-
#[inline]
37-
fn request_builder(&self, method: reqwest::Method, rel_path: &str) -> reqwest::r#async::RequestBuilder \{
122+
type Request = reqwest::r#async::RequestBuilder;
123+
type Response = reqwest::r#async::Response;
124+
125+
fn request_builder(&self, method: http::Method, rel_path: &str) -> Self::Request \{
38126
let mut u = String::from("{base_url | unescaped}");
39127
u.push_str(rel_path.trim_start_matches('/'));
40128
self.request(method, &u)
41129
}
42130

43-
#[inline]
44-
fn make_request(&self, req: reqwest::r#async::Request)
45-
-> Box<dyn Future<Item=Response, Error=reqwest::Error> + Send> \{
46-
Box::new(self.execute(req)) as Box<_>
131+
async fn make_request(&self, req: Self::Request) -> Result<Self::Response, ApiError<Self::Response>> \{
132+
let req = req.build().map_err(ApiError::Reqwest)?;
133+
let resp = self.execute(req).map_err(ApiError::Reqwest).compat().await?;
134+
Ok(resp)
47135
}
48136
}
49137

50138
/// A trait for indicating that the implementor can send an API call.
51-
pub trait Sendable \{
139+
#[async_trait::async_trait]
140+
pub trait Sendable<Client>
141+
where
142+
Client: ApiClient + Sync + 'static,
143+
\{
52144
/// The output object from this API request.
53-
type Output: DeserializeOwned + Send + 'static;
145+
type Output: serde::de::DeserializeOwned;
54146

55147
/// HTTP method used by this call.
56-
const METHOD: reqwest::Method;
148+
const METHOD: http::Method;
57149

58150
/// Relative URL for this API call formatted appropriately with parameter values.
59151
///
@@ -62,55 +154,41 @@ pub mod client \{
62154

63155
/// Modifier for this object. Builders override this method if they
64156
/// wish to add query parameters, set body, etc.
65-
fn modify(&self, req: reqwest::r#async::RequestBuilder) -> Result<reqwest::r#async::RequestBuilder, ApiError> \{
157+
fn modify(&self, req: Client::Request) -> Result<Client::Request, ApiError<Client::Response>> \{
66158
Ok(req)
67159
}
68160

69161
/// Sends the request and returns a future for the response object.
70-
fn send(&self, client: &dyn ApiClient) -> Box<dyn Future<Item=Self::Output, Error=ApiError> + Send> \{
71-
Box::new(self.send_raw(client).and_then(|mut resp| -> Box<dyn Future<Item=_, Error=ApiError> + Send> \{
72-
let value = resp.headers().get(reqwest::header::CONTENT_TYPE);
73-
let body_concat = |resp: &mut Response| \{
74-
let body = std::mem::replace(resp.body_mut(), Decoder::empty());
75-
body.concat2().map_err(ApiError::from)
76-
};
77-
78-
if let Some(ty) = value.as_ref()
79-
.and_then(|v| v.to_str().ok())
80-
.and_then(|v| v.parse::<mime::MediaType>().ok())
81-
\{
82-
{{- for coder in media_coders }}
83-
{{ if not @first }}else {{ endif }}if media_types::M_{ @index }.matches(&ty) \{
84-
return Box::new(body_concat(&mut resp).and_then(|v| \{
85-
{coder.decoder | unescaped}(v.as_ref()).map_err(ApiError::from)
86-
})) as Box<_>
87-
}
88-
{{- endfor }}
162+
async fn send(&self, client: &Client) -> Result<Self::Output, ApiError<Client::Response>> \{
163+
let resp = self.send_raw(client).await?;
164+
let media = resp.media_type();
165+
if let Some(ty) = media \{
166+
if media_types::M_0.matches(&ty) \{
167+
let (_, bytes) = resp.body_bytes().await?;
168+
return serde_json::from_reader(bytes.as_ref()).map_err(ApiError::from)
89169
}
170+
else if media_types::M_1.matches(&ty) \{
171+
let (_, bytes) = resp.body_bytes().await?;
172+
return serde_yaml::from_reader(bytes.as_ref()).map_err(ApiError::from)
173+
}
174+
}
90175

91-
let ty = value
92-
.map(|v| String::from_utf8_lossy(v.as_bytes()).into_owned())
93-
.unwrap_or_default();
94-
Box::new(futures::future::err(ApiError::UnsupportedMediaType(ty, Mutex::new(resp)))) as Box<_>
95-
})) as Box<_>
176+
let ty = resp.header(http::header::CONTENT_TYPE.as_str())
177+
.map(|v| String::from_utf8_lossy(v.as_bytes()).into_owned())
178+
.unwrap_or_default();
179+
Err(ApiError::UnsupportedMediaType(ty, Mutex::new(resp)))
96180
}
97181

98182
/// Convenience method for returning a raw response after sending a request.
99-
fn send_raw(&self, client: &dyn ApiClient) -> Box<dyn Future<Item=Response, Error=ApiError> + Send> \{
183+
async fn send_raw(&self, client: &Client) -> Result<Client::Response, ApiError<Client::Response>> \{
100184
let rel_path = self.rel_path();
101-
let builder = self.modify(client.request_builder(Self::METHOD, &rel_path));
102-
let req = match builder.and_then(|b| b.build().map_err(ApiError::Reqwest)) \{
103-
Ok(r) => r,
104-
Err(e) => return Box::new(future::err(e)),
105-
};
106-
107-
Box::new(client.make_request(req).map_err(ApiError::Reqwest).and_then(move |resp| \{
108-
if resp.status().is_success() \{
109-
futures::future::ok(resp)
110-
} else \{
111-
futures::future::err(ApiError::Failure(rel_path.into_owned(), resp.status(), Mutex::new(resp)).into())
112-
}
113-
})) as Box<_>
185+
let req = self.modify(client.request_builder(Self::METHOD, &rel_path))?;
186+
let resp = client.make_request(req).await?;
187+
if resp.status().is_success() \{
188+
Ok(resp)
189+
} else \{
190+
Err(ApiError::Failure(rel_path.into_owned(), resp.status(), Mutex::new(resp)))
191+
}
114192
}
115193
}
116194

@@ -124,15 +202,9 @@ pub mod client \{
124202
{{- endfor }}
125203
}
126204
}
127-
128-
impl From<reqwest::Error> for ApiError \{
129-
fn from(e: reqwest::Error) -> Self \{
130-
ApiError::Reqwest(e)
131-
}
132-
}
133205
{{- for coder in media_coders }}
134206

135-
impl From<{coder.error_ty_path | unescaped}> for ApiError \{
207+
impl<R: Response + 'static> From<{coder.error_ty_path | unescaped}> for ApiError<R> \{
136208
fn from(e: {coder.error_ty_path | unescaped}) -> Self \{
137209
ApiError::{coder.error_variant | unescaped}(e)
138210
}

src/build/manifest.hbs

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ path = "main.rs"
1212
path = "lib.rs"
1313
{{ endif }}
1414
[dependencies]
15+
async-trait = "0.1"
1516
failure = "0.1"
1617
futures = "0.1"
18+
futures-preview = \{ version = "0.3.0-alpha.16", features = ["compat"], package = "futures-preview" }
19+
http = "0.1"
1720
lazy_static = "1.4"
1821
log = "0.4"
1922
mime = \{ git = "https://github.com/hyperium/mime" }
@@ -26,7 +29,6 @@ url = "2.1"
2629
{{ if is_cli }}
2730
clap = \{ version = "2.33", features = ["yaml"] }
2831
env_logger = "0.6"
29-
futures-preview = \{ version = "0.3.0-alpha.16", features = ["compat"], package = "futures-preview" }
3032
humantime = "1.2"
3133
openssl = \{ version = "0.10", features = ["vendored"] }
3234
runtime = "0.3.0-alpha.7"

src/v2/codegen/impls.rs

+30-15
Original file line numberDiff line numberDiff line change
@@ -636,14 +636,17 @@ impl<'a, 'b> SendableCodegen<'a, 'b> {
636636
_ => return Ok(()),
637637
};
638638

639-
f.write_str("\nimpl")?;
639+
f.write_str("\nimpl<Client: ")?;
640+
f.write_str(self.builder.helper_module_prefix)?;
641+
f.write_str("client::ApiClient + Sync + 'static")?;
642+
640643
if self.builder.needs_any {
641-
f.write_str("<Any: serde::Serialize>")?;
644+
f.write_str(", Any: serde::Serialize")?;
642645
}
643646

644-
f.write_str(" ")?;
647+
f.write_str(">")?;
645648
f.write_str(self.builder.helper_module_prefix)?;
646-
f.write_str("client::Sendable for ")?;
649+
f.write_str("client::Sendable<Client> for ")?;
647650
self.builder.write_name(f)?;
648651
self.builder
649652
.write_generics_if_necessary(f, None, TypeParameters::ChangeAll)?;
@@ -681,7 +684,7 @@ impl<'a, 'b> SendableCodegen<'a, 'b> {
681684
f.write_str(">")?;
682685
}
683686

684-
f.write_str(";\n\n const METHOD: reqwest::Method = reqwest::Method::")?;
687+
f.write_str(";\n\n const METHOD: http::Method = http::Method::")?;
685688
f.write_str(&method.to_string().to_uppercase())?;
686689
f.write_str(";\n\n fn rel_path(&self) -> std::borrow::Cow<'static, str> {\n ")?;
687690

@@ -736,7 +739,7 @@ impl<'a, 'b> SendableCodegen<'a, 'b> {
736739
fn handle_header_param(&mut self, field: StructField) {
737740
let is_required = field.prop.is_required();
738741
let name = field.name.to_snek_case();
739-
let mut param_ref = String::from("self.");
742+
let mut param_ref = String::from("&self.");
740743
if self.needs_container {
741744
param_ref.push_str("inner.");
742745
}
@@ -761,7 +764,7 @@ impl<'a, 'b> SendableCodegen<'a, 'b> {
761764
self.headers,
762765
"req = req.header({:?}, {});",
763766
&field.name,
764-
if is_required { &param_ref } else { "v" }
767+
if is_required { &param_ref } else { "&v" }
765768
);
766769

767770
if !is_required {
@@ -837,9 +840,13 @@ impl<'a, 'b> SendableCodegen<'a, 'b> {
837840
where
838841
F: Write,
839842
{
840-
f.write_str("\n\n fn modify(&self, req: reqwest::r#async::RequestBuilder) -> Result<reqwest::r#async::RequestBuilder, ")?;
843+
f.write_str("\n\n fn modify(&self, req: Client::Request) -> Result<Client::Request, ")?;
844+
f.write_str(&self.builder.helper_module_prefix)?;
845+
f.write_str("client::ApiError<Client::Response>> {")?;
846+
f.write_str("\n use ")?;
841847
f.write_str(&self.builder.helper_module_prefix)?;
842-
f.write_str("client::ApiError> {")?;
848+
f.write_str("client::Request;")?;
849+
843850
if !self.headers.is_empty() {
844851
f.write_str("\n let mut req = req;")?;
845852
f.write_str(&self.headers)?;
@@ -850,10 +857,14 @@ impl<'a, 'b> SendableCodegen<'a, 'b> {
850857
if self.builder.body_required {
851858
f.write_str("\n ")?;
852859
if let Some((range, coder)) = self.builder.encoding {
853-
write!(f, ".header(reqwest::header::CONTENT_TYPE, {:?})", range)?;
860+
write!(
861+
f,
862+
".header(http::header::CONTENT_TYPE.as_str(), {:?})",
863+
range
864+
)?;
854865

855866
f.write_str(
856-
"\n .body({
867+
"\n .body_bytes({
857868
let mut vec = vec![];
858869
",
859870
)?;
@@ -876,14 +887,18 @@ impl<'a, 'b> SendableCodegen<'a, 'b> {
876887
}
877888

878889
if let Some(r) = accepted_range {
879-
write!(f, "\n .header(reqwest::header::ACCEPT, {:?})", r)?;
890+
write!(
891+
f,
892+
"\n .header(http::header::ACCEPT.as_str(), {:?})",
893+
r
894+
)?;
880895
}
881896

882897
if !self.form.is_empty() {
883-
write!(f, "\n .header(reqwest::header::CONTENT_TYPE, \"application/x-www-form-urlencoded\")")?;
884-
f.write_str("\n .body({\n let mut ser = url::form_urlencoded::Serializer::new(String::new());")?;
898+
f.write_str("\n .body_bytes({\n let mut ser = url::form_urlencoded::Serializer::new(String::new());")?;
885899
f.write_str(&self.form)?;
886-
f.write_str("\n ser.finish()\n })")?;
900+
f.write_str("\n ser.finish().into_bytes()\n })")?;
901+
write!(f, "\n .header(http::header::CONTENT_TYPE.as_str(), \"application/x-www-form-urlencoded\")")?;
887902
}
888903

889904
if !self.query.is_empty() {

0 commit comments

Comments
 (0)