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#[derive(Clone)]
28pub struct ServerHandle {
29 pub(crate) state: Arc<ServerState>,
30 pub(crate) default_encryption: EncryptionKind,
31}
32
33#[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 pub fn connection_id(&self) -> &str {
44 &self.connection_id
45 }
46
47 pub fn peer_addr(&self) -> Option<SocketAddr> {
49 self.peer_addr
50 }
51
52 pub fn peer_ip(&self) -> Option<String> {
54 self.peer_addr.map(|addr| addr.ip().to_string())
55 }
56
57 pub fn server(&self) -> &ServerHandle {
59 &self.server
60 }
61}
62
63#[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 pub fn connection_id(&self) -> &str {
75 &self.connection_id
76 }
77
78 pub fn peer_addr(&self) -> Option<SocketAddr> {
80 self.peer_addr
81 }
82
83 pub fn peer_ip(&self) -> Option<String> {
85 self.peer_addr.map(|addr| addr.ip().to_string())
86 }
87
88 pub fn reason(&self) -> &str {
90 &self.reason
91 }
92
93 pub fn server(&self) -> &ServerHandle {
95 &self.server
96 }
97}
98
99#[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
112pub trait ValidateParams {
114 fn validate(&self) -> Result<(), ApiError>;
115}
116
117impl ApiContext {
118 pub fn connection_id(&self) -> &str {
120 &self.connection_id
121 }
122
123 pub fn peer_addr(&self) -> Option<SocketAddr> {
125 self.peer_addr
126 }
127
128 pub fn peer_ip(&self) -> Option<String> {
130 self.peer_addr.map(|addr| addr.ip().to_string())
131 }
132
133 pub fn request_id(&self) -> &str {
135 &self.request_id
136 }
137
138 pub fn route(&self) -> &str {
140 &self.route
141 }
142
143 pub fn params(&self) -> &Value {
145 &self.params
146 }
147
148 pub fn param(&self, key: &str) -> Option<&Value> {
150 self.params.as_object()?.get(key)
151 }
152
153 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 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 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 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 pub fn attachments(&self) -> &[FileAttachment] {
194 &self.attachments
195 }
196
197 pub fn metadata(&self) -> &Value {
199 &self.metadata
200 }
201
202 pub fn server(&self) -> &ServerHandle {
204 &self.server
205 }
206
207 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#[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 pub fn connection_id(&self) -> &str {
239 &self.connection_id
240 }
241
242 pub fn peer_addr(&self) -> Option<SocketAddr> {
244 self.peer_addr
245 }
246
247 pub fn peer_ip(&self) -> Option<String> {
249 self.peer_addr.map(|addr| addr.ip().to_string())
250 }
251
252 pub fn event_id(&self) -> &str {
254 &self.event_id
255 }
256
257 pub fn name(&self) -> &str {
259 &self.name
260 }
261
262 pub fn data(&self) -> &Value {
264 &self.data
265 }
266
267 pub fn attachments(&self) -> &[FileAttachment] {
269 &self.attachments
270 }
271
272 pub fn metadata(&self) -> &Value {
274 &self.metadata
275 }
276
277 pub fn server(&self) -> &ServerHandle {
279 &self.server
280 }
281}
282
283#[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#[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 pub fn bad_request(message: impl Into<String>) -> Self {
306 Self::new("bad_request", message, 400)
307 }
308
309 pub fn not_found(message: impl Into<String>) -> Self {
311 Self::new("not_found", message, 404)
312 }
313
314 pub fn internal(message: impl Into<String>) -> Self {
316 Self::new("internal_error", message, 500)
317 }
318
319 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 pub fn with_details(mut self, details: Value) -> Self {
331 self.details = Some(details);
332 self
333 }
334
335 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#[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}