use crate::common::*;
use arrayvec::{ArrayString, ArrayVec};
use self::Topic::*;
const OP_GET: &str = "get";
const OP_DELETE: &str = "delete";
const OP_UPDATE: &str = "update";
const SUFFIX_DOCUMENTS: &str = "/documents";
const SUFFIX_DELTA: &str = "/delta";
#[derive(Debug)]
pub struct ThingShadow<'a> {
pub thing_name: &'a str,
pub shadow_name: Option<&'a str>,
pub shadow_op: Topic,
}
#[derive(Debug, PartialEq)]
pub enum Topic {
Get = 0,
GetAccepted,
GetRejected,
Delete,
DeleteAccepted,
DeleteRejected,
Update,
UpdateAccepted,
UpdateRejected,
UpdateDocuments,
UpdateDelta,
}
pub fn assemble_topic(
topic_type: Topic,
thing_name: &str,
named: Option<&str>,
) -> Result<ArrayString<SHADOW_TOPIC_MAX_LENGTH>, Error> {
is_valid_thing_name(thing_name)?;
let mut s = ArrayString::<SHADOW_TOPIC_MAX_LENGTH>::new();
s.push_str(AWS_THINGS_PREFIX);
s.push_str(thing_name);
match named {
None => {
s.push_str(SHADOW_API_BRIDGE);
s.push_str(op(&topic_type));
s.push_str(suffix(&topic_type));
Ok(s)
}
Some(shadow_name) => {
is_valid_shadow_name(shadow_name)?;
s.push_str(NAMED_SHADOW_API_BRIDGE);
s.push_str(shadow_name);
s.push_str("/");
s.push_str(op(&topic_type));
s.push_str(suffix(&topic_type));
Ok(s)
}
}
}
fn op(topic_type: &Topic) -> &str {
match topic_type {
Get | GetAccepted | GetRejected => OP_GET,
Delete | DeleteAccepted | DeleteRejected => OP_DELETE,
Update | UpdateAccepted | UpdateRejected | UpdateDocuments | UpdateDelta => OP_UPDATE,
}
}
fn suffix(topic_type: &Topic) -> &str {
match topic_type {
GetAccepted | DeleteAccepted | UpdateAccepted => SUFFIX_ACCEPTED,
GetRejected | DeleteRejected | UpdateRejected => SUFFIX_REJECTED,
UpdateDocuments => SUFFIX_DOCUMENTS,
UpdateDelta => SUFFIX_DELTA,
_ => "",
}
}
pub fn match_topic<'a>(topic: &'a str) -> Result<ThingShadow, Error> {
is_valid_mqtt_topic(topic)?;
let s = is_valid_prefix(topic, AWS_THINGS_PREFIX)?;
let mid = s.find('/').ok_or(Error::NoMatch);
let (thing_name, s) = s.split_at(mid?);
is_valid_thing_name(thing_name)?;
let s = is_valid_bridge(s, SHADOW_API_BRIDGE)?;
let v: ArrayVec<&str, 16> = s.split('/').collect();
match v[..] {
[_named, shadow_name, op, suffix] => {
is_valid_shadow_name(shadow_name)?;
Ok(ThingShadow {
thing_name,
shadow_name: Some(shadow_name),
shadow_op: find_message_type(op, Some(suffix))?,
})
}
[_named, shadow_name, op] => {
is_valid_shadow_name(shadow_name)?;
Ok(ThingShadow {
thing_name,
shadow_name: Some(shadow_name),
shadow_op: find_message_type(op, None)?,
})
}
[op, suffix] => Ok(ThingShadow {
thing_name,
shadow_name: None,
shadow_op: find_message_type(op, Some(suffix))?,
}),
[op] => Ok(ThingShadow {
thing_name,
shadow_name: None,
shadow_op: find_message_type(op, None)?,
}),
_ => Err(Error::NoMatch),
}
}
fn find_message_type(op: &str, suffix: Option<&str>) -> Result<Topic, Error> {
match (op, suffix) {
("get", None) => Ok(Get),
("get", Some("accepted")) => Ok(GetAccepted),
("get", Some("rejected")) => Ok(GetRejected),
("delete", None) => Ok(Delete),
("delete", Some("accepted")) => Ok(DeleteAccepted),
("delete", Some("rejected")) => Ok(DeleteRejected),
("update", None) => Ok(Update),
("update", Some("accepted")) => Ok(UpdateAccepted),
("update", Some("rejected")) => Ok(UpdateRejected),
("update", Some("documents")) => Ok(UpdateDocuments),
("update", Some("delta")) => Ok(UpdateDelta),
_ => Err(Error::MessageTypeParseFailed),
}
}
#[cfg(test)]
mod tests {
use crate::shadow;
#[test]
fn assemble_named_topic_string() {
let topic = shadow::assemble_topic(shadow::Topic::Get, "chloe", Some("common")).unwrap();
assert_eq!("$aws/things/chloe/shadow/name/common/get", topic.as_str());
}
#[test]
fn assemble_classic_topic_string() {
let topic = shadow::assemble_topic(shadow::Topic::Get, "chloe", None).unwrap();
assert_eq!("$aws/things/chloe/shadow/get", topic.as_str());
}
#[test]
fn assemble_classic_topic_string_suffix() {
let topic = shadow::assemble_topic(shadow::Topic::GetAccepted, "chloe", None).unwrap();
assert_eq!("$aws/things/chloe/shadow/get/accepted", topic.as_str());
}
#[test]
fn match_classic_shadow_topic_string() {
let topic = "$aws/things/chloe/shadow/get/accepted";
let shadow = shadow::match_topic(topic).unwrap();
assert_eq!(shadow.thing_name, "chloe");
assert_eq!(shadow.shadow_name, None);
assert_eq!(shadow.shadow_op, shadow::Topic::GetAccepted);
}
#[test]
fn match_named_shadow_topic_string() {
let topic = "$aws/things/chloe/shadow/name/common/get/rejected";
let shadow = shadow::match_topic(topic).unwrap();
assert_eq!(shadow.thing_name, "chloe");
assert_eq!(shadow.shadow_name.unwrap(), "common");
assert_eq!(shadow.shadow_op, shadow::Topic::GetRejected);
}
}