origin_asset/
transport.rs1use reqwest::{Client, Method, Response};
2use serde::de::DeserializeOwned;
3use serde_json::Value;
4
5use crate::error::{OriginError, Result};
6
7#[derive(Debug, Clone)]
9pub struct HttpTransport {
10 client: Client,
11 base_url: String,
12 api_key: String,
13}
14
15impl HttpTransport {
16 pub fn new(base_url: impl Into<String>, api_key: impl Into<String>) -> Self {
17 Self {
18 client: Client::new(),
19 base_url: base_url.into().trim_end_matches('/').to_string(),
20 api_key: api_key.into(),
21 }
22 }
23
24 pub fn with_client(mut self, client: Client) -> Self {
26 self.client = client;
27 self
28 }
29
30 pub fn client(&self) -> &Client {
31 &self.client
32 }
33
34 pub fn base_url(&self) -> &str {
35 &self.base_url
36 }
37
38 pub async fn request<T: DeserializeOwned>(
43 &self,
44 method: Method,
45 path: &str,
46 body: Option<&Value>,
47 ) -> Result<T> {
48 let url = format!("{}{}", self.base_url, path);
49
50 let mut builder = self.client.request(method, &url).bearer_auth(&self.api_key);
51
52 if let Some(body) = body {
53 builder = builder.json(body);
54 }
55
56 let response = builder.send().await?;
57 self.parse_response(response).await
58 }
59
60 pub async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
62 self.request(Method::GET, path, None).await
63 }
64
65 pub async fn post<T: DeserializeOwned>(&self, path: &str, body: &Value) -> Result<T> {
67 self.request(Method::POST, path, Some(body)).await
68 }
69
70 pub async fn delete<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
72 self.request(Method::DELETE, path, None).await
73 }
74
75 pub async fn post_multipart<T: DeserializeOwned>(
77 &self,
78 path: &str,
79 form: reqwest::multipart::Form,
80 ) -> Result<T> {
81 let url = format!("{}{}", self.base_url, path);
82
83 let response = self
84 .client
85 .post(&url)
86 .bearer_auth(&self.api_key)
87 .multipart(form)
88 .send()
89 .await?;
90
91 self.parse_response(response).await
92 }
93
94 async fn parse_response<T: DeserializeOwned>(&self, response: Response) -> Result<T> {
96 let status = response.status().as_u16();
97 let text = response.text().await?;
98
99 if text.is_empty() {
100 return Err(OriginError::api(status, "empty response body"));
101 }
102
103 let payload: Value = serde_json::from_str(&text).map_err(|_| {
104 OriginError::api(status, format!("invalid JSON: {}", truncate(&text, 256)))
105 })?;
106
107 if let Some(ok) = payload.get("ok").and_then(|v| v.as_bool()) {
109 if ok {
110 let data = payload.get("data").cloned().unwrap_or(Value::Null);
111 let result = serde_json::from_value(data)?;
112 return Ok(result);
113 }
114
115 let command = payload
116 .get("command")
117 .and_then(|v| v.as_str())
118 .map(String::from);
119 let error = payload.get("error").cloned().unwrap_or(Value::Null);
120 let (code, message) = parse_error_payload(&error);
121 return Err(OriginError::api_full(status, code, message, command));
122 }
123
124 if status >= 400 {
125 return Err(OriginError::api(status, truncate(&text, 512).to_string()));
126 }
127
128 let result = serde_json::from_value(payload)?;
129 Ok(result)
130 }
131}
132
133fn parse_error_payload(error: &Value) -> (Option<String>, String) {
134 match error {
135 Value::String(s) => (None, s.clone()),
136 Value::Object(map) => {
137 let code = map.get("code").and_then(|v| v.as_str()).map(String::from);
138 let message = map
139 .get("message")
140 .and_then(|v| v.as_str())
141 .unwrap_or("unknown error")
142 .to_string();
143 (code, message)
144 }
145 _ => (None, error.to_string()),
146 }
147}
148
149fn truncate(s: &str, max: usize) -> &str {
150 if s.len() <= max {
151 s
152 } else {
153 &s[..max]
154 }
155}