Skip to main content

ferriskey_sdk/
config.rs

1//! SDK configuration primitives.
2//!
3//! ## Design Philosophy
4//!
5//! Configuration uses a typed builder pattern with compile-time validation.
6//! The `SdkConfigBuilder` uses TypeState markers to ensure that required
7//! fields (like `base_url`) are set before calling `.build()`.
8//!
9//! ## Example
10//!
11//! ```
12//! use std::time::Duration;
13//!
14//! use ferriskey_sdk::{AuthStrategy, SdkConfig};
15//!
16//! let config = SdkConfig::builder("https://api.example.com")
17//!     .auth(AuthStrategy::Bearer("token".into()))
18//!     .timeout(Duration::from_secs(30))
19//!     .build();
20//! ```
21
22use std::time::Duration;
23
24// ---------------------------------------------------------------------------
25// Authentication Strategy
26// ---------------------------------------------------------------------------
27
28/// Authentication strategy applied to outgoing SDK requests.
29///
30/// ## Type Safety
31///
32/// Using an enum rather than `Option<String>` ensures that authentication
33/// modes are explicitly named and exhaustive pattern matching is possible.
34#[derive(Clone, Debug, Default, Eq, PartialEq)]
35pub enum AuthStrategy {
36    /// Do not attach authentication.
37    #[default]
38    None,
39    /// Attach an HTTP bearer token.
40    Bearer(String),
41    // Future variants can be added without breaking changes:
42    // ApiKey { header: String, key: String },
43    // OAuth2 { token: String, refresh: String },
44}
45
46impl AuthStrategy {
47    /// Returns `true` if authentication is configured.
48    #[must_use]
49    pub const fn is_configured(&self) -> bool {
50        !matches!(self, Self::None)
51    }
52
53    /// Returns the bearer token if this strategy is `Bearer`.
54    #[must_use]
55    pub const fn bearer_token(&self) -> Option<&String> {
56        match self {
57            Self::Bearer(token) => Some(token),
58            Self::None => None,
59        }
60    }
61}
62
63// ---------------------------------------------------------------------------
64// SdkConfig with Typed Builder
65// ---------------------------------------------------------------------------
66
67/// Runtime configuration for the FerrisKey SDK.
68///
69/// ## Immutability
70///
71/// Once built, `SdkConfig` is immutable. This prevents accidental mutation
72/// of configuration during request processing and enables safe sharing
73/// across async tasks without locks.
74#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct SdkConfig {
76    /// Base URL used to resolve relative request paths.
77    base_url: String,
78    /// Authentication strategy used for outgoing requests.
79    auth: AuthStrategy,
80    /// Request timeout duration.
81    timeout: Option<Duration>,
82    /// Custom user-agent header value.
83    user_agent: Option<String>,
84}
85
86impl SdkConfig {
87    /// Create a new SDK configuration.
88    ///
89    /// Prefer using [`Self::builder()`] for more complex configurations.
90    #[must_use]
91    pub fn new(base_url: impl Into<String>, auth: AuthStrategy) -> Self {
92        Self { base_url: base_url.into(), auth, timeout: None, user_agent: None }
93    }
94
95    /// Create a typed builder with the required base URL.
96    ///
97    /// The builder ensures the base URL is always provided.
98    #[must_use]
99    pub fn builder(base_url: impl Into<String>) -> SdkConfigBuilder<BaseUrlSet> {
100        SdkConfigBuilder {
101            base_url: base_url.into(),
102            auth: AuthStrategy::default(),
103            timeout: None,
104            user_agent: None,
105            _state: std::marker::PhantomData,
106        }
107    }
108
109    /// Access the base URL.
110    #[must_use]
111    pub fn base_url(&self) -> &str {
112        &self.base_url
113    }
114
115    /// Access the authentication strategy.
116    #[must_use]
117    pub const fn auth(&self) -> &AuthStrategy {
118        &self.auth
119    }
120
121    /// Access the configured timeout.
122    #[must_use]
123    pub const fn timeout(&self) -> Option<Duration> {
124        self.timeout
125    }
126
127    /// Access the user-agent string.
128    #[must_use]
129    pub fn user_agent(&self) -> Option<&str> {
130        self.user_agent.as_deref()
131    }
132}
133
134// ---------------------------------------------------------------------------
135// TypeState marker for SdkConfigBuilder
136// ---------------------------------------------------------------------------
137
138/// TypeState marker: base URL has been set.
139#[derive(Debug, Clone, Copy)]
140pub struct BaseUrlSet;
141
142/// Typed builder for [`SdkConfig`] with compile-time validation.
143///
144/// ## Type-State Pattern
145///
146/// The builder uses a phantom type parameter to track whether the required
147/// `base_url` field has been set. Calling `.build()` on an incomplete
148/// builder is a compile-time error.
149///
150/// ```compile_fail
151/// // This will not compile - base_url not set:
152/// let config = SdkConfigBuilder::<()>::new().build();
153/// ```
154#[derive(Debug)]
155pub struct SdkConfigBuilder<S> {
156    base_url: String,
157    auth: AuthStrategy,
158    timeout: Option<Duration>,
159    user_agent: Option<String>,
160    _state: std::marker::PhantomData<S>,
161}
162
163impl SdkConfigBuilder<BaseUrlSet> {
164    /// Build the configuration. Available only when base_url is set.
165    #[must_use]
166    pub fn build(self) -> SdkConfig {
167        SdkConfig {
168            base_url: self.base_url,
169            auth: self.auth,
170            timeout: self.timeout,
171            user_agent: self.user_agent,
172        }
173    }
174}
175
176impl<S> SdkConfigBuilder<S> {
177    /// Set the authentication strategy.
178    #[must_use]
179    pub fn auth(mut self, auth: AuthStrategy) -> Self {
180        self.auth = auth;
181        self
182    }
183
184    /// Set the request timeout.
185    #[must_use]
186    pub const fn timeout(mut self, timeout: Duration) -> Self {
187        self.timeout = Some(timeout);
188        self
189    }
190
191    /// Set the user-agent header.
192    #[must_use]
193    pub fn user_agent(mut self, agent: impl Into<String>) -> Self {
194        self.user_agent = Some(agent.into());
195        self
196    }
197}
198
199// ---------------------------------------------------------------------------
200// Extension trait for fluent AuthStrategy construction
201// ---------------------------------------------------------------------------
202
203/// Extension trait for building auth strategies fluently.
204pub trait AuthStrategyExt {
205    /// Create a bearer auth strategy.
206    fn bearer(token: impl Into<String>) -> AuthStrategy;
207}
208
209impl AuthStrategyExt for AuthStrategy {
210    fn bearer(token: impl Into<String>) -> Self {
211        Self::Bearer(token.into())
212    }
213}