Skip to main content

jules_core/builder/
mod.rs

1//! Builder module.
2
3use crate::client::ClientRequest;
4use crate::errors::{SDKError, ValidationError};
5use crate::response::ClientResponse;
6use crate::traits::Client;
7use std::future::Future;
8use std::time::Duration;
9
10/// A builder for creating a client.
11#[derive(Clone)]
12pub struct ClientBuilder {
13    base_url: Option<String>,
14    timeout: Duration,
15    auth_token: Option<String>,
16}
17
18impl std::fmt::Debug for ClientBuilder {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        f.debug_struct("ClientBuilder")
21            .field("base_url", &self.base_url)
22            .field("timeout", &self.timeout)
23            .field(
24                "auth_token",
25                &self.auth_token.as_ref().map(|_| "***REDACTED***"),
26            )
27            .finish()
28    }
29}
30
31impl Default for ClientBuilder {
32    fn default() -> Self {
33        Self {
34            base_url: None,
35            timeout: Duration::from_secs(30),
36            auth_token: None,
37        }
38    }
39}
40
41impl ClientBuilder {
42    /// Creates a new `ClientBuilder`.
43    #[must_use]
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Sets the base URL.
49    #[must_use]
50    pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
51        self.base_url = Some(base_url.into());
52        self
53    }
54
55    /// Sets the timeout.
56    #[must_use]
57    pub fn timeout(mut self, timeout: Duration) -> Self {
58        self.timeout = timeout;
59        self
60    }
61
62    /// Sets the auth token.
63    #[must_use]
64    pub fn auth_token(mut self, auth_token: impl Into<String>) -> Self {
65        self.auth_token = Some(auth_token.into());
66        self
67    }
68
69    /// Validates and builds the configuration.
70    ///
71    /// # Errors
72    /// Returns `SDKError::Validation` if required fields are missing.
73    pub fn build(self) -> Result<BuiltClient, SDKError> {
74        let base_url = self
75            .base_url
76            .ok_or_else(|| SDKError::Validation(ValidationError::new("base_url is required")))?;
77
78        let auth_token = self
79            .auth_token
80            .ok_or_else(|| SDKError::Validation(ValidationError::new("auth_token is required")))?;
81
82        Ok(BuiltClient {
83            base_url,
84            timeout: self.timeout,
85            auth_token,
86        })
87    }
88}
89
90/// A concrete client built by `ClientBuilder`.
91#[derive(Clone)]
92pub struct BuiltClient {
93    base_url: String,
94    timeout: Duration,
95    auth_token: String,
96}
97
98impl std::fmt::Debug for BuiltClient {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        f.debug_struct("BuiltClient")
101            .field("base_url", &self.base_url)
102            .field("timeout", &self.timeout)
103            .field("auth_token", &"***REDACTED***")
104            .finish()
105    }
106}
107
108impl BuiltClient {
109    /// Returns the base URL.
110    #[must_use]
111    pub fn base_url(&self) -> &str {
112        &self.base_url
113    }
114
115    /// Returns the timeout.
116    #[must_use]
117    pub fn timeout(&self) -> Duration {
118        self.timeout
119    }
120
121    /// Returns the auth token.
122    #[must_use]
123    pub fn auth_token(&self) -> &str {
124        &self.auth_token
125    }
126}
127
128impl Client for BuiltClient {
129    #[allow(clippy::manual_async_fn)]
130    fn send_request(
131        &self,
132        request: ClientRequest,
133    ) -> impl Future<Output = Result<ClientResponse, SDKError>> + Send {
134        async move {
135            let _ = request;
136            Err(SDKError::Validation(ValidationError::new(
137                "Not implemented",
138            )))
139        }
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_client_builder_defaults() {
149        let builder = ClientBuilder::new();
150        assert_eq!(builder.timeout, Duration::from_secs(30));
151        assert!(builder.base_url.is_none());
152        assert!(builder.auth_token.is_none());
153    }
154
155    #[test]
156    fn test_client_builder_validation_error_url() {
157        let builder = ClientBuilder::new().auth_token("secret");
158        let result = builder.build();
159        assert!(result.is_err());
160        if let Err(SDKError::Validation(e)) = result {
161            assert_eq!(e.message, "base_url is required");
162        } else {
163            panic!("Expected validation error");
164        }
165    }
166
167    #[test]
168    fn test_client_builder_validation_error_auth() {
169        let builder = ClientBuilder::new().base_url("https://api.example.com");
170        let result = builder.build();
171        assert!(result.is_err());
172        if let Err(SDKError::Validation(e)) = result {
173            assert_eq!(e.message, "auth_token is required");
174        } else {
175            panic!("Expected validation error");
176        }
177    }
178
179    #[test]
180    fn test_client_builder_success() {
181        let builder = ClientBuilder::new()
182            .base_url("https://api.example.com")
183            .auth_token("secret")
184            .timeout(Duration::from_secs(10));
185
186        let client = builder.build().unwrap();
187        assert_eq!(client.base_url, "https://api.example.com");
188        assert_eq!(client.auth_token, "secret");
189        assert_eq!(client.timeout, Duration::from_secs(10));
190    }
191
192    #[tokio::test]
193    async fn test_built_client_send_request() {
194        let builder = ClientBuilder::new()
195            .base_url("https://api.example.com")
196            .auth_token("secret");
197        let client = builder.build().unwrap();
198        let request = ClientRequest::default();
199        let result = client.send_request(request).await;
200
201        assert!(result.is_err());
202        if let Err(SDKError::Validation(e)) = result {
203            assert_eq!(e.message, "Not implemented");
204        } else {
205            panic!("Expected validation error with message 'Not implemented'");
206        }
207    }
208}
209/// Request builder module.
210pub mod request;
211pub use request::RequestBuilder;
212
213/// Conversation builder module.
214pub mod conversation;
215pub use conversation::ConversationBuilder;
216
217/// Message builder module.
218pub mod message;
219pub use message::MessageBuilder;