sdaas_rs/client.rs
1use crate::types::{DeltaRequest, DeltaResponse, ValidationResponse};
2use crate::{Error, Result};
3use reqwest::{Client as HttpClient, StatusCode};
4use uuid::Uuid;
5
6/// Async client for SDaaS API
7///
8/// The [`Client`] provides methods for computing text deltas and validating API keys.
9/// It uses async/await patterns with tokio for high-efficiency concurrent operations.
10///
11/// # Example
12///
13/// ```no_run
14/// use sdaas_rs::Client;
15///
16/// #[tokio::main]
17/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
18/// let client = Client::new(
19/// "your-api-key",
20/// "https://saas-core-production.up.railway.app"
21/// );
22///
23/// let delta = client.compute_delta("Hello", "Hello World").await?;
24/// println!("Delta: {:?}", delta.delta);
25/// Ok(())
26/// }
27/// ```
28pub struct Client {
29 http_client: HttpClient,
30 api_key: String,
31 base_url: String,
32}
33
34impl Client {
35 /// Create a new SDaaS client
36 ///
37 /// # Arguments
38 ///
39 /// * `api_key` - Your SDaaS API key (can be obtained from https://saas-core-production.up.railway.app)
40 /// * `base_url` - Base URL for the SDaaS API (defaults to `https://saas-core-production.up.railway.app`)
41 ///
42 /// # Example
43 ///
44 /// ```no_run
45 /// use sdaas_rs::Client;
46 ///
47 /// let client = Client::new(
48 /// "your-api-key",
49 /// "https://saas-core-production.up.railway.app"
50 /// );
51 /// ```
52 pub fn new(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
53 Self {
54 http_client: HttpClient::new(),
55 api_key: api_key.into(),
56 base_url: base_url.into(),
57 }
58 }
59
60 /// Compute delta between source and target text
61 ///
62 /// Sends a request to the SDaaS API to compute the delta (diff) between two texts.
63 /// The response includes delta operations and compression metrics.
64 ///
65 /// # Arguments
66 ///
67 /// * `source` - The original text
68 /// * `target` - The target text to transform source into
69 ///
70 /// # Returns
71 ///
72 /// Returns a [`DeltaResponse`] containing:
73 /// - `delta`: Vector of insert/delete operations
74 /// - `size_bytes`: Compressed delta size
75 /// - `compression_ratio`: Efficiency metric (0.0-1.0)
76 ///
77 /// # Errors
78 ///
79 /// Returns [`Error::Unauthorized`] if API key is invalid.
80 /// Returns [`Error::RateLimitExceeded`] if rate limit is hit.
81 /// Returns [`Error::QuotaExceeded`] if quota is exhausted.
82 ///
83 /// # Example
84 ///
85 /// ```no_run
86 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
87 /// use sdaas_rs::Client;
88 ///
89 /// let client = Client::new("your-api-key", "https://saas-core-production.up.railway.app");
90 /// let delta = client.compute_delta("Hello", "Hello World").await?;
91 /// println!("Operations: {}", delta.delta.len());
92 /// println!("Compression: {:.1}%", delta.compression_ratio * 100.0);
93 /// # Ok(())
94 /// # }
95 /// ```
96 pub async fn compute_delta(&self, source: &str, target: &str) -> Result<DeltaResponse> {
97 let request = DeltaRequest {
98 source: source.to_string(),
99 target: target.to_string(),
100 };
101
102 let url = format!("{}/api/delta", self.base_url);
103 let request_id = Uuid::new_v4().to_string();
104
105 let response = self
106 .http_client
107 .post(&url)
108 .header("X-API-Key", &self.api_key)
109 .header("X-Request-ID", &request_id)
110 .header("Content-Type", "application/json")
111 .json(&request)
112 .send()
113 .await?;
114
115 match response.status() {
116 StatusCode::OK => response
117 .json::<DeltaResponse>()
118 .await
119 .map_err(|e| Error::InvalidResponse(e.to_string())),
120 StatusCode::UNAUTHORIZED => Err(Error::Unauthorized),
121 StatusCode::TOO_MANY_REQUESTS => Err(Error::RateLimitExceeded),
122 StatusCode::PAYMENT_REQUIRED => Err(Error::QuotaExceeded),
123 status => Err(Error::Api(format!("API returned: {}", status))),
124 }
125 }
126
127 /// Validate API key and get current quota/rate limit info
128 ///
129 /// Checks if the API key is valid and retrieves the current account details.
130 ///
131 /// # Returns
132 ///
133 /// Returns a [`ValidationResponse`] containing:
134 /// - `valid`: Whether the key is valid
135 /// - `key`: [`KeyValidation`] with tier, quota, and rate limit info
136 ///
137 /// # Errors
138 ///
139 /// Returns [`Error::InvalidApiKey`] if the API key is invalid.
140 ///
141 /// # Example
142 ///
143 /// ```no_run
144 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
145 /// use sdaas_rs::Client;
146 ///
147 /// let client = Client::new("your-api-key", "https://saas-core-production.up.railway.app");
148 /// let validation = client.validate_key().await?;
149 /// println!("Tier: {}", validation.key.tier);
150 /// println!("Quota remaining: {}", validation.key.quota_remaining);
151 /// println!("Rate limit: {}/sec", validation.key.rate_limit);
152 /// # Ok(())
153 /// # }
154 /// ```
155 pub async fn validate_key(&self) -> Result<ValidationResponse> {
156 let url = format!("{}/api/validate", self.base_url);
157
158 let response = self
159 .http_client
160 .get(&url)
161 .header("X-API-Key", &self.api_key)
162 .send()
163 .await?;
164
165 match response.status() {
166 StatusCode::OK => response
167 .json::<ValidationResponse>()
168 .await
169 .map_err(|e| Error::InvalidResponse(e.to_string())),
170 StatusCode::UNAUTHORIZED => Err(Error::InvalidApiKey),
171 status => Err(Error::Api(format!("API returned: {}", status))),
172 }
173 }
174
175 /// Set a new API key
176 ///
177 /// Updates the API key used for subsequent requests.
178 ///
179 /// # Example
180 ///
181 /// ```no_run
182 /// use sdaas_rs::Client;
183 ///
184 /// let mut client = Client::new("old-key", "https://saas-core-production.up.railway.app");
185 /// client.set_api_key("new-key");
186 /// ```
187 pub fn set_api_key(&mut self, api_key: impl Into<String>) {
188 self.api_key = api_key.into();
189 }
190
191 /// Get the current base URL
192 ///
193 /// Returns a reference to the base URL being used for API requests.
194 pub fn base_url(&self) -> &str {
195 &self.base_url
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn test_client_creation() {
205 let client = Client::new("test-key", "https://example.com");
206 assert_eq!(client.base_url(), "https://example.com");
207 }
208
209 #[test]
210 fn test_set_api_key() {
211 let mut client = Client::new("old-key", "https://example.com");
212 client.set_api_key("new-key");
213 assert_eq!(client.api_key, "new-key");
214 }
215
216 #[test]
217 fn test_client_url_normalization() {
218 let client = Client::new("key", "https://example.com/");
219 assert_eq!(client.base_url(), "https://example.com/");
220 }
221}