//! Builder pattern for SupervisorClient to ensure proper configuration //! //! This module provides a type-safe builder that guarantees a client cannot be //! created without a secret, preventing authentication issues. #[cfg(target_arch = "wasm32")] use crate::wasm::WasmSupervisorClient; #[cfg(not(target_arch = "wasm32"))] use crate::{SupervisorClient, HttpTransport, ClientResult, ClientError}; #[cfg(not(target_arch = "wasm32"))] use jsonrpsee::http_client::HttpClientBuilder; #[cfg(not(target_arch = "wasm32"))] use http::{HeaderMap, HeaderName, HeaderValue}; /// Builder for WasmSupervisorClient that enforces secret requirement #[cfg(target_arch = "wasm32")] #[derive(Clone)] pub struct WasmSupervisorClientBuilder { server_url: Option, secret: Option, } #[cfg(target_arch = "wasm32")] impl WasmSupervisorClientBuilder { /// Create a new builder pub fn new() -> Self { Self { server_url: None, secret: None, } } /// Set the server URL pub fn server_url(mut self, url: impl Into) -> Self { self.server_url = Some(url.into()); self } /// Set the authentication secret (required) pub fn secret(mut self, secret: impl Into) -> Self { self.secret = Some(secret.into()); self } /// Build the client /// /// Returns Err if server_url or secret is not set pub fn build(self) -> Result { let server_url = self.server_url.ok_or("Server URL is required")?; let secret = self.secret.ok_or("Secret is required for authenticated client")?; if secret.is_empty() { return Err("Secret cannot be empty".to_string()); } Ok(WasmSupervisorClient::new(server_url, secret)) } } #[cfg(target_arch = "wasm32")] impl Default for WasmSupervisorClientBuilder { fn default() -> Self { Self::new() } } /// Builder for SupervisorClient (HTTP transport) #[cfg(not(target_arch = "wasm32"))] #[derive(Debug, Clone)] pub struct SupervisorClientBuilder { url: Option, secret: Option, timeout: Option, } #[cfg(not(target_arch = "wasm32"))] impl SupervisorClientBuilder { /// Create a new builder pub fn new() -> Self { Self { url: None, secret: None, timeout: Some(std::time::Duration::from_secs(30)), } } /// Set the server URL pub fn url(mut self, url: impl Into) -> Self { self.url = Some(url.into()); self } /// Set the authentication secret pub fn secret(mut self, secret: impl Into) -> Self { self.secret = Some(secret.into()); self } /// Set the request timeout (default: 30 seconds) pub fn timeout(mut self, timeout: std::time::Duration) -> Self { self.timeout = Some(timeout); self } /// Build the SupervisorClient with HTTP transport pub fn build(self) -> ClientResult> { let server_url = self.url .ok_or_else(|| ClientError::Http("URL is required".to_string()))?; let secret = self.secret .ok_or_else(|| ClientError::Http("Secret is required".to_string()))?; // Create headers with Authorization bearer token let mut headers = HeaderMap::new(); let auth_value = format!("Bearer {}", secret); headers.insert( HeaderName::from_static("authorization"), HeaderValue::from_str(&auth_value) .map_err(|e| ClientError::Http(format!("Invalid auth header: {}", e)))? ); let client = HttpClientBuilder::default() .request_timeout(self.timeout.unwrap_or(std::time::Duration::from_secs(30))) .set_headers(headers) .build(&server_url) .map_err(|e| ClientError::Http(e.to_string()))?; let transport = HttpTransport { client }; Ok(SupervisorClient { transport, secret, }) } } #[cfg(not(target_arch = "wasm32"))] impl Default for SupervisorClientBuilder { fn default() -> Self { Self::new() } } #[cfg(all(test, target_arch = "wasm32"))] mod tests { use super::*; #[test] fn test_builder_requires_all_fields() { let builder = WasmSupervisorClientBuilder::new(); assert!(builder.build().is_err()); let builder = WasmSupervisorClientBuilder::new() .server_url("http://localhost:3030"); assert!(builder.build().is_err()); let builder = WasmSupervisorClientBuilder::new() .secret("test-secret"); assert!(builder.build().is_err()); } #[test] fn test_builder_success() { let builder = WasmSupervisorClientBuilder::new() .server_url("http://localhost:3030") .secret("test-secret"); assert!(builder.build().is_ok()); } #[test] fn test_build_error_messages() { let result = WasmSupervisorClientBuilder::new().build(); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "Server URL is required"); let result = WasmSupervisorClientBuilder::new() .server_url("http://localhost:3030") .build(); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "Secret is required for authenticated client"); let result = WasmSupervisorClientBuilder::new() .server_url("http://localhost:3030") .secret("") .build(); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "Secret cannot be empty"); } }