mpc_manager/
service.rs

1//! # JSON-RPC 2.0 services.
2//!
3//! Services are in charge of handling requests and notifications. They are
4//! registered in the server and invoked according to the request method.
5
6#[cfg(feature = "server")]
7use self::{
8    group_service::GroupService, notification::Notification, session_service::SessionService,
9};
10#[cfg(feature = "server")]
11use crate::state::{ClientId, State};
12#[cfg(feature = "server")]
13use axum::async_trait;
14#[cfg(feature = "server")]
15use std::{collections::HashMap, sync::Arc};
16
17pub mod group_service;
18pub mod notification;
19pub mod session_service;
20
21/// Separator for subroutes.
22pub const SUBROUTE_SEPARATOR: &str = "_";
23
24#[cfg(feature = "server")]
25type ServiceResponse = Result<Option<json_rpc2::Response>, json_rpc2::Error>;
26
27/// Trait for async services that maybe handle a request.
28#[async_trait]
29#[cfg(feature = "server")]
30pub trait Service: Send + Sync {
31    /// Handles incoming requests.
32    ///
33    /// Should reply with a response or a `None`, according to the message type
34    /// (request or notification).
35    ///
36    /// If the method is not handled by the service it should
37    /// return an error of type `MethodNotFound`.
38    async fn handle(
39        &self,
40        request: &json_rpc2::Request,
41        ctx: (Arc<State>, Arc<tokio::sync::Mutex<Vec<Notification>>>),
42        client_id: ClientId,
43    ) -> Result<Option<json_rpc2::Response>, json_rpc2::Error>;
44}
45
46/// Service handler in charge of routing communications to given
47/// services, in case they match, otherwise returns not found.
48#[cfg(feature = "server")]
49pub struct ServiceHandler {
50    /// Services that the server should invoke for every request.
51    services: HashMap<String, Box<dyn Service>>,
52}
53
54#[cfg(feature = "server")]
55impl ServiceHandler {
56    /// Create a new ServiceHandler.
57    pub fn new() -> Self {
58        let mut services: HashMap<String, Box<dyn Service>> = HashMap::new();
59        let group_service: Box<dyn Service> = Box::new(GroupService {});
60        let session_service: Box<dyn Service> = Box::new(SessionService {});
61        services.insert(group_service::ROUTE_PREFIX.into(), group_service);
62        services.insert(session_service::ROUTE_PREFIX.into(), session_service);
63        Self { services }
64    }
65
66    /// Infallible service handler, errors are automatically converted to responses.
67    ///
68    /// If a request was a notification (no id field) this will yield `None`.
69    pub async fn serve(
70        &self,
71        request: &json_rpc2::Request,
72        ctx: (Arc<State>, Arc<tokio::sync::Mutex<Vec<Notification>>>),
73        client_id: ClientId,
74    ) -> Option<json_rpc2::Response> {
75        match self.handle(request, ctx, client_id).await {
76            Ok(response) => response,
77            Err(e) => Some((request, e).into()),
78        }
79    }
80
81    /// Call services according to subroute.
82    ///
83    /// If no services match the incoming request this will
84    /// return `json_rpc2::Error::MethodNotFound`.
85    async fn handle(
86        &self,
87        req: &json_rpc2::Request,
88        ctx: (Arc<State>, Arc<tokio::sync::Mutex<Vec<Notification>>>),
89        client_id: ClientId,
90    ) -> Result<Option<json_rpc2::Response>, json_rpc2::Error> {
91        let subroute = req.method().split(SUBROUTE_SEPARATOR);
92        let subroute: Vec<&str> = subroute.collect();
93
94        if subroute.len() < 2 {
95            return Ok(Some(self.build_error_not_found_method(req)));
96        }
97
98        let subroute = subroute[0];
99        if let Some(service) = self.services.get(subroute) {
100            return service.handle(req, ctx, client_id).await;
101        }
102
103        Ok(Some(self.build_error_not_found_method(req)))
104    }
105
106    /// Build a `json_rpc2::Error::MethodNotFound` error response.
107    fn build_error_not_found_method(&self, req: &json_rpc2::Request) -> json_rpc2::Response {
108        let err = json_rpc2::Error::MethodNotFound {
109            name: req.method().to_string(),
110            id: req.id().clone(),
111        };
112        (req, err).into()
113    }
114}
115
116#[cfg(feature = "server")]
117impl Default for ServiceHandler {
118    fn default() -> Self {
119        Self::new()
120    }
121}