diff options
| author | soryu <soryu@soryu.co> | 2026-01-11 05:52:14 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-15 00:21:16 +0000 |
| commit | 87044a747b47bd83249d61a45842c7f7b2eae56d (patch) | |
| tree | ef2000ce79ffcc2723ef841acef5aa1deb1d5378 /makima/src/daemon/api/client.rs | |
| parent | 077820c4167c168072d217a1b01df840463a12a8 (diff) | |
| download | soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.tar.gz soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.zip | |
Contract system
Diffstat (limited to 'makima/src/daemon/api/client.rs')
| -rw-r--r-- | makima/src/daemon/api/client.rs | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/makima/src/daemon/api/client.rs b/makima/src/daemon/api/client.rs new file mode 100644 index 0000000..b27d606 --- /dev/null +++ b/makima/src/daemon/api/client.rs @@ -0,0 +1,129 @@ +//! Base HTTP client for makima API. + +use reqwest::Client; +use serde::{de::DeserializeOwned, Serialize}; +use thiserror::Error; + +/// API client errors. +#[derive(Error, Debug)] +pub enum ApiError { + #[error("HTTP request failed: {0}")] + Request(#[from] reqwest::Error), + + #[error("API error (HTTP {status}): {message}")] + Api { status: u16, message: String }, + + #[error("Failed to parse response: {0}")] + Parse(String), +} + +/// HTTP client for makima API. +pub struct ApiClient { + client: Client, + base_url: String, + api_key: String, +} + +impl ApiClient { + /// Create a new API client. + pub fn new(base_url: String, api_key: String) -> Result<Self, ApiError> { + let client = Client::builder() + .build()?; + + Ok(Self { + client, + base_url: base_url.trim_end_matches('/').to_string(), + api_key, + }) + } + + /// Make a GET request. + pub async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, ApiError> { + let url = format!("{}{}", self.base_url, path); + let response = self.client + .get(&url) + .header("X-Makima-Tool-Key", &self.api_key) + .send() + .await?; + + self.handle_response(response).await + } + + /// Make a POST request with JSON body. + pub async fn post<T: DeserializeOwned, B: Serialize>( + &self, + path: &str, + body: &B, + ) -> Result<T, ApiError> { + let url = format!("{}{}", self.base_url, path); + let response = self.client + .post(&url) + .header("X-Makima-Tool-Key", &self.api_key) + .header("Content-Type", "application/json") + .json(body) + .send() + .await?; + + self.handle_response(response).await + } + + /// Make a POST request without body. + pub async fn post_empty<T: DeserializeOwned>(&self, path: &str) -> Result<T, ApiError> { + let url = format!("{}{}", self.base_url, path); + let response = self.client + .post(&url) + .header("X-Makima-Tool-Key", &self.api_key) + .send() + .await?; + + self.handle_response(response).await + } + + /// Make a PUT request with JSON body. + pub async fn put<T: DeserializeOwned, B: Serialize>( + &self, + path: &str, + body: &B, + ) -> Result<T, ApiError> { + let url = format!("{}{}", self.base_url, path); + let response = self.client + .put(&url) + .header("X-Makima-Tool-Key", &self.api_key) + .header("Content-Type", "application/json") + .json(body) + .send() + .await?; + + self.handle_response(response).await + } + + /// Handle API response. + async fn handle_response<T: DeserializeOwned>( + &self, + response: reqwest::Response, + ) -> Result<T, ApiError> { + let status = response.status(); + let status_code = status.as_u16(); + + if !status.is_success() { + let body = response.text().await.unwrap_or_default(); + return Err(ApiError::Api { + status: status_code, + message: body, + }); + } + + let body = response.text().await?; + + // Handle empty responses + if body.is_empty() || body == "null" { + // Try to parse empty/null as the target type + serde_json::from_str::<T>("null") + .or_else(|_| serde_json::from_str::<T>("{}")) + .map_err(|e| ApiError::Parse(e.to_string())) + } else { + serde_json::from_str::<T>(&body) + .map_err(|e| ApiError::Parse(format!("{}: {}", e, body))) + } + } +} |
