wscall_server/
server_types.rs1use 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 ApiContext {
36 pub(crate) connection_id: String,
37 pub(crate) peer_addr: Option<SocketAddr>,
38 pub(crate) request_id: String,
39 pub(crate) route: String,
40 pub(crate) params: Value,
41 pub(crate) attachments: Vec<FileAttachment>,
42 pub(crate) metadata: Value,
43 pub(crate) server: ServerHandle,
44}
45
46pub trait ValidateParams {
48 fn validate(&self) -> Result<(), ApiError>;
49}
50
51impl ApiContext {
52 pub fn connection_id(&self) -> &str {
54 &self.connection_id
55 }
56
57 pub fn peer_addr(&self) -> Option<SocketAddr> {
59 self.peer_addr
60 }
61
62 pub fn peer_ip(&self) -> Option<String> {
64 self.peer_addr.map(|addr| addr.ip().to_string())
65 }
66
67 pub fn request_id(&self) -> &str {
69 &self.request_id
70 }
71
72 pub fn route(&self) -> &str {
74 &self.route
75 }
76
77 pub fn params(&self) -> &Value {
79 &self.params
80 }
81
82 pub fn param(&self, key: &str) -> Option<&Value> {
84 self.params.as_object()?.get(key)
85 }
86
87 pub fn require_param(&self, key: &str) -> Result<&Value, ApiError> {
89 self.param(key)
90 .ok_or_else(|| ApiError::bad_request(format!("missing required param: {key}")))
91 }
92
93 pub fn bind<T>(&self) -> Result<T, ApiError>
95 where
96 T: DeserializeOwned,
97 {
98 serde_json::from_value(self.params.clone())
99 .map_err(|source| ApiError::bad_request(format!("invalid params: {source}")))
100 }
101
102 pub fn bind_and_validate<T>(&self) -> Result<T, ApiError>
104 where
105 T: DeserializeOwned + ValidateParams,
106 {
107 let params: T = self.bind()?;
108 params.validate()?;
109 Ok(params)
110 }
111
112 pub fn bind_validated<T>(&self) -> Result<T, ApiError>
114 where
115 T: DeserializeOwned + Validate,
116 {
117 let params: T = self.bind()?;
118 params.validate().map_err(|source| {
119 ApiError::bad_request("params validation failed").with_details(json!({
120 "validation_errors": validation::errors_to_details(&source),
121 }))
122 })?;
123 Ok(params)
124 }
125
126 pub fn attachments(&self) -> &[FileAttachment] {
128 &self.attachments
129 }
130
131 pub fn metadata(&self) -> &Value {
133 &self.metadata
134 }
135
136 pub fn server(&self) -> &ServerHandle {
138 &self.server
139 }
140
141 pub fn attachment_summaries(&self) -> Vec<Value> {
143 self.attachments
144 .iter()
145 .map(|attachment| {
146 json!({
147 "id": attachment.id,
148 "name": attachment.name,
149 "content_type": attachment.content_type,
150 "size": attachment.size,
151 })
152 })
153 .collect()
154 }
155}
156
157#[derive(Clone)]
159pub struct EventContext {
160 pub(crate) connection_id: String,
161 pub(crate) peer_addr: Option<SocketAddr>,
162 pub(crate) event_id: String,
163 pub(crate) name: String,
164 pub(crate) data: Value,
165 pub(crate) attachments: Vec<FileAttachment>,
166 pub(crate) metadata: Value,
167 pub(crate) server: ServerHandle,
168}
169
170impl EventContext {
171 pub fn connection_id(&self) -> &str {
173 &self.connection_id
174 }
175
176 pub fn peer_addr(&self) -> Option<SocketAddr> {
178 self.peer_addr
179 }
180
181 pub fn peer_ip(&self) -> Option<String> {
183 self.peer_addr.map(|addr| addr.ip().to_string())
184 }
185
186 pub fn event_id(&self) -> &str {
188 &self.event_id
189 }
190
191 pub fn name(&self) -> &str {
193 &self.name
194 }
195
196 pub fn data(&self) -> &Value {
198 &self.data
199 }
200
201 pub fn attachments(&self) -> &[FileAttachment] {
203 &self.attachments
204 }
205
206 pub fn metadata(&self) -> &Value {
208 &self.metadata
209 }
210
211 pub fn server(&self) -> &ServerHandle {
213 &self.server
214 }
215}
216
217#[derive(Clone)]
219pub struct ExceptionContext {
220 pub connection_id: String,
221 pub request_id: Option<String>,
222 pub target: String,
223 pub message_kind: &'static str,
224 pub error: ApiError,
225}
226
227#[derive(Debug, Clone, Error)]
229#[error("{code}: {message}")]
230pub struct ApiError {
231 pub code: String,
232 pub message: String,
233 pub status: u16,
234 pub details: Option<Value>,
235}
236
237impl ApiError {
238 pub fn bad_request(message: impl Into<String>) -> Self {
240 Self::new("bad_request", message, 400)
241 }
242
243 pub fn not_found(message: impl Into<String>) -> Self {
245 Self::new("not_found", message, 404)
246 }
247
248 pub fn internal(message: impl Into<String>) -> Self {
250 Self::new("internal_error", message, 500)
251 }
252
253 pub fn new(code: impl Into<String>, message: impl Into<String>, status: u16) -> Self {
255 Self {
256 code: code.into(),
257 message: message.into(),
258 status,
259 details: None,
260 }
261 }
262
263 pub fn with_details(mut self, details: Value) -> Self {
265 self.details = Some(details);
266 self
267 }
268
269 pub fn into_payload(self) -> ErrorPayload {
271 ErrorPayload {
272 code: self.code,
273 message: self.message,
274 status: self.status,
275 details: self.details,
276 }
277 }
278}
279
280#[derive(Debug, Error)]
282pub enum ServerError {
283 #[error("io error: {0}")]
284 Io(#[from] std::io::Error),
285 #[error("websocket error: {0}")]
286 WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
287 #[error("protocol error: {0}")]
288 Protocol(#[from] ProtocolError),
289 #[error("api error: {0:?}")]
290 Api(#[from] ApiError),
291 #[error("connection idle timeout: {0}")]
292 IdleTimeout(String),
293 #[error("outbound queue is full for connection {0}")]
294 OutboundQueueFull(String),
295}