octocrate_core/
request.rs1#[cfg(feature = "file-body")]
2use crate::utils::file_to_body;
3use crate::{
4 api_config::SharedAPIConfig,
5 error::{APIErrorResponse, Error},
6 request_builder::RequestBuilder,
7 response::GitHubResponse,
8};
9#[cfg(feature = "multipart")]
10use reqwest::multipart::Form;
11use std::marker::PhantomData;
12#[cfg(feature = "file-body")]
13use tokio::fs::File;
14
15pub struct Request<Body, Query, Response> {
16 pub(crate) builder: reqwest::RequestBuilder,
17 pub(crate) api_config: SharedAPIConfig,
18 pub(crate) _body: PhantomData<Body>,
19 pub(crate) _query: PhantomData<Query>,
20 pub(crate) _response: PhantomData<Response>,
21}
22
23impl<Body, Query, ResponseData> Request<Body, Query, ResponseData>
24where
25 Body: serde::Serialize,
26 Query: serde::Serialize,
27 ResponseData: serde::de::DeserializeOwned,
28{
29 pub fn builder(config: &SharedAPIConfig) -> RequestBuilder<Body, Query, ResponseData> {
30 RequestBuilder::new(config)
31 }
32
33 pub fn query(mut self, query: &Query) -> Self {
34 self.builder = self.builder.query(query);
35
36 self
37 }
38
39 #[cfg(feature = "multipart")]
40 pub fn multipart(mut self, form: Form) -> Self {
41 self.builder = self.builder.multipart(form);
42
43 self
44 }
45
46 pub fn header<K, V>(mut self, key: K, value: V) -> Self
47 where
48 K: Into<String>,
49 V: Into<String>,
50 {
51 let key = key.into();
52 let value = value.into();
53 self.builder = self.builder.header(key, value);
54
55 self
56 }
57
58 #[cfg(feature = "file-body")]
59 pub fn file(mut self, file: File) -> Self {
60 self.builder = self.builder.body(file_to_body(file));
61
62 self
63 }
64
65 pub fn body(mut self, body: &Body) -> Self {
66 self.builder = self.builder.json(body);
67
68 self
69 }
70
71 pub async fn send_with_response(self) -> Result<GitHubResponse<ResponseData>, Error> {
74 let mut builder = self
75 .builder
76 .header("User-Agent", "octocrate")
77 .header("Accept", "application/vnd.github+json");
78
79 if let Some(token) = &self.api_config.token {
80 if let Some(token) = token.get_token() {
81 builder = builder.header("Authorization", format!("Bearer {}", token));
82 }
83 }
84
85 let res = builder.send().await;
86 match res {
87 Ok(res) => {
88 let status = res.status();
89 let content_length = res.content_length();
90 let headers = res.headers().clone();
91 let version = res.version();
92 let url = res.url().clone();
93
94 if !status.is_success() {
95 if let Ok(error_response) = res.json::<APIErrorResponse>().await {
96 return Err(Error::RequestFailed(error_response));
97 }
98
99 let err_msg = format!("Request failed with {}", status);
100 return Err(Error::Error(err_msg));
101 }
102
103 let res = res.text().await.map_err(|err| {
104 Error::Error(format!(
105 "Failed to read response with status {}: {}",
106 status, err
107 ))
108 })?;
109
110 match serde_json::from_str(&res) {
111 Ok(data) => {
112 let github_response = GitHubResponse {
113 content_length,
114 data,
115 headers,
116 status,
117 url,
118 version,
119 };
120 Ok(github_response)
121 }
122 Err(error) => Err(Error::Error(format!(
123 r#"Failed to parse response with status {}: {}
124
125 Response: {}"#,
126 status, error, res
127 ))),
128 }
129 }
130 Err(err) => Err(Error::Error(err.to_string())),
131 }
132 }
133
134 pub async fn send(self) -> Result<ResponseData, Error> {
135 Ok(self.send_with_response().await?.data)
136 }
137}
138
139#[cfg(feature = "pagination")]
140impl<Body, Query, ResponseData> Request<Body, Query, ResponseData>
141where
142 Body: serde::Serialize,
143 Query: serde::Serialize,
144 ResponseData: serde::de::DeserializeOwned + IntoIterator,
145{
146 pub async fn paginated_send(self) -> Result<octocrate_types::PaginatedData<ResponseData>, Error> {
147 Ok(self.paginated_send_with_response().await?.paginate())
148 }
149
150 pub async fn paginated_send_with_response(
151 self,
152 ) -> Result<crate::GitHubPaginatedResponse<ResponseData>, Error> {
153 Ok(self.send_with_response().await?.into())
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::api_config::APIConfig;
161
162 #[tokio::test]
163 async fn test_request_builder() {
164 let config = APIConfig::default().shared();
165
166 #[derive(serde::Serialize)]
167 struct Query {
168 page: u32,
169 }
170
171 #[derive(serde::Serialize)]
172 struct Body {
173 name: String,
174 }
175
176 #[derive(serde::Deserialize)]
177 struct Response {
178 full_name: String,
179 }
180
181 let request = Request::<Body, Query, Response>::builder(&config)
182 .get("/repos/panghu-huang/octocrate")
183 .build();
184
185 let response = request.query(&Query { page: 1 }).send().await.unwrap();
186
187 assert_eq!(response.full_name, "panghu-huang/octocrate");
188 }
189}