Skip to main content

camel_component_ws/
config.rs

1use std::time::Duration;
2
3use camel_component_api::CamelError;
4
5#[derive(Debug, Clone, Default, serde::Deserialize)]
6pub struct WsConfig {
7    pub max_connections: Option<u32>,
8    pub max_message_size: Option<u32>,
9    pub heartbeat_interval_ms: Option<u64>,
10    pub idle_timeout_ms: Option<u64>,
11    pub connect_timeout_ms: Option<u64>,
12    pub response_timeout_ms: Option<u64>,
13}
14
15#[derive(Debug, Clone)]
16pub struct WsEndpointConfig {
17    pub scheme: String,
18    pub host: String,
19    pub port: u16,
20    pub path: String,
21    pub max_connections: u32,
22    pub max_message_size: u32,
23    pub send_to_all: bool,
24    pub heartbeat_interval: Duration,
25    pub idle_timeout: Duration,
26    pub connect_timeout: Duration,
27    pub response_timeout: Duration,
28    pub allow_origin: String,
29    pub tls_cert: Option<String>,
30    pub tls_key: Option<String>,
31}
32
33impl Default for WsEndpointConfig {
34    fn default() -> Self {
35        Self {
36            scheme: "ws".into(),
37            host: "0.0.0.0".into(),
38            port: 8080,
39            path: "/".into(),
40            max_connections: 100,
41            max_message_size: 65536,
42            send_to_all: false,
43            heartbeat_interval: Duration::ZERO,
44            idle_timeout: Duration::ZERO,
45            connect_timeout: Duration::from_secs(10),
46            response_timeout: Duration::from_secs(30),
47            allow_origin: "*".into(),
48            tls_cert: None,
49            tls_key: None,
50        }
51    }
52}
53
54#[derive(Debug, Clone)]
55pub struct WsServerConfig {
56    pub inner: WsEndpointConfig,
57}
58
59#[derive(Debug, Clone)]
60pub struct WsClientConfig {
61    pub inner: WsEndpointConfig,
62}
63
64impl WsEndpointConfig {
65    pub fn from_uri(uri: &str) -> Result<Self, CamelError> {
66        let parsed = camel_component_api::parse_uri(uri)
67            .map_err(|e| CamelError::EndpointCreationFailed(e.to_string()))?;
68
69        let scheme = parsed.scheme;
70        if scheme != "ws" && scheme != "wss" {
71            return Err(CamelError::EndpointCreationFailed(format!(
72                "Invalid WebSocket scheme: {scheme}"
73            )));
74        }
75
76        let host_port_path = parsed.path;
77        let host_port_path = host_port_path.strip_prefix("//").unwrap_or(&host_port_path);
78        let (host_port, path) = match host_port_path.split_once('/') {
79            Some((hp, p)) => (hp, format!("/{p}")),
80            None => (host_port_path, "/".to_string()),
81        };
82
83        let (host, port) = match host_port.rsplit_once(':') {
84            Some((h, p)) if p.parse::<u16>().is_ok() => {
85                let parsed_port = p.parse::<u16>().unwrap(); // allow-unwrap
86                (h.to_string(), parsed_port)
87            }
88            _ => (
89                host_port.to_string(),
90                if scheme == "wss" { 443 } else { 80 },
91            ),
92        };
93
94        let mut cfg = Self {
95            scheme,
96            host: if host.is_empty() {
97                "0.0.0.0".to_string()
98            } else {
99                host
100            },
101            port,
102            path,
103            ..Self::default()
104        };
105
106        let params = parsed.params;
107        // Validate maxConnections >= 1 (WS-015)
108        if let Some(v) = params
109            .get("maxConnections")
110            .and_then(|v| v.parse::<u32>().ok())
111        {
112            if v == 0 {
113                return Err(CamelError::InvalidUri("maxConnections must be >= 1".into()));
114            }
115            cfg.max_connections = v;
116        }
117        // Validate maxMessageSize > 0 (WS-019)
118        if let Some(v) = params
119            .get("maxMessageSize")
120            .and_then(|v| v.parse::<u32>().ok())
121        {
122            if v == 0 {
123                return Err(CamelError::InvalidUri("maxMessageSize must be > 0".into()));
124            }
125            cfg.max_message_size = v;
126        }
127        if let Some(v) = params.get("sendToAll").and_then(|v| v.parse::<bool>().ok()) {
128            cfg.send_to_all = v;
129        }
130        if let Some(v) = params
131            .get("heartbeatIntervalMs")
132            .and_then(|v| v.parse::<u64>().ok())
133        {
134            cfg.heartbeat_interval = Duration::from_millis(v);
135        }
136        if let Some(v) = params
137            .get("idleTimeoutMs")
138            .and_then(|v| v.parse::<u64>().ok())
139        {
140            cfg.idle_timeout = Duration::from_millis(v);
141        }
142        if let Some(v) = params
143            .get("connectTimeoutMs")
144            .and_then(|v| v.parse::<u64>().ok())
145        {
146            cfg.connect_timeout = Duration::from_millis(v);
147        }
148        if let Some(v) = params
149            .get("responseTimeoutMs")
150            .and_then(|v| v.parse::<u64>().ok())
151        {
152            cfg.response_timeout = Duration::from_millis(v);
153        }
154        if let Some(v) = params.get("allowOrigin") {
155            if v.is_empty() {
156                return Err(CamelError::InvalidUri(
157                    "allowOrigin must not be empty when specified".into(),
158                ));
159            }
160            cfg.allow_origin = v.to_string();
161        }
162        if let Some(v) = params.get("tlsCert") {
163            cfg.tls_cert = Some(v.to_string());
164        }
165        if let Some(v) = params.get("tlsKey") {
166            cfg.tls_key = Some(v.to_string());
167        }
168
169        Ok(cfg)
170    }
171
172    pub fn server_config(&self) -> WsServerConfig {
173        WsServerConfig {
174            inner: self.clone(),
175        }
176    }
177
178    pub fn client_config(&self) -> WsClientConfig {
179        WsClientConfig {
180            inner: self.clone(),
181        }
182    }
183
184    pub fn canonical_host(&self) -> String {
185        match self.host.as_str() {
186            "0.0.0.0" | "localhost" => "127.0.0.1".to_string(),
187            h => h.to_string(),
188        }
189    }
190}