aws_iot_device_sdk_embedded_rust/
defender.rs

1use arrayvec::{ArrayString, ArrayVec};
2
3use self::Topic::*;
4use crate::common::*;
5
6const API_BRIDGE: &str = "/defender/metrics/";
7const API_JSON_FORMAT: &str = "json";
8const API_CBOR_FORMAT: &str = "cbor";
9
10/// The struct outputs which API the topic is for. It also outputs
11/// the thing name in the given topic.
12pub struct ThingDefender<'a> {
13    pub thing_name: &'a str,
14    pub api: Topic,
15}
16
17///
18/// A Defender topic has the following format:"<Prefix><Thing Name><Bridge><Report Format><Suffix>"
19///
20/// Where:
21///     - Prefix = $aws/things/
22///     - Thing Name = Name of the thing.
23///     - Bridge = /defender/metrics/
24///     - Report Format = json or cbor
25///     - Suffix = /accepted or /rejected or empty
26///
27#[derive(Debug, PartialEq)]
28pub enum Topic {
29    JsonReportPublish,
30    /* Topic for publishing a JSON report. */
31    JsonReportAccepted,
32    /* Topic for getting a JSON report accepted response. */
33    JsonReportRejected,
34    /* Topic for getting a JSON report rejected response. */
35    CborReportPublish,
36    /* Topic for publishing a CBOR report. */
37    CborReportAccepted,
38    /* Topic for getting a CBOR report accepted response. */
39    CborReportRejected, /* Topic for getting a CBOR report rejected response. */
40}
41
42/// Populate the topic string for a Device Defender operation.
43///
44/// # Example
45/// ```
46/// use aws_iot_device_sdk_embedded_rust::defender::Topic::*;
47/// use aws_iot_device_sdk_embedded_rust::{defender};
48///
49/// let topic = defender::get_topic("chloe", defender::Topic::JsonReportPublish).unwrap();
50/// assert_eq!(&topic[..], "$aws/things/chloe/defender/metrics/json")
51/// ```
52pub fn get_topic(
53    thing_name: &str,
54    api: Topic,
55) -> Result<ArrayString<DEFENDER_TOPIC_MAX_LENGTH>, Error> {
56    is_valid_thing_name(thing_name)?;
57    let mut s = ArrayString::<DEFENDER_TOPIC_MAX_LENGTH>::new();
58    s.push_str(AWS_THINGS_PREFIX);
59    s.push_str(thing_name);
60    s.push_str(API_BRIDGE);
61    s.push_str(op(&api));
62    s.push_str(suffix(&api));
63
64    Ok(s)
65}
66
67fn op(api: &Topic) -> &str {
68    match api {
69        JsonReportPublish | JsonReportAccepted | JsonReportRejected => API_JSON_FORMAT,
70        CborReportPublish | CborReportAccepted | CborReportRejected => API_CBOR_FORMAT,
71    }
72}
73
74fn suffix(topic_type: &Topic) -> &str {
75    match topic_type {
76        JsonReportAccepted | CborReportAccepted => SUFFIX_ACCEPTED,
77        JsonReportRejected | CborReportRejected => SUFFIX_REJECTED,
78        _ => "",
79    }
80}
81
82/// Check if the given topic is one of the Device Defender topics.
83///
84/// # Example
85/// ```
86/// use aws_iot_device_sdk_embedded_rust::defender::Topic::*;
87/// use aws_iot_device_sdk_embedded_rust::{defender};
88///
89/// let defender =
90///     defender::match_topic("$aws/things/chloe/defender/metrics/json/accepted").unwrap();
91///
92/// assert_eq!(defender.thing_name, "chloe");
93/// assert_eq!(defender.api, defender::Topic::JsonReportAccepted)
94/// ```
95pub fn match_topic(topic: &str) -> Result<ThingDefender, Error> {
96    is_valid_mqtt_topic(topic)?;
97
98    let s = is_valid_prefix(topic, AWS_THINGS_PREFIX)?;
99
100    let mid = s.find('/').ok_or(Error::FAIL);
101    let (thing_name, mut s) = s.split_at(mid?);
102    is_valid_thing_name(thing_name)?;
103
104    s = is_valid_bridge(s, API_BRIDGE)?;
105
106    let v: ArrayVec<&str, 16> = s.split('/').collect();
107    let api: Topic;
108    match v[..] {
109        // ~$aws/things/<thingName>/defender/metrics/~<format>/suffix
110        [op, suffix] => {
111            match (op, suffix) {
112                (API_JSON_FORMAT, ACCEPTED) => api = JsonReportAccepted,
113                (API_JSON_FORMAT, REJECTED) => api = JsonReportRejected,
114                (API_CBOR_FORMAT, ACCEPTED) => api = CborReportAccepted,
115                (API_CBOR_FORMAT, REJECTED) => api = CborReportRejected,
116                _ => return Err(Error::NoMatch),
117            }
118            Ok(ThingDefender { thing_name, api })
119        }
120        // Not defender topic
121        _ => Err(Error::NoMatch),
122    }
123}
124#[cfg(test)]
125mod tests {
126    use crate::defender;
127    #[test]
128    fn get_topic_json() {
129        let topic = defender::get_topic("chloe", defender::Topic::JsonReportPublish).unwrap();
130        assert_eq!(&topic[..], "$aws/things/chloe/defender/metrics/json");
131    }
132
133    #[test]
134    fn get_topic_cbor_rejected() {
135        let topic = defender::get_topic("chloe", defender::Topic::CborReportRejected).unwrap();
136        assert_eq!(
137            &topic[..],
138            "$aws/things/chloe/defender/metrics/cbor/rejected"
139        );
140    }
141    #[test]
142    fn test_match_topic_some_name() {
143        let defender =
144            defender::match_topic("$aws/things/chloe/defender/metrics/json/accepted").unwrap();
145
146        assert_eq!(defender.thing_name, "chloe");
147        assert_eq!(defender.api, defender::Topic::JsonReportAccepted);
148    }
149}