oci_rust_sdk/core/client/
http_client.rs1use crate::auth::provider::AuthProvider;
2use crate::auth::signer::RequestSigner;
3use crate::core::error::{OciError, ServiceErrorResponse};
4use crate::core::retry::Retrier;
5use reqwest::Method;
6use serde::de::DeserializeOwned;
7use std::sync::Arc;
8
9pub struct OciClient {
11 client: reqwest::Client,
12 signer: RequestSigner,
13 endpoint: String,
14 retrier: Retrier,
15}
16
17impl OciClient {
18 pub fn new(
24 auth_provider: Arc<dyn AuthProvider>,
25 endpoint: String,
26 ) -> crate::core::Result<Self> {
27 Self::with_timeout(auth_provider, endpoint, std::time::Duration::from_secs(30))
28 }
29
30 pub fn with_timeout(
31 auth_provider: Arc<dyn AuthProvider>,
32 endpoint: String,
33 timeout: std::time::Duration,
34 ) -> crate::core::Result<Self> {
35 let client = reqwest::Client::builder()
36 .user_agent(format!("oci-rust-sdk/{}", env!("CARGO_PKG_VERSION")))
37 .timeout(timeout)
38 .gzip(true)
39 .build()
40 .map_err(OciError::HttpError)?;
41
42 let signer = RequestSigner::new(auth_provider)?;
43 let retrier = Retrier::new();
44
45 Ok(Self {
46 client,
47 signer,
48 endpoint,
49 retrier,
50 })
51 }
52
53 pub fn with_retrier(mut self, retrier: Retrier) -> Self {
55 self.retrier = retrier;
56 self
57 }
58
59 pub async fn get<T>(&self, path: &str) -> crate::core::Result<OciResponse<T>>
61 where
62 T: DeserializeOwned,
63 {
64 self.request(Method::GET, path, None::<&()>).await
65 }
66
67 pub async fn post<B, T>(
69 &self,
70 path: &str,
71 body: Option<&B>,
72 ) -> crate::core::Result<OciResponse<T>>
73 where
74 B: serde::Serialize,
75 T: DeserializeOwned,
76 {
77 self.request(Method::POST, path, body).await
78 }
79
80 pub async fn put<B, T>(
82 &self,
83 path: &str,
84 body: Option<&B>,
85 ) -> crate::core::Result<OciResponse<T>>
86 where
87 B: serde::Serialize,
88 T: DeserializeOwned,
89 {
90 self.request(Method::PUT, path, body).await
91 }
92
93 pub async fn delete<T>(&self, path: &str) -> crate::core::Result<OciResponse<T>>
95 where
96 T: DeserializeOwned,
97 {
98 self.request(Method::DELETE, path, None::<&()>).await
99 }
100
101 async fn request<B, T>(
103 &self,
104 method: Method,
105 path: &str,
106 body: Option<&B>,
107 ) -> crate::core::Result<OciResponse<T>>
108 where
109 B: serde::Serialize,
110 T: DeserializeOwned,
111 {
112 let body_bytes = if let Some(b) = body {
114 Some(serde_json::to_vec(b)?)
115 } else {
116 None
117 };
118
119 self.retrier
121 .execute_with_retry(|| {
122 let body_ref = body_bytes.as_deref();
123 self.execute_request(method.clone(), path, body_ref)
124 })
125 .await
126 }
127
128 async fn execute_request<T>(
130 &self,
131 method: Method,
132 path: &str,
133 body: Option<&[u8]>,
134 ) -> crate::core::Result<OciResponse<T>>
135 where
136 T: DeserializeOwned,
137 {
138 let url = format!("{}{}", self.endpoint, path);
140 let parsed_url =
141 url::Url::parse(&url).map_err(|e| OciError::Other(format!("Invalid URL: {}", e)))?;
142
143 let mut request_builder = self.client.request(method.clone(), url);
145
146 if let Some(body_bytes) = body {
148 request_builder = request_builder.body(body_bytes.to_vec());
149 }
150
151 let mut request = request_builder.build().map_err(OciError::HttpError)?;
153
154 self.signer
156 .sign_request(method.as_str(), &parsed_url, request.headers_mut(), body)?;
157
158 let response = self.client.execute(request).await?;
160
161 let status = response.status();
163 let headers = response.headers().clone();
164
165 if !status.is_success() {
166 let error_text = response.text().await?;
168
169 let error = if let Ok(service_error) =
170 serde_json::from_str::<ServiceErrorResponse>(&error_text)
171 {
172 OciError::from_response(status.as_u16(), service_error.code, service_error.message)
173 } else {
174 OciError::from_response(status.as_u16(), "Unknown".to_string(), error_text)
175 };
176
177 return Err(error);
178 }
179
180 let body = if status == reqwest::StatusCode::NO_CONTENT {
182 serde_json::from_str("{}")?
184 } else {
185 let bytes = match response.bytes().await {
186 Ok(b) => b,
187 Err(e) => {
188 eprintln!("OCI_DIAG body.bytes() failed: {:?}", e);
189 return Err(OciError::HttpError(e));
190 }
191 };
192 match serde_json::from_slice(&bytes) {
193 Ok(body) => body,
194 Err(e) => {
195 let preview: String =
196 String::from_utf8_lossy(&bytes).chars().take(2000).collect();
197 eprintln!(
198 "OCI_DIAG json parse failed: {} | len={} | body_preview={}",
199 e,
200 bytes.len(),
201 preview
202 );
203 return Err(OciError::SerdeError(e));
204 }
205 }
206 };
207
208 Ok(OciResponse { body, headers })
209 }
210}
211
212pub struct OciResponse<T> {
214 pub body: T,
215 pub headers: reqwest::header::HeaderMap,
216}
217
218impl<T> OciResponse<T> {
219 pub fn get_header(&self, name: &str) -> Option<String> {
221 self.headers
222 .get(name)
223 .and_then(|v| v.to_str().ok())
224 .map(|s| s.to_string())
225 }
226
227 pub fn request_id(&self) -> Option<String> {
229 self.get_header("opc-request-id")
230 }
231
232 pub fn next_page(&self) -> Option<String> {
234 self.get_header("opc-next-page")
235 }
236}