190 lines
5.7 KiB
Rust
190 lines
5.7 KiB
Rust
//! 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<String>,
|
|
secret: Option<String>,
|
|
}
|
|
|
|
#[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<String>) -> Self {
|
|
self.server_url = Some(url.into());
|
|
self
|
|
}
|
|
|
|
/// Set the authentication secret (required)
|
|
pub fn secret(mut self, secret: impl Into<String>) -> 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<WasmSupervisorClient, String> {
|
|
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<String>,
|
|
secret: Option<String>,
|
|
timeout: Option<std::time::Duration>,
|
|
}
|
|
|
|
#[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<String>) -> Self {
|
|
self.url = Some(url.into());
|
|
self
|
|
}
|
|
|
|
/// Set the authentication secret
|
|
pub fn secret(mut self, secret: impl Into<String>) -> 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<SupervisorClient<HttpTransport>> {
|
|
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");
|
|
}
|
|
}
|