use log::{debug, error, info, warn};
use crate::{
definitions::{InputPlugDefinition, OutputPlugDefinition, PlugDefinitionCommon},
three_part_topic::ThreePartTopic,
PlugDefinition, TetherAgent, TetherOrCustomTopic,
};
pub struct InputPlugOptions {
plug_name: String,
qos: Option<i32>,
override_subscribe_role: Option<String>,
override_subscribe_id: Option<String>,
override_subscribe_plug_name: Option<String>,
override_topic: Option<String>,
}
pub struct OutputPlugOptions {
plug_name: String,
qos: Option<i32>,
override_publish_role: Option<String>,
override_publish_id: Option<String>,
override_topic: Option<String>,
retain: Option<bool>,
}
pub enum PlugOptionsBuilder {
InputPlugOptions(InputPlugOptions),
OutputPlugOptions(OutputPlugOptions),
}
impl PlugOptionsBuilder {
pub fn create_input(name: &str) -> PlugOptionsBuilder {
PlugOptionsBuilder::InputPlugOptions(InputPlugOptions {
plug_name: String::from(name),
override_subscribe_id: None,
override_subscribe_role: None,
override_subscribe_plug_name: None,
override_topic: None,
qos: None,
})
}
pub fn create_output(name: &str) -> PlugOptionsBuilder {
PlugOptionsBuilder::OutputPlugOptions(OutputPlugOptions {
plug_name: String::from(name),
override_publish_id: None,
override_publish_role: None,
override_topic: None,
qos: None,
retain: None,
})
}
pub fn qos(mut self, qos: Option<i32>) -> Self {
match &mut self {
PlugOptionsBuilder::InputPlugOptions(s) => s.qos = qos,
PlugOptionsBuilder::OutputPlugOptions(s) => s.qos = qos,
};
self
}
pub fn role(mut self, role: Option<&str>) -> Self {
match &mut self {
PlugOptionsBuilder::InputPlugOptions(s) => {
if s.override_topic.is_some() {
error!("Override topic was also provided; this will take precedence");
} else {
s.override_subscribe_role = role.map(|s| s.into());
}
}
PlugOptionsBuilder::OutputPlugOptions(s) => {
if s.override_topic.is_some() {
error!("Override topic was also provided; this will take precedence");
} else {
s.override_publish_role = role.map(|s| s.into());
}
}
};
self
}
pub fn id(mut self, id: Option<&str>) -> Self {
match &mut self {
PlugOptionsBuilder::InputPlugOptions(s) => {
if s.override_topic.is_some() {
error!("Override topic was also provided; this will take precedence");
} else {
s.override_subscribe_id = id.map(|s| s.into());
}
}
PlugOptionsBuilder::OutputPlugOptions(s) => {
if s.override_topic.is_some() {
error!("Override topic was also provided; this will take precedence");
} else {
s.override_publish_id = id.map(|s| s.into());
}
}
};
self
}
pub fn name(mut self, override_plug_name: Option<&str>) -> Self {
match &mut self {
PlugOptionsBuilder::InputPlugOptions(opt) => {
if opt.override_topic.is_some() {
error!("Override topic was also provided; this will take precedence");
}
if let Some(s) = override_plug_name {
if s.eq("+") {
info!(
"Plug Name part given is a wildcard; subscribe topic will use this but (internally) Plug Name will remain \"{}\"", &opt.plug_name
);
} else {
error!("Input Plugs cannot change their name after ::create_input constructor EXCEPT for wildcard \"+\"");
}
opt.override_subscribe_plug_name = override_plug_name.map(|s| s.into());
} else {
debug!("Override plug name set to None; will use original name \"{}\" given in ::create_input constructor", opt.plug_name);
}
}
PlugOptionsBuilder::OutputPlugOptions(_) => {
error!(
"Output Plugs cannot change their name part after ::create_output constructor"
);
}
};
self
}
pub fn any_plug(mut self) -> Self {
match &mut self {
PlugOptionsBuilder::InputPlugOptions(opt) => {
opt.override_subscribe_plug_name = Some("+".into());
}
PlugOptionsBuilder::OutputPlugOptions(_) => {
error!(
"Output Plugs cannot change their name part after ::create_output constructor"
);
}
}
self
}
pub fn topic(mut self, override_topic: Option<&str>) -> Self {
match override_topic {
Some(t) => {
if TryInto::<ThreePartTopic>::try_into(t).is_ok() {
info!("Custom topic passes Three Part Topic validation");
} else {
if t == "#" {
info!(
"Wildcard \"#\" custom topics are not Three Part Topics but are valid"
);
} else {
warn!(
"Could not convert \"{}\" into Tether 3 Part Topic; presumably you know what you're doing!",
t
);
}
}
match &mut self {
PlugOptionsBuilder::InputPlugOptions(s) => s.override_topic = Some(t.into()),
PlugOptionsBuilder::OutputPlugOptions(s) => s.override_topic = Some(t.into()),
};
}
None => {
match &mut self {
PlugOptionsBuilder::InputPlugOptions(s) => s.override_topic = None,
PlugOptionsBuilder::OutputPlugOptions(s) => s.override_topic = None,
};
}
}
self
}
pub fn retain(mut self, should_retain: Option<bool>) -> Self {
match &mut self {
Self::InputPlugOptions(_) => {
error!("Cannot set retain flag on Input Plug / subscription");
}
Self::OutputPlugOptions(s) => {
s.retain = should_retain;
}
}
self
}
pub fn build(self, tether_agent: &TetherAgent) -> anyhow::Result<PlugDefinition> {
match self {
Self::InputPlugOptions(plug_options) => {
let tpt: TetherOrCustomTopic = match plug_options.override_topic {
Some(custom) => TetherOrCustomTopic::Custom(custom),
None => {
debug!("Not a custom topic; provided overrides: role = {:?}, id = {:?}, name = {:?}", plug_options.override_subscribe_role, plug_options.override_subscribe_id, plug_options.override_subscribe_plug_name);
TetherOrCustomTopic::Tether(ThreePartTopic::new_for_subscribe(
&plug_options.plug_name,
plug_options.override_subscribe_role.as_deref(),
plug_options.override_subscribe_id.as_deref(),
plug_options.override_subscribe_plug_name.as_deref(),
))
}
};
let plug_definition =
InputPlugDefinition::new(&plug_options.plug_name, tpt, plug_options.qos);
match tether_agent
.client()
.subscribe(&plug_definition.topic_str(), plug_definition.qos())
{
Ok(res) => {
debug!("This topic was fine: \"{}\"", plug_definition.topic_str());
debug!("Server respond OK for subscribe: {res:?}");
Ok(PlugDefinition::InputPlug(plug_definition))
}
Err(e) => Err(e.into()),
}
}
Self::OutputPlugOptions(plug_options) => {
let tpt: TetherOrCustomTopic = match plug_options.override_topic {
Some(custom) => TetherOrCustomTopic::Custom(custom),
None => TetherOrCustomTopic::Tether(ThreePartTopic::new_for_publish(
plug_options.override_publish_role.as_deref(),
plug_options.override_publish_id.as_deref(),
&plug_options.plug_name,
tether_agent,
)),
};
let plug_definition = OutputPlugDefinition::new(
&plug_options.plug_name,
tpt,
plug_options.qos,
plug_options.retain,
);
Ok(PlugDefinition::OutputPlug(plug_definition))
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{PlugOptionsBuilder, TetherAgentOptionsBuilder};
#[test]
fn default_input_plug() {
let tether_agent = TetherAgentOptionsBuilder::new("tester")
.build()
.expect("sorry, these tests require working localhost Broker");
let input = PlugOptionsBuilder::create_input("one")
.build(&tether_agent)
.unwrap();
assert_eq!(input.name(), "one");
assert_eq!(input.topic(), "+/+/one");
}
#[test]
fn default_input_plug_with_agent_custom_id() {
let tether_agent = TetherAgentOptionsBuilder::new("tester")
.id(Some("verySpecialGroup"))
.build()
.expect("sorry, these tests require working localhost Broker");
let input = PlugOptionsBuilder::create_input("one")
.build(&tether_agent)
.unwrap();
assert_eq!(input.name(), "one");
assert_eq!(input.topic(), "+/+/one");
}
#[test]
fn default_output_plug() {
let tether_agent = TetherAgentOptionsBuilder::new("tester")
.build()
.expect("sorry, these tests require working localhost Broker");
let input = PlugOptionsBuilder::create_output("two")
.build(&tether_agent)
.unwrap();
assert_eq!(input.name(), "two");
assert_eq!(input.topic(), "tester/any/two");
}
#[test]
fn output_plug_default_but_agent_id_custom() {
let tether_agent = TetherAgentOptionsBuilder::new("tester")
.id(Some("specialCustomGrouping"))
.build()
.expect("sorry, these tests require working localhost Broker");
let input = PlugOptionsBuilder::create_output("somethingStandard")
.build(&tether_agent)
.unwrap();
assert_eq!(input.name(), "somethingStandard");
assert_eq!(
input.topic(),
"tester/specialCustomGrouping/somethingStandard"
);
}
#[test]
fn input_id_andor_role() {
let tether_agent = TetherAgentOptionsBuilder::new("tester")
.build()
.expect("sorry, these tests require working localhost Broker");
let input_role_only = PlugOptionsBuilder::create_input("thePlug")
.role(Some("specificRole".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_role_only.name(), "thePlug");
assert_eq!(input_role_only.topic(), "specificRole/+/thePlug");
let input_id_only = PlugOptionsBuilder::create_input("thePlug")
.id(Some("specificID".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_id_only.name(), "thePlug");
assert_eq!(input_id_only.topic(), "+/specificID/thePlug");
let input_both = PlugOptionsBuilder::create_input("thePlug")
.id(Some("specificID".into()))
.role(Some("specificRole".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_both.name(), "thePlug");
assert_eq!(input_both.topic(), "specificRole/specificID/thePlug");
}
#[test]
fn input_specific_id_andor_role_with_plugname() {
let tether_agent = TetherAgentOptionsBuilder::new("tester")
.build()
.expect("sorry, these tests require working localhost Broker");
let input_role_only = PlugOptionsBuilder::create_input("thePlug")
.role(Some("specificRole".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_role_only.name(), "thePlug");
assert_eq!(input_role_only.topic(), "specificRole/+/thePlug");
let input_id_only = PlugOptionsBuilder::create_input("thePlug")
.id(Some("specificID".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_id_only.name(), "thePlug");
assert_eq!(input_id_only.topic(), "+/specificID/thePlug");
let input_both = PlugOptionsBuilder::create_input("thePlug")
.id(Some("specificID".into()))
.role(Some("specificRole".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_both.name(), "thePlug");
assert_eq!(input_both.topic(), "specificRole/specificID/thePlug");
}
#[test]
fn input_specific_id_andor_role_no_plugname() {
let tether_agent = TetherAgentOptionsBuilder::new("tester")
.build()
.expect("sorry, these tests require working localhost Broker");
let input_only_plugname_none = PlugOptionsBuilder::create_input("thePlug")
.name(Some("+"))
.build(&tether_agent)
.unwrap();
assert_eq!(input_only_plugname_none.name(), "thePlug");
assert_eq!(input_only_plugname_none.topic(), "+/+/+");
let input_role_only = PlugOptionsBuilder::create_input("thePlug")
.name(Some("+"))
.role(Some("specificRole".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_role_only.name(), "thePlug");
assert_eq!(input_role_only.topic(), "specificRole/+/+");
let input_id_only = PlugOptionsBuilder::create_input("thePlug")
.any_plug() .id(Some("specificID".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_id_only.name(), "thePlug");
assert_eq!(input_id_only.topic(), "+/specificID/+");
let input_both = PlugOptionsBuilder::create_input("thePlug")
.name(Some("+"))
.id(Some("specificID".into()))
.role(Some("specificRole".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_both.name(), "thePlug");
assert_eq!(input_both.topic(), "specificRole/specificID/+");
}
#[test]
fn output_custom() {
let tether_agent = TetherAgentOptionsBuilder::new("tester")
.build()
.expect("sorry, these tests require working localhost Broker");
let output_custom_role = PlugOptionsBuilder::create_output("theOutputPlug")
.role(Some("customRole".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(output_custom_role.name(), "theOutputPlug");
assert_eq!(output_custom_role.topic(), "customRole/any/theOutputPlug");
let output_custom_id = PlugOptionsBuilder::create_output("theOutputPlug")
.id(Some("customID".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(output_custom_id.name(), "theOutputPlug");
assert_eq!(output_custom_id.topic(), "tester/customID/theOutputPlug");
let output_custom_both = PlugOptionsBuilder::create_output("theOutputPlug")
.role(Some("customRole".into()))
.id(Some("customID".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(output_custom_both.name(), "theOutputPlug");
assert_eq!(
output_custom_both.topic(),
"customRole/customID/theOutputPlug"
);
}
#[test]
fn input_manual_topics() {
let tether_agent = TetherAgentOptionsBuilder::new("tester")
.build()
.expect("sorry, these tests require working localhost Broker");
let input_all = PlugOptionsBuilder::create_input("everything")
.topic(Some("#".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_all.name(), "everything");
assert_eq!(input_all.topic(), "#");
let input_nontether = PlugOptionsBuilder::create_input("weird")
.topic(Some("foo/bar/baz/one/two/three".into()))
.build(&tether_agent)
.unwrap();
assert_eq!(input_nontether.name(), "weird");
assert_eq!(input_nontether.topic(), "foo/bar/baz/one/two/three");
}
}