secret_store_sdk/
config.rs1use crate::{auth::Auth, cache::CacheConfig, errors::Result, telemetry::TelemetryConfig, Error};
2use std::time::Duration;
3
4#[derive(Debug, Clone)]
6pub struct ClientConfig {
7 pub base_url: String,
9 pub auth: Auth,
11 pub timeout: Duration,
13 pub retries: u32,
15 pub user_agent_suffix: Option<String>,
17 pub cache_config: CacheConfig,
19 pub telemetry_config: TelemetryConfig,
21 pub allow_insecure_http: bool,
23}
24
25#[derive(Debug)]
27pub struct ClientBuilder {
28 base_url: String,
29 auth: Option<Auth>,
30 timeout_ms: u64,
31 retries: u32,
32 user_agent_suffix: Option<String>,
33 cache_enabled: bool,
34 cache_max_entries: u64,
35 cache_ttl_secs: u64,
36 telemetry_config: TelemetryConfig,
37 allow_insecure_http: bool,
38}
39
40impl ClientBuilder {
41 pub fn new(base_url: impl Into<String>) -> Self {
47 Self {
48 base_url: base_url.into(),
49 auth: None,
50 timeout_ms: crate::DEFAULT_TIMEOUT_MS,
51 retries: crate::DEFAULT_RETRIES,
52 user_agent_suffix: None,
53 cache_enabled: true,
54 cache_max_entries: crate::DEFAULT_CACHE_MAX_ENTRIES,
55 cache_ttl_secs: crate::DEFAULT_CACHE_TTL_SECS,
56 telemetry_config: TelemetryConfig::default(),
57 allow_insecure_http: false,
58 }
59 }
60
61 pub fn auth(mut self, auth: Auth) -> Self {
63 self.auth = Some(auth);
64 self
65 }
66
67 pub fn timeout_ms(mut self, timeout_ms: u64) -> Self {
69 self.timeout_ms = timeout_ms;
70 self
71 }
72
73 pub fn retries(mut self, retries: u32) -> Self {
75 self.retries = retries;
76 self
77 }
78
79 pub fn user_agent_extra(mut self, suffix: impl Into<String>) -> Self {
81 self.user_agent_suffix = Some(suffix.into());
82 self
83 }
84
85 pub fn enable_cache(mut self, enabled: bool) -> Self {
87 self.cache_enabled = enabled;
88 self
89 }
90
91 pub fn cache_max_entries(mut self, max_entries: u64) -> Self {
93 self.cache_max_entries = max_entries;
94 self
95 }
96
97 pub fn cache_ttl_secs(mut self, ttl_secs: u64) -> Self {
99 self.cache_ttl_secs = ttl_secs;
100 self
101 }
102
103 #[cfg(feature = "metrics")]
105 pub fn with_telemetry(mut self, config: TelemetryConfig) -> Self {
106 self.telemetry_config = config;
107 self
108 }
109
110 #[cfg(feature = "metrics")]
112 pub fn enable_telemetry(mut self) -> Self {
113 self.telemetry_config.enabled = true;
114 self
115 }
116
117 #[cfg(feature = "danger-insecure-http")]
119 pub fn allow_insecure_http(mut self) -> Self {
120 self.allow_insecure_http = true;
121 self
122 }
123
124 pub fn build(self) -> Result<crate::Client> {
126 let url = self.base_url.trim_end_matches('/');
128
129 if url.starts_with("http://") && !self.allow_insecure_http {
131 #[cfg(feature = "danger-insecure-http")]
132 return Err(Error::Config(
133 "HTTP URLs are not allowed by default. Use .allow_insecure_http() to enable (dangerous!)".to_string()
134 ));
135
136 #[cfg(not(feature = "danger-insecure-http"))]
137 return Err(Error::Config(
138 "HTTP URLs are not allowed. Enable the 'danger-insecure-http' feature and use .allow_insecure_http() (dangerous!)".to_string()
139 ));
140 }
141
142 let auth = self.auth.ok_or_else(|| {
144 Error::Config(
145 "Authentication is required. Use .auth() to set authentication method".to_string(),
146 )
147 })?;
148
149 if !url.starts_with("http://") && !url.starts_with("https://") {
151 return Err(Error::Config(
152 "Base URL must start with http:// or https://".to_string(),
153 ));
154 }
155
156 let config = ClientConfig {
157 base_url: url.to_string(),
158 auth,
159 timeout: Duration::from_millis(self.timeout_ms),
160 retries: self.retries,
161 user_agent_suffix: self.user_agent_suffix,
162 cache_config: CacheConfig {
163 enabled: self.cache_enabled,
164 max_entries: self.cache_max_entries,
165 default_ttl_secs: self.cache_ttl_secs,
166 },
167 telemetry_config: self.telemetry_config,
168 allow_insecure_http: self.allow_insecure_http,
169 };
170
171 crate::client::Client::new(config)
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_builder_requires_auth() {
181 let result = ClientBuilder::new("https://example.com").build();
182 assert!(result.is_err());
183 assert!(matches!(result.unwrap_err(), Error::Config(_)));
184 }
185
186 #[test]
187 fn test_builder_validates_url() {
188 let result = ClientBuilder::new("not-a-url")
189 .auth(Auth::bearer("token"))
190 .build();
191 assert!(result.is_err());
192 }
193
194 #[test]
195 #[cfg(not(feature = "danger-insecure-http"))]
196 fn test_builder_rejects_http() {
197 let result = ClientBuilder::new("http://example.com")
198 .auth(Auth::bearer("token"))
199 .build();
200 assert!(result.is_err());
201 assert!(matches!(result.unwrap_err(), Error::Config(_)));
202 }
203}