monocle/server/
handler.rs1use crate::server::op_sink::WsOpSink;
8use crate::server::protocol::{ErrorCode, ErrorData, RequestEnvelope};
9use async_trait::async_trait;
10use serde::de::DeserializeOwned;
11use serde_json::Value;
12use std::sync::Arc;
13
14use crate::config::MonocleConfig;
19
20#[derive(Clone)]
28pub struct WsContext {
29 pub config: MonocleConfig,
31}
32
33impl WsContext {
34 pub fn from_config(config: MonocleConfig) -> Self {
36 Self { config }
37 }
38
39 pub fn data_dir(&self) -> &str {
41 &self.config.data_dir
42 }
43}
44
45impl Default for WsContext {
46 fn default() -> Self {
47 Self::from_config(MonocleConfig::default())
48 }
49}
50
51#[derive(Debug, Clone)]
57pub struct WsRequest {
58 pub id: String,
60
61 pub op_id: Option<String>,
63
64 pub method: String,
66
67 pub params: Value,
69}
70
71impl WsRequest {
72 pub fn from_envelope(envelope: RequestEnvelope) -> Self {
76 let id = envelope
77 .id
78 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
79 Self {
80 id,
81 op_id: None,
82 method: envelope.method,
83 params: envelope.params,
84 }
85 }
86}
87
88pub type WsResult<T> = Result<T, WsError>;
94
95#[derive(Debug, Clone)]
97pub struct WsError {
98 pub code: ErrorCode,
100 pub message: String,
102 pub details: Option<Value>,
104}
105
106impl WsError {
107 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
109 Self {
110 code,
111 message: message.into(),
112 details: None,
113 }
114 }
115
116 pub fn with_details(code: ErrorCode, message: impl Into<String>, details: Value) -> Self {
118 Self {
119 code,
120 message: message.into(),
121 details: Some(details),
122 }
123 }
124
125 pub fn invalid_params(message: impl Into<String>) -> Self {
127 Self::new(ErrorCode::InvalidParams, message)
128 }
129
130 pub fn operation_failed(message: impl Into<String>) -> Self {
132 Self::new(ErrorCode::OperationFailed, message)
133 }
134
135 pub fn not_initialized(resource: &str) -> Self {
137 Self::new(
138 ErrorCode::NotInitialized,
139 format!("{} data not initialized", resource),
140 )
141 }
142
143 pub fn internal(message: impl Into<String>) -> Self {
145 Self::new(ErrorCode::InternalError, message)
146 }
147
148 pub fn to_error_data(&self) -> ErrorData {
150 match &self.details {
151 Some(details) => {
152 ErrorData::with_details(self.code, self.message.clone(), details.clone())
153 }
154 None => ErrorData::new(self.code, self.message.clone()),
155 }
156 }
157}
158
159impl std::fmt::Display for WsError {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 write!(f, "{:?}: {}", self.code, self.message)
162 }
163}
164
165impl std::error::Error for WsError {}
166
167impl From<anyhow::Error> for WsError {
168 fn from(err: anyhow::Error) -> Self {
169 Self::operation_failed(err.to_string())
170 }
171}
172
173impl From<serde_json::Error> for WsError {
174 fn from(err: serde_json::Error) -> Self {
175 Self::invalid_params(err.to_string())
176 }
177}
178
179#[async_trait]
187pub trait WsMethod: Send + Sync + 'static {
188 const METHOD: &'static str;
190
191 const IS_STREAMING: bool = false;
193
194 type Params: DeserializeOwned + Send;
196
197 fn validate(_params: &Self::Params) -> WsResult<()> {
201 Ok(())
202 }
203
204 async fn handle(
209 ctx: Arc<WsContext>,
210 req: WsRequest,
211 params: Self::Params,
212 sink: WsOpSink,
213 ) -> WsResult<()>;
214}
215
216pub type DynHandler = Box<
222 dyn Fn(Arc<WsContext>, WsRequest, WsOpSink) -> futures::future::BoxFuture<'static, WsResult<()>>
223 + Send
224 + Sync,
225>;
226
227pub fn make_handler<M: WsMethod>() -> DynHandler {
229 Box::new(move |ctx, req, sink| {
230 Box::pin(async move {
231 let params: M::Params = serde_json::from_value(req.params.clone())?;
233
234 M::validate(¶ms)?;
236
237 M::handle(ctx, req, params, sink).await
239 })
240 })
241}
242
243#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_ws_context_default() {
253 let ctx = WsContext::default();
254 assert!(ctx.data_dir().contains("monocle"));
255 }
256
257 #[test]
258 fn test_ws_context_from_config() {
259 let config = MonocleConfig::default();
260 let ctx = WsContext::from_config(config.clone());
261 assert_eq!(ctx.data_dir(), &config.data_dir);
262 }
263
264 #[test]
265 fn test_ws_request_from_envelope() {
266 let envelope = RequestEnvelope {
268 id: Some("test-id".to_string()),
269 method: "time.parse".to_string(),
270 params: serde_json::json!({}),
271 };
272 let req = WsRequest::from_envelope(envelope);
273 assert_eq!(req.id, "test-id");
274 assert_eq!(req.op_id, None);
275 assert_eq!(req.method, "time.parse");
276
277 let envelope = RequestEnvelope {
279 id: None,
280 method: "time.parse".to_string(),
281 params: serde_json::json!({}),
282 };
283 let req = WsRequest::from_envelope(envelope);
284 assert!(!req.id.is_empty());
285 assert_ne!(req.id, "test-id"); }
287
288 #[test]
289 fn test_ws_error_conversion() {
290 let err = WsError::invalid_params("missing field");
291 assert_eq!(err.code, ErrorCode::InvalidParams);
292 assert!(err.message.contains("missing field"));
293
294 let error_data = err.to_error_data();
295 assert_eq!(error_data.code, ErrorCode::InvalidParams);
296 }
297
298 #[test]
299 fn test_ws_error_from_anyhow() {
300 let anyhow_err = anyhow::anyhow!("something went wrong");
301 let ws_err: WsError = anyhow_err.into();
302 assert_eq!(ws_err.code, ErrorCode::OperationFailed);
303 assert!(ws_err.message.contains("something went wrong"));
304 }
305}