1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Provides an API for the user of the library to check the authentication information provided by stomp, hold
//! metadata regarding the client and its connection, and pass that metadata to other implementation-specific
//! code, for example [`crate::destinations::Destination`].
use futures::FutureExt;
use std::{
    future::{ready, Future},
    pin::Pin,
};

use crate::error::StomperError;

/// The default value for the `server` header in the [`stomp_parser::server::ConnectedFrame`]
pub const DEFAULT_SERVER: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));

/// A representation the client and the connection between client and STOMP backend.
///
/// The specific type used
/// will be implementation specific, and can therefore hold arbitrary information, which is passed to destinations when communicating
/// with them. It therefore forms the bridge between the underlying transport and the destinations, allowing information to be shared between them.
pub trait Client: Sync + Send + Clone {
    /// Indicates the value to returns for the `server` header in the [`stomp_parser::server::ConnectedFrame`], if any.
    ///
    /// Defaults to [`DEFAULT_SERVER`].
    fn server(&self) -> Option<String> {
        Some(DEFAULT_SERVER.to_owned())
    }

    /// Indicates the value to returns for the `session` header in the [`stomp_parser::server::ConnectedFrame`], if any.
    ///
    /// Defaults to [`None`]
    fn session(&self) -> Option<String> {
        None
    }
}

/// A factory which allows creation of a single instance of its parameter type, which must implement [`Client`].
///
/// It passes the  the `login` and `passcode` headers from the [`stomp_parser::client::ConnectFrame`] so that the library user may use them as appropriate.
///
/// The expected usage pattern is that the library caller creates a factory initialised based on the transport parameters (e.g. auth settings)
/// and passes the factors to `little-stomper` when the a message stream on that transport ist to be handled by it.
///
/// An implementation for functions and closures with the same singature as [`ClientFactory::create`] is provided.
pub trait ClientFactory<C: Client>: Sync + Send {
    /// Returns a future yielding the [`Client`] instance, consuming the factory.
    fn create<T: AsRef<str>, S: AsRef<str>>(
        self,
        login: Option<T>,
        passcode: Option<S>,
    ) -> Pin<Box<dyn Future<Output = Result<C, StomperError>> + Send + 'static>>;
}

impl<C, F> ClientFactory<C> for F
where
    F: FnOnce(
            Option<&str>,
            Option<&str>,
        ) -> Pin<Box<dyn Future<Output = Result<C, StomperError>> + Send + 'static>>
        + Sync
        + Send,
    C: Client,
{
    fn create<S: AsRef<str>, T: AsRef<str>>(
        self,
        login: Option<S>,
        passcode: Option<T>,
    ) -> Pin<Box<dyn Future<Output = Result<C, StomperError>> + Send + 'static>> {
        self(
            login.as_ref().map(S::as_ref),
            passcode.as_ref().map(T::as_ref),
        )
    }
}

/// A [`Client`] instance which does nothing beyond the default implementations on the trait.
#[derive(Clone)]
pub struct DefaultClient;

impl Client for DefaultClient {}

/// The factory for [`DefaultClient`].
pub struct DefaultClientFactory;

impl ClientFactory<DefaultClient> for DefaultClientFactory {
    fn create<S: AsRef<str>, T: AsRef<str>>(
        self,
        _login: Option<S>,
        _passcode: Option<T>,
    ) -> std::pin::Pin<Box<dyn Future<Output = Result<DefaultClient, StomperError>> + Send + 'static>>
    {
        ready(Ok(DefaultClient {})).boxed()
    }
}
#[cfg(test)]
mod test {
    use super::*;

    #[derive(Clone, Default)]
    struct TestClient {
        login: Option<String>,
        passcode: Option<String>,
    }

    impl Client for TestClient {}

    #[test]
    fn default_server_is_little_stomper_version() {
        let client = TestClient::default();

        assert_eq!(DEFAULT_SERVER, client.server().unwrap());
    }

    #[test]
    fn default_session_is_none() {
        let client = TestClient::default();

        assert_eq!(None, client.session());
    }

    async fn create_client<C: Client, F: ClientFactory<C>>(factory: F) -> C {
        factory
            .create(Some("log1n"), Some("passc0de"))
            .await
            .expect("Should be ok")
    }

    #[tokio::test]
    async fn closure_works_as_factory() {
        let client = create_client(|login: Option<&str>, passcode: Option<&str>| {
            let login = login.map(str::to_owned);
            let passcode = passcode.map(str::to_owned);

            async { Ok(TestClient { login, passcode }) }.boxed()
        })
        .await;

        assert_eq!("log1n", client.login.unwrap());
        assert_eq!("passc0de", client.passcode.unwrap());
    }

    #[test]
    fn default_client_returns_defaults() {
        let client = DefaultClient;

        assert_eq!(DEFAULT_SERVER, client.server().unwrap());
        assert_eq!(None, client.session());
    }

    #[tokio::test]
    async fn default_client_factory_returns_defaults() {
        let client = create_client(DefaultClientFactory).await;

        assert_eq!(DEFAULT_SERVER, client.server().unwrap());
        assert_eq!(None, client.session());
    }
}