perspective_client/session.rs
1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use std::error::Error as StdError;
14use std::sync::Arc;
15
16use futures::Future;
17use prost::Message;
18
19use crate::proto::request::ClientReq;
20use crate::proto::{Request, Response};
21use crate::{Client, ClientError, asyncfn};
22#[cfg(doc)]
23use crate::{Table, View};
24
25/// The server-side representation of a connection to a [`Client`].
26///
27/// For each [`Client`] that wants to connect to a `perspective_server::Server`,
28/// a dedicated [`Session`] must be created. The [`Session`] handles routing
29/// messages emitted by the `perspective_server::Server`ve_server::Server`, as
30/// well as owning any resources the [`Client`] may request.
31pub trait Session<E> {
32    /// Handle an incoming request from the [`Client`]. Calling
33    /// [`Session::handle_request`] will result in the `send_response` parameter
34    /// which was used to construct this [`Session`] to fire one or more times.
35    ///
36    /// # Arguments
37    ///
38    /// - `request` An incoming request message, generated from a
39    ///   [`Client::new`]'s `send_request` handler (which may-or-may-not be
40    ///   local).
41    fn handle_request(&self, request: &[u8]) -> impl Future<Output = Result<(), E>>;
42
43    /// Close this [`Session`], cleaning up any callbacks (e.g. arguments
44    /// provided to [`Session::handle_request`]) and resources (e.g. views
45    /// returned by a call to [`Table::view`]).
46    ///
47    /// Dropping a [`Session`] outside of the context of [`Session::close`]
48    /// will cause a [`tracing`] error-level log to be emitted, but won't fail.
49    /// They will, however, leak.
50    fn close(self) -> impl Future<Output = ()>;
51}
52
53type ProxyCallbackError = Box<dyn StdError + Send + Sync>;
54type ProxyCallback = Arc<dyn Fn(&[u8]) -> Result<(), ProxyCallbackError> + Send + Sync>;
55
56/// A [`Session`] implementation which tunnels through another [`Client`].
57/// @private
58#[derive(Clone)]
59pub struct ProxySession {
60    parent: Client,
61    callback: ProxyCallback,
62}
63
64impl ProxySession {
65    pub fn new(
66        client: Client,
67        send_response: impl Fn(&[u8]) -> Result<(), ProxyCallbackError> + Send + Sync + 'static,
68    ) -> Self {
69        ProxySession {
70            parent: client,
71            callback: Arc::new(send_response),
72        }
73    }
74}
75
76fn encode(response: Response, callback: ProxyCallback) -> Result<(), ClientError> {
77    let mut enc = vec![];
78    response.encode(&mut enc)?;
79    callback(&enc).map_err(|x| ClientError::Unknown(x.to_string()))?;
80    Ok(())
81}
82
83impl Session<ClientError> for ProxySession {
84    async fn handle_request(&self, request: &[u8]) -> Result<(), ClientError> {
85        let req = Request::decode(request)?;
86        let callback = self.callback.clone();
87        match req.client_req.as_ref() {
88            Some(ClientReq::ViewOnUpdateReq(_)) => {
89                let on_update =
90                    asyncfn!(callback, async move |response| encode(response, callback));
91                self.parent.subscribe(&req, on_update).await?
92            },
93            Some(_) => {
94                let on_update = move |response| encode(response, callback);
95                self.parent
96                    .subscribe_once(&req, Box::new(on_update))
97                    .await?
98            },
99            None => {
100                return Err(ClientError::Internal(
101                    "ProxySession::handle_request: invalid request".to_string(),
102                ));
103            },
104        };
105
106        Ok(())
107    }
108
109    async fn close(self) {}
110}