tcp_console/
builder.rs

1use crate::console::{Console, Error};
2use crate::ensure_newline;
3use crate::subscription::{BoxedSubscription, Subscription};
4use std::collections::hash_map::Entry;
5use std::collections::HashMap;
6use std::fmt::Debug;
7use std::hash::Hash;
8use tokio::net::ToSocketAddrs;
9
10/// A builder for [Console].
11pub struct Builder<Services, A> {
12    subscriptions: HashMap<Services, BoxedSubscription>,
13    bind_address: Option<A>,
14    welcome: Option<String>,
15    accept_only_localhost: bool,
16}
17
18impl<Services, A> Builder<Services, A>
19where
20    Services: Eq + Hash + Debug,
21    A: ToSocketAddrs,
22{
23    pub fn new() -> Self {
24        Self {
25            subscriptions: HashMap::new(),
26            bind_address: None,
27            welcome: None,
28            accept_only_localhost: false,
29        }
30    }
31
32    pub fn subscribe<S>(mut self, service_id: Services, subscription: S) -> Result<Self, Error>
33    where
34        S: Subscription + Send + Sync + 'static,
35    {
36        // `HashMap::entry(x)` consumes its argument, while we might need this string afterwards.
37        let service_id_string = format!("{service_id:?}");
38
39        match self.subscriptions.entry(service_id) {
40            Entry::Occupied(_) => Err(Error::ServiceIdUsed(service_id_string)),
41            Entry::Vacant(entry) => {
42                entry.insert(Box::new(subscription));
43                Ok(self)
44            }
45        }
46    }
47
48    pub fn bind_address(mut self, bind_address: A) -> Self {
49        self.bind_address = Some(bind_address);
50        self
51    }
52
53    pub fn welcome(mut self, message: &str) -> Self {
54        self.welcome = Some(message.to_owned());
55        self
56    }
57
58    pub fn accept_only_localhost(mut self) -> Self {
59        self.accept_only_localhost = true;
60        self
61    }
62
63    pub fn build(self) -> Result<Console<Services, A>, Error> {
64        let Some(bind_address) = self.bind_address else {
65            return Err(Error::NoBindAddress);
66        };
67
68        Ok(Console::new(
69            self.subscriptions,
70            bind_address,
71            ensure_newline(self.welcome.unwrap_or_default()),
72            self.accept_only_localhost,
73        ))
74    }
75}
76
77impl<Services, A> Default for Builder<Services, A>
78where
79    Services: Eq + Hash + Debug,
80    A: ToSocketAddrs,
81{
82    fn default() -> Self {
83        Self::new()
84    }
85}