use fluent_uri::Uri;
use mqttbytes::{v5::SubscribeFilter, QoS};
use rand::{distr::Alphanumeric, rng, Rng};
use super::{
mqtt::MqttWorker, Message, MqttClient, MqttClientConfig, OnConnectedCallback, OnMessageCallback,
};
use crate::client::config::OnMessageOwnedCallback;
use std::time::Instant;
pub struct MqttClientBuilder {
mqtt_broker: String,
client_id: Option<String>,
clean_session: bool,
subscribe_filters: Vec<SubscribeFilter>,
on_message_callback: Option<Box<OnMessageCallback>>,
on_message_owned_callback: Option<Box<OnMessageOwnedCallback>>,
on_connected_callback: Option<Box<OnConnectedCallback>>,
}
#[derive(Debug, thiserror::Error)]
pub enum MqttClientBuilderError {
#[error("Invalid MQTT broker URI: {0}")]
InvalidUri(#[source] fluent_uri::ParseError),
}
impl MqttClientBuilder {
pub fn new(uri: &str) -> Self {
Self {
mqtt_broker: uri.to_owned(),
client_id: None,
clean_session: false,
subscribe_filters: vec![],
on_message_callback: None,
on_message_owned_callback: None,
on_connected_callback: None,
}
}
pub fn client_id<S: Into<String>>(mut self, client_id: S) -> Self {
self.client_id = Some(client_id.into());
self
}
pub fn clean_session(mut self, clean_session: bool) -> Self {
self.clean_session = clean_session;
self
}
pub fn subscribe<S: Into<String>>(mut self, filter: S, qos: QoS) -> Self {
if qos == QoS::ExactlyOnce {
panic!("Quantity of Service 2 (Exactly Once) is not supported");
}
let sf = SubscribeFilter::new(filter.into(), qos);
self.subscribe_filters.push(sf);
self
}
pub fn on_message_callback<F>(mut self, callback: F) -> Self
where
F: FnMut(&Message) + Send + Sync + 'static,
{
self.on_message_callback = Some(Box::new(callback));
self
}
pub fn on_connected_callback<F>(mut self, callback: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
self.on_connected_callback = Some(Box::new(callback));
self
}
pub fn on_message_owned_callback<F>(mut self, callback: F) -> Self
where
F: FnMut(Message, Instant) + Send + 'static,
{
self.on_message_owned_callback = Some(Box::new(callback));
self
}
pub fn build(self) -> Result<(MqttClient, MqttWorker), MqttClientBuilderError> {
let uri = Uri::parse(self.mqtt_broker)
.map_err(|(err, _u)| MqttClientBuilderError::InvalidUri(err))?;
let client_id = self.client_id.unwrap_or_else(|| {
let username = uri
.authority()
.and_then(|a| a.userinfo())
.map(|u| u.as_str().split_once(':').map_or(u.as_str(), |(u, _)| u))
.unwrap_or("");
if !username.is_empty() {
format!("{}-{}", username, random_string(8))
} else {
random_string(16)
}
});
let config = MqttClientConfig {
client_id,
subscribe_filters: self.subscribe_filters,
on_message_callback: self.on_message_callback,
on_connected_callback: self.on_connected_callback,
clean_session: self.clean_session,
};
Ok(MqttClient::new(uri, config, self.on_message_owned_callback))
}
}
fn random_string(length: usize) -> String {
rng()
.sample_iter(&Alphanumeric)
.take(length)
.map(char::from)
.collect()
}