Skip to main content

kairos_client/
lib.rs

1//! Kairos Gateway Client Library
2//!
3//! This library provides client functionality for interacting with the Kairos API Gateway,
4//! including health checks, metrics retrieval, and configuration management.
5//! 
6//! The client supports both native (using tokio + reqwest) and WebAssembly (using gloo-net)
7//! compilation targets with completely separate implementations.
8
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11use url::Url;
12
13// Conditional imports based on target
14#[cfg(feature = "native")]
15use reqwest::Client;
16#[cfg(feature = "native")]
17use std::time::Duration;
18
19#[cfg(feature = "wasm")]
20use gloo_net::http::Request;
21#[cfg(feature = "wasm")]
22use js_sys;
23
24#[derive(Error, Debug)]
25pub enum ClientError {
26    #[cfg(feature = "native")]
27    #[error("HTTP request failed: {0}")]
28    Http(#[from] reqwest::Error),
29    
30    #[cfg(feature = "wasm")]
31    #[error("HTTP request failed: {0}")]
32    GlooHttp(#[from] gloo_net::Error),
33    
34    #[error("Invalid URL: {0}")]
35    InvalidUrl(#[from] url::ParseError),
36    
37    #[error("Gateway returned error: {status} - {message}")]
38    Gateway { status: u16, message: String },
39    
40    #[error("Serialization error: {0}")]
41    Serialization(#[from] serde_json::Error),
42    
43    #[cfg(feature = "wasm")]
44    #[error("JavaScript error: {0}")]
45    JsError(String),
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct HealthStatus {
50    pub status: String,
51    pub timestamp: String,
52    pub version: String,
53    pub uptime_seconds: u64,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]  
57pub struct MetricsSnapshot {
58    pub requests_total: u64,
59    pub requests_success: u64,
60    pub requests_error: u64,
61    pub active_connections: u64,
62    pub average_response_time_ms: f64,
63    pub timestamp: String,
64}
65
66/// Client for interacting with Kairos API Gateway
67pub struct GatewayClient {
68    #[cfg(feature = "native")]
69    client: Client,
70    base_url: Url,
71}
72
73impl GatewayClient {
74    /// Create a new gateway client
75    pub fn new(gateway_url: &str) -> Result<Self, ClientError> {
76        let base_url = Url::parse(gateway_url)?;
77        
78        #[cfg(feature = "native")]
79        let client = Client::builder()
80            .timeout(Duration::from_secs(30))
81            .build()?;
82            
83        Ok(Self {
84            #[cfg(feature = "native")]
85            client,
86            base_url,
87        })
88    }
89    
90    /// Check gateway health status
91    pub async fn health(&self) -> Result<HealthStatus, ClientError> {
92        let url = self.base_url.join("/health")?;
93        
94        #[cfg(feature = "native")]
95        {
96            let response = self.client.get(url).send().await?;
97            
98            if response.status().is_success() {
99                let health = response.json::<HealthStatus>().await?;
100                return Ok(health);
101            } else {
102                return Err(ClientError::Gateway {
103                    status: response.status().as_u16(),
104                    message: response.text().await.unwrap_or_default(),
105                });
106            }
107        }
108        
109        #[cfg(feature = "wasm")]
110        {
111            let response = Request::get(url.as_str()).send().await?;
112            
113            if response.ok() {
114                let health = response.json::<HealthStatus>().await?;
115                return Ok(health);
116            } else {
117                let text = response.text().await.unwrap_or_default();
118                return Err(ClientError::Gateway {
119                    status: response.status(),
120                    message: text,
121                });
122            }
123        }
124    }
125    
126    /// Get gateway metrics
127    pub async fn metrics(&self) -> Result<String, ClientError> {
128        let url = self.base_url.join("/metrics")?;
129        
130        #[cfg(feature = "native")]
131        {
132            let response = self.client.get(url).send().await?;
133            
134            if response.status().is_success() {
135                let metrics = response.text().await?;
136                return Ok(metrics);
137            } else {
138                return Err(ClientError::Gateway {
139                    status: response.status().as_u16(),
140                    message: response.text().await.unwrap_or_default(),
141                });
142            }
143        }
144        
145        #[cfg(feature = "wasm")]
146        {
147            let response = Request::get(url.as_str()).send().await?;
148            
149            if response.ok() {
150                let metrics = response.text().await?;
151                return Ok(metrics);
152            } else {
153                let text = response.text().await.unwrap_or_default();
154                return Err(ClientError::Gateway {
155                    status: response.status(),
156                    message: text,
157                });
158            }
159        }
160    }
161    
162    /// Get parsed metrics snapshot
163    pub async fn metrics_snapshot(&self) -> Result<MetricsSnapshot, ClientError> {
164        // For now, return mock data with some variation
165        // TODO: Parse actual Prometheus format or add JSON endpoint
166        let timestamp = chrono::Utc::now().to_rfc3339();
167        
168        // Simulate some realistic metrics with conditional random generation
169        #[cfg(feature = "wasm")]
170        let (_random_factor, _error_factor, _conn_factor, _latency_factor) = 
171            (js_sys::Math::random(), js_sys::Math::random(), js_sys::Math::random(), js_sys::Math::random());
172        
173        #[cfg(feature = "native")]
174        let (_random_factor, _error_factor, _conn_factor, _latency_factor) = {
175            use rand::Rng;
176            let mut rng = rand::thread_rng();
177            (rng.gen::<f64>(), rng.gen::<f64>(), rng.gen::<f64>(), rng.gen::<f64>())
178        };
179        
180        let requests_total = 1000 + (_random_factor * 500.0) as u64;
181        let requests_error = (requests_total as f64 * 0.02 + _error_factor * 10.0) as u64;
182        let requests_success = requests_total - requests_error;
183        
184        Ok(MetricsSnapshot {
185            requests_total,
186            requests_success,
187            requests_error,
188            active_connections: (10.0 + _conn_factor * 20.0) as u64,
189            average_response_time_ms: 10.0 + _latency_factor * 50.0,
190            timestamp,
191        })
192    }
193}