configvault_sdk/
client.rs1use std::collections::HashMap;
2use std::time::Duration;
3
4use reqwest::header::{HeaderMap, HeaderValue};
5
6use crate::errors::{handle_error_response, ConfigVaultError};
7use crate::models::{ConfigListResponse, ConfigResponse, HealthResponse, SyncResponse};
8use crate::watcher::ConfigWatcher;
9
10pub struct ConfigVaultClient {
12 base_url: String,
13 api_key: String,
14 http: reqwest::Client,
15 timeout: Duration,
16}
17
18impl ConfigVaultClient {
19 pub fn new(base_url: &str, api_key: &str) -> Self {
21 Self::with_timeout(base_url, api_key, Duration::from_secs(30))
22 }
23
24 pub fn with_timeout(base_url: &str, api_key: &str, timeout: Duration) -> Self {
26 let mut headers = HeaderMap::new();
27 headers.insert(
28 "X-Api-Key",
29 HeaderValue::from_str(api_key).expect("API key must be a valid header value"),
30 );
31
32 let http = reqwest::Client::builder()
33 .default_headers(headers)
34 .timeout(timeout)
35 .use_rustls_tls()
36 .build()
37 .expect("Failed to build HTTP client");
38
39 Self {
40 base_url: base_url.trim_end_matches('/').to_string(),
41 api_key: api_key.to_string(),
42 http,
43 timeout,
44 }
45 }
46
47 pub async fn get(&self, key: &str) -> Result<String, ConfigVaultError> {
51 let url = format!("{}/config/{}", self.base_url, key);
52 let response = self.http.get(&url).send().await?;
53
54 if !response.status().is_success() {
55 return Err(handle_error_response(
56 response.status().as_u16(),
57 Some(key),
58 ));
59 }
60
61 let data: ConfigResponse = response.json().await?;
62 Ok(data.value)
63 }
64
65 pub async fn exists(&self, key: &str) -> Result<bool, ConfigVaultError> {
69 let url = format!("{}/config/{}", self.base_url, key);
70 let response = self.http.head(&url).send().await?;
71
72 let status = response.status().as_u16();
73 match status {
74 200 => Ok(true),
75 404 => Ok(false),
76 401 | 503 => Err(handle_error_response(status, None)),
77 _ => Err(handle_error_response(status, None)),
78 }
79 }
80
81 pub async fn list(&self, namespace: &str) -> Result<HashMap<String, String>, ConfigVaultError> {
85 let url = reqwest::Url::parse_with_params(
86 &format!("{}/config", self.base_url),
87 &[("prefix", namespace)],
88 )
89 .map_err(|e| ConfigVaultError::Unexpected {
90 status: 0,
91 message: format!("Invalid URL: {e}"),
92 })?;
93 let response = self.http.get(url).send().await?;
94
95 if !response.status().is_success() {
96 return Err(handle_error_response(
97 response.status().as_u16(),
98 None,
99 ));
100 }
101
102 let data: ConfigListResponse = response.json().await?;
103 Ok(data.configs)
104 }
105
106 pub async fn sync(&self) -> Result<SyncResponse, ConfigVaultError> {
112 let url = format!("{}/sync", self.base_url);
113 let response = self.http.post(&url).send().await?;
114
115 if !response.status().is_success() {
116 return Err(handle_error_response(response.status().as_u16(), None));
117 }
118
119 let data: SyncResponse = response.json().await?;
120 Ok(data)
121 }
122
123 pub async fn health(&self) -> Result<HealthResponse, ConfigVaultError> {
127 let url = format!("{}/health", self.base_url);
128 let response = self.http.get(&url).send().await?;
129
130 let data: HealthResponse = response.json().await?;
131 Ok(data)
132 }
133
134 pub fn watch(&self, filter: Option<&str>) -> ConfigWatcher {
138 ConfigWatcher::new(
139 &self.base_url,
140 &self.api_key,
141 filter,
142 self.timeout,
143 )
144 }
145}