Skip to main content

oramacore_client/
client.rs

1//! HTTP client for Orama API operations.
2
3use std::collections::HashMap;
4use std::sync::Arc;
5
6use reqwest::{Client as ReqwestClient, Method, Response};
7use serde::de::DeserializeOwned;
8use serde::Serialize;
9use url::Url;
10
11use crate::auth::{Auth, Target};
12use crate::error::{OramaError, Result};
13
14/// API key position in the request
15#[derive(Debug, Clone, PartialEq)]
16pub enum ApiKeyPosition {
17    Header,
18    QueryParams,
19}
20
21/// Client request configuration
22#[derive(Debug)]
23pub struct ClientRequest<T> {
24    pub target: Target,
25    pub method: Method,
26    pub path: String,
27    pub api_key_position: ApiKeyPosition,
28    pub body: Option<T>,
29    pub params: Option<HashMap<String, String>>,
30}
31
32impl<T> ClientRequest<T> {
33    /// Create a new GET request
34    pub fn get(path: String, target: Target, api_key_position: ApiKeyPosition) -> Self {
35        Self {
36            target,
37            method: Method::GET,
38            path,
39            api_key_position,
40            body: None,
41            params: None,
42        }
43    }
44
45    /// Create a new POST request
46    pub fn post(path: String, target: Target, api_key_position: ApiKeyPosition, body: T) -> Self {
47        Self {
48            target,
49            method: Method::POST,
50            path,
51            api_key_position,
52            body: Some(body),
53            params: None,
54        }
55    }
56
57    /// Add query parameters
58    pub fn with_params(mut self, params: HashMap<String, String>) -> Self {
59        self.params = Some(params);
60        self
61    }
62
63    /// Add a single query parameter
64    pub fn with_param<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
65        let mut params = self.params.unwrap_or_default();
66        params.insert(key.into(), value.into());
67        self.params = Some(params);
68        self
69    }
70}
71
72/// HTTP client for Orama API
73#[derive(Debug, Clone)]
74pub struct OramaClient {
75    client: Arc<ReqwestClient>,
76    auth: Auth,
77}
78
79impl OramaClient {
80    /// Create a new Orama client
81    pub fn new(auth: Auth) -> Result<Self> {
82        let client = ReqwestClient::builder()
83            .user_agent("oramacore-client-rust/1.2.0")
84            .build()?;
85
86        Ok(Self {
87            client: Arc::new(client),
88            auth,
89        })
90    }
91
92    /// Make a request and return the deserialized response
93    pub async fn request<T, R>(&self, req: ClientRequest<T>) -> Result<R>
94    where
95        T: Serialize,
96        R: DeserializeOwned,
97    {
98        let response = self.get_response(req).await?;
99
100        if !response.status().is_success() {
101            let status = response.status().as_u16();
102            let text = response.text().await.unwrap_or_default();
103
104            return Err(match status {
105                401 => OramaError::auth("Unauthorized: are you using the correct API Key?"),
106                400 => OramaError::api(status, format!("Bad Request: {text}")),
107                _ => OramaError::api(status, text),
108            });
109        }
110
111        // Use robust JSON parsing for API responses
112        let text = response.text().await?;
113        let result = crate::utils::safe_json_parse::<R>(&text)
114            .map_err(|e| OramaError::generic(format!("Failed to parse API response: {e}")))?;
115        Ok(result)
116    }
117
118    /// Make a request and return the raw response
119    pub async fn get_response<T>(&self, req: ClientRequest<T>) -> Result<Response>
120    where
121        T: Serialize,
122    {
123        let auth_ref = self.auth.get_ref(req.target).await?;
124        let base_url = Url::parse(&auth_ref.base_url)?;
125        let url = base_url.join(&req.path)?;
126
127        let mut request_builder = self.client.request(req.method, url);
128
129        // Set headers
130        request_builder = request_builder.header("Content-Type", "application/json");
131
132        if req.api_key_position == ApiKeyPosition::Header {
133            request_builder =
134                request_builder.header("Authorization", format!("Bearer {}", auth_ref.bearer));
135        }
136
137        // Set query parameters
138        let mut query_params = req.params.unwrap_or_default();
139        if req.api_key_position == ApiKeyPosition::QueryParams {
140            query_params.insert("api-key".to_string(), auth_ref.bearer);
141        }
142
143        if !query_params.is_empty() {
144            request_builder = request_builder.query(&query_params);
145        }
146
147        // Set body for POST requests
148        if let Some(body) = req.body {
149            request_builder = request_builder.json(&body);
150        }
151
152        let response = request_builder.send().await?;
153        Ok(response)
154    }
155
156    /// Get the underlying reqwest client
157    pub fn inner(&self) -> &ReqwestClient {
158        &self.client
159    }
160
161    /// Get authentication reference for a target
162    pub async fn get_auth_ref(&self, target: Target) -> Result<crate::auth::AuthRef> {
163        self.auth.get_ref(target).await
164    }
165}