Skip to main content

wscall_server/
server_types.rs

1use std::net::SocketAddr;
2use std::sync::Arc;
3
4use serde::de::DeserializeOwned;
5use serde_json::{Value, json};
6use thiserror::Error;
7use tokio::sync::{RwLock, mpsc};
8use validator::Validate;
9use wscall_protocol::{
10    EncryptionKind, ErrorPayload, FileAttachment, PacketEnvelope, ProtocolError,
11};
12
13use crate::validation;
14
15pub(crate) enum ServerOutbound {
16    Packet(PacketEnvelope),
17    Ping(Vec<u8>),
18    Pong(Vec<u8>),
19    Close,
20}
21
22pub(crate) struct ServerState {
23    pub clients: RwLock<std::collections::HashMap<String, mpsc::Sender<ServerOutbound>>>,
24}
25
26/// Handle that can be cloned into request and event handlers for server push operations.
27#[derive(Clone)]
28pub struct ServerHandle {
29    pub(crate) state: Arc<ServerState>,
30    pub(crate) default_encryption: EncryptionKind,
31}
32
33/// Context passed to server connection lifecycle handlers.
34#[derive(Clone)]
35pub struct ServerConnectionContext {
36    pub(crate) connection_id: String,
37    pub(crate) peer_addr: Option<SocketAddr>,
38    pub(crate) server: ServerHandle,
39}
40
41impl ServerConnectionContext {
42    /// Returns the logical connection id for the current client.
43    pub fn connection_id(&self) -> &str {
44        &self.connection_id
45    }
46
47    /// Returns the peer socket address when available.
48    pub fn peer_addr(&self) -> Option<SocketAddr> {
49        self.peer_addr
50    }
51
52    /// Returns the peer IP as a string when available.
53    pub fn peer_ip(&self) -> Option<String> {
54        self.peer_addr.map(|addr| addr.ip().to_string())
55    }
56
57    /// Returns a server handle for outbound event operations.
58    pub fn server(&self) -> &ServerHandle {
59        &self.server
60    }
61}
62
63/// Context passed to server disconnect lifecycle handlers.
64#[derive(Clone)]
65pub struct ServerDisconnectContext {
66    pub(crate) connection_id: String,
67    pub(crate) peer_addr: Option<SocketAddr>,
68    pub(crate) reason: String,
69    pub(crate) server: ServerHandle,
70}
71
72impl ServerDisconnectContext {
73    /// Returns the logical connection id for the disconnected client.
74    pub fn connection_id(&self) -> &str {
75        &self.connection_id
76    }
77
78    /// Returns the peer socket address when available.
79    pub fn peer_addr(&self) -> Option<SocketAddr> {
80        self.peer_addr
81    }
82
83    /// Returns the peer IP as a string when available.
84    pub fn peer_ip(&self) -> Option<String> {
85        self.peer_addr.map(|addr| addr.ip().to_string())
86    }
87
88    /// Returns the human-readable disconnect reason.
89    pub fn reason(&self) -> &str {
90        &self.reason
91    }
92
93    /// Returns a server handle for outbound event operations.
94    pub fn server(&self) -> &ServerHandle {
95        &self.server
96    }
97}
98
99/// Request context passed to API route handlers.
100#[derive(Clone)]
101pub struct ApiContext {
102    pub(crate) connection_id: String,
103    pub(crate) peer_addr: Option<SocketAddr>,
104    pub(crate) request_id: String,
105    pub(crate) route: String,
106    pub(crate) params: Value,
107    pub(crate) attachments: Vec<FileAttachment>,
108    pub(crate) metadata: Value,
109    pub(crate) server: ServerHandle,
110}
111
112/// Trait for custom parameter validation after JSON binding.
113pub trait ValidateParams {
114    fn validate(&self) -> Result<(), ApiError>;
115}
116
117impl ApiContext {
118    /// Returns the logical connection id for the current client.
119    pub fn connection_id(&self) -> &str {
120        &self.connection_id
121    }
122
123    /// Returns the peer socket address when available.
124    pub fn peer_addr(&self) -> Option<SocketAddr> {
125        self.peer_addr
126    }
127
128    /// Returns the peer IP as a string when available.
129    pub fn peer_ip(&self) -> Option<String> {
130        self.peer_addr.map(|addr| addr.ip().to_string())
131    }
132
133    /// Returns the request correlation id.
134    pub fn request_id(&self) -> &str {
135        &self.request_id
136    }
137
138    /// Returns the matched route name.
139    pub fn route(&self) -> &str {
140        &self.route
141    }
142
143    /// Returns the raw JSON params payload.
144    pub fn params(&self) -> &Value {
145        &self.params
146    }
147
148    /// Looks up a single parameter by key.
149    pub fn param(&self, key: &str) -> Option<&Value> {
150        self.params.as_object()?.get(key)
151    }
152
153    /// Looks up a required parameter or returns a bad request error.
154    pub fn require_param(&self, key: &str) -> Result<&Value, ApiError> {
155        self.param(key)
156            .ok_or_else(|| ApiError::bad_request(format!("missing required param: {key}")))
157    }
158
159    /// Binds the raw params payload into a strongly typed value.
160    pub fn bind<T>(&self) -> Result<T, ApiError>
161    where
162        T: DeserializeOwned,
163    {
164        serde_json::from_value(self.params.clone())
165            .map_err(|source| ApiError::bad_request(format!("invalid params: {source}")))
166    }
167
168    /// Binds params and runs `ValidateParams` on the result.
169    pub fn bind_and_validate<T>(&self) -> Result<T, ApiError>
170    where
171        T: DeserializeOwned + ValidateParams,
172    {
173        let params: T = self.bind()?;
174        params.validate()?;
175        Ok(params)
176    }
177
178    /// Binds params and runs `validator::Validate` on the result.
179    pub fn bind_validated<T>(&self) -> Result<T, ApiError>
180    where
181        T: DeserializeOwned + Validate,
182    {
183        let params: T = self.bind()?;
184        params.validate().map_err(|source| {
185            ApiError::bad_request("params validation failed").with_details(json!({
186                "validation_errors": validation::errors_to_details(&source),
187            }))
188        })?;
189        Ok(params)
190    }
191
192    /// Returns all attachments that accompanied the request.
193    pub fn attachments(&self) -> &[FileAttachment] {
194        &self.attachments
195    }
196
197    /// Returns the raw metadata payload.
198    pub fn metadata(&self) -> &Value {
199        &self.metadata
200    }
201
202    /// Returns a server handle for outbound event operations.
203    pub fn server(&self) -> &ServerHandle {
204        &self.server
205    }
206
207    /// Returns a simplified JSON view of incoming attachments.
208    pub fn attachment_summaries(&self) -> Vec<Value> {
209        self.attachments
210            .iter()
211            .map(|attachment| {
212                json!({
213                    "id": attachment.id,
214                    "name": attachment.name,
215                    "content_type": attachment.content_type,
216                    "size": attachment.size,
217                })
218            })
219            .collect()
220    }
221}
222
223/// Event context passed to event handlers.
224#[derive(Clone)]
225pub struct EventContext {
226    pub(crate) connection_id: String,
227    pub(crate) peer_addr: Option<SocketAddr>,
228    pub(crate) event_id: String,
229    pub(crate) name: String,
230    pub(crate) data: Value,
231    pub(crate) attachments: Vec<FileAttachment>,
232    pub(crate) metadata: Value,
233    pub(crate) server: ServerHandle,
234}
235
236impl EventContext {
237    /// Returns the logical connection id for the current client.
238    pub fn connection_id(&self) -> &str {
239        &self.connection_id
240    }
241
242    /// Returns the peer socket address when available.
243    pub fn peer_addr(&self) -> Option<SocketAddr> {
244        self.peer_addr
245    }
246
247    /// Returns the peer IP as a string when available.
248    pub fn peer_ip(&self) -> Option<String> {
249        self.peer_addr.map(|addr| addr.ip().to_string())
250    }
251
252    /// Returns the event correlation id.
253    pub fn event_id(&self) -> &str {
254        &self.event_id
255    }
256
257    /// Returns the event name.
258    pub fn name(&self) -> &str {
259        &self.name
260    }
261
262    /// Returns the raw JSON event data.
263    pub fn data(&self) -> &Value {
264        &self.data
265    }
266
267    /// Returns attachments that accompanied the event.
268    pub fn attachments(&self) -> &[FileAttachment] {
269        &self.attachments
270    }
271
272    /// Returns the raw metadata payload.
273    pub fn metadata(&self) -> &Value {
274        &self.metadata
275    }
276
277    /// Returns a server handle for outbound event operations.
278    pub fn server(&self) -> &ServerHandle {
279        &self.server
280    }
281}
282
283/// Context passed to the global exception handler.
284#[derive(Clone)]
285pub struct ExceptionContext {
286    pub connection_id: String,
287    pub request_id: Option<String>,
288    pub target: String,
289    pub message_kind: &'static str,
290    pub error: ApiError,
291}
292
293/// Application-level error returned from handlers.
294#[derive(Debug, Clone, Error)]
295#[error("{code}: {message}")]
296pub struct ApiError {
297    pub code: String,
298    pub message: String,
299    pub status: u16,
300    pub details: Option<Value>,
301}
302
303impl ApiError {
304    /// Constructs a 400 bad request error.
305    pub fn bad_request(message: impl Into<String>) -> Self {
306        Self::new("bad_request", message, 400)
307    }
308
309    /// Constructs a 404 not found error.
310    pub fn not_found(message: impl Into<String>) -> Self {
311        Self::new("not_found", message, 404)
312    }
313
314    /// Constructs a 500 internal error.
315    pub fn internal(message: impl Into<String>) -> Self {
316        Self::new("internal_error", message, 500)
317    }
318
319    /// Constructs an error with an explicit code and status.
320    pub fn new(code: impl Into<String>, message: impl Into<String>, status: u16) -> Self {
321        Self {
322            code: code.into(),
323            message: message.into(),
324            status,
325            details: None,
326        }
327    }
328
329    /// Attaches structured details to the error.
330    pub fn with_details(mut self, details: Value) -> Self {
331        self.details = Some(details);
332        self
333    }
334
335    /// Converts the error into a transport payload.
336    pub fn into_payload(self) -> ErrorPayload {
337        ErrorPayload {
338            code: self.code,
339            message: self.message,
340            status: self.status,
341            details: self.details,
342        }
343    }
344}
345
346/// Errors produced by the reusable server runtime.
347#[derive(Debug, Error)]
348pub enum ServerError {
349    #[error("io error: {0}")]
350    Io(#[from] std::io::Error),
351    #[error("websocket error: {0}")]
352    WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
353    #[error("protocol error: {0}")]
354    Protocol(#[from] ProtocolError),
355    #[error("api error: {0:?}")]
356    Api(#[from] ApiError),
357    #[error("connection idle timeout: {0}")]
358    IdleTimeout(String),
359    #[error("outbound queue is full for connection {0}")]
360    OutboundQueueFull(String),
361}