aws_iot_device_sdk_embedded_rust/
shadow.rs1use crate::common::*;
2use arrayvec::{ArrayString, ArrayVec};
3
4use self::Topic::*;
5
6const API_BRIDGE: &str = "/shadow/";
7const API_BRIDGE_NAME: &str = "/shadow/name/";
8const OP_GET: &str = "get";
9const OP_DELETE: &str = "delete";
10const OP_UPDATE: &str = "update";
11const SUFFIX_DOCUMENTS: &str = "/documents";
12const SUFFIX_DELTA: &str = "/delta";
13#[derive(Debug)]
18pub struct ThingShadow<'a> {
19 pub thing_name: &'a str,
20 pub shadow_name: Option<&'a str>,
21 pub shadow_op: Topic,
22}
23
24#[derive(Debug, PartialEq)]
27pub enum Topic {
28 Get = 0,
29 GetAccepted,
30 GetRejected,
31 Delete,
32 DeleteAccepted,
33 DeleteRejected,
34 Update,
35 UpdateAccepted,
36 UpdateRejected,
37 UpdateDocuments,
38 UpdateDelta,
39}
40
41pub fn get_topic(
54 topic_type: Topic,
55 thing_name: &str,
56 named: Option<&str>,
57) -> Result<ArrayString<SHADOW_TOPIC_MAX_LENGTH>, Error> {
58 is_valid_thing_name(thing_name)?;
59 let mut s = ArrayString::<SHADOW_TOPIC_MAX_LENGTH>::new();
60 s.push_str(AWS_THINGS_PREFIX);
61 s.push_str(thing_name);
62 match named {
63 None => {
65 s.push_str(API_BRIDGE);
66 s.push_str(op(&topic_type));
67 s.push_str(suffix(&topic_type));
68 Ok(s)
69 }
70 Some(shadow_name) => {
72 is_valid_shadow_name(shadow_name)?;
73 s.push_str(API_BRIDGE_NAME);
74 s.push_str(shadow_name);
75 s.push_str("/");
76 s.push_str(op(&topic_type));
77 s.push_str(suffix(&topic_type));
78 Ok(s)
79 }
80 }
81}
82
83fn op(topic_type: &Topic) -> &str {
84 match topic_type {
85 Get | GetAccepted | GetRejected => OP_GET,
86 Delete | DeleteAccepted | DeleteRejected => OP_DELETE,
87 Update | UpdateAccepted | UpdateRejected | UpdateDocuments | UpdateDelta => OP_UPDATE,
88 }
89}
90fn suffix(topic_type: &Topic) -> &str {
91 match topic_type {
92 GetAccepted | DeleteAccepted | UpdateAccepted => SUFFIX_ACCEPTED,
93 GetRejected | DeleteRejected | UpdateRejected => SUFFIX_REJECTED,
94 UpdateDocuments => SUFFIX_DOCUMENTS,
95 UpdateDelta => SUFFIX_DELTA,
96 _ => "",
97 }
98}
99
100pub fn match_topic<'a>(topic: &'a str) -> Result<ThingShadow, Error> {
119 is_valid_mqtt_topic(topic)?;
120
121 let s = is_valid_prefix(topic, AWS_THINGS_PREFIX)?;
122
123 let mid = s.find('/').ok_or(Error::NoMatch);
124 let (thing_name, s) = s.split_at(mid?);
125 is_valid_thing_name(thing_name)?;
126
127 let s = is_valid_bridge(s, API_BRIDGE)?;
128
129 let v: ArrayVec<&str, 16> = s.split('/').collect();
130 match v[..] {
131 [_named, shadow_name, op, suffix] => {
133 is_valid_shadow_name(shadow_name)?;
134 Ok(ThingShadow {
135 thing_name,
136 shadow_name: Some(shadow_name),
137 shadow_op: find_message_type(op, suffix)?,
138 })
139 }
140 [op, suffix] => Ok(ThingShadow {
142 thing_name,
143 shadow_name: None,
144 shadow_op: find_message_type(op, suffix)?,
145 }),
146 _ => Err(Error::NoMatch),
148 }
149}
150
151fn find_message_type(op: &str, suffix: &str) -> Result<Topic, Error> {
152 match (op, suffix) {
153 ("get", "accepted") => Ok(GetAccepted),
154 ("get", "rejected") => Ok(GetRejected),
155 ("delete", "accepted") => Ok(DeleteAccepted),
156 ("delete", "rejected") => Ok(DeleteRejected),
157 ("update", "accepted") => Ok(UpdateAccepted),
158 ("update", "rejected") => Ok(UpdateRejected),
159 ("update", "documents") => Ok(UpdateDocuments),
160 ("update", "delta") => Ok(UpdateDelta),
161 _ => Err(Error::MessageTypeParseFailed),
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use crate::shadow;
168 #[test]
169 fn assemble_named_topic_string() {
170 let topic = shadow::get_topic(shadow::Topic::Get, "chloe", Some("common")).unwrap();
171 assert_eq!("$aws/things/chloe/shadow/name/common/get", topic.as_str());
172 }
173 #[test]
174 fn assemble_classic_topic_string() {
175 let topic = shadow::get_topic(shadow::Topic::Get, "chloe", None).unwrap();
176 assert_eq!("$aws/things/chloe/shadow/get", topic.as_str());
177 }
178 #[test]
179 fn assemble_classic_topic_string_suffix() {
180 let topic = shadow::get_topic(shadow::Topic::GetAccepted, "chloe", None).unwrap();
181 assert_eq!("$aws/things/chloe/shadow/get/accepted", topic.as_str());
182 }
183 #[test]
184 fn match_classic_shadow_topic_string() {
185 let topic = "$aws/things/chloe/shadow/get/accepted";
186 let shadow = shadow::match_topic(topic).unwrap();
187 assert_eq!(shadow.thing_name, "chloe");
188 assert_eq!(shadow.shadow_name, None);
189 assert_eq!(shadow.shadow_op, shadow::Topic::GetAccepted);
190 }
191 #[test]
192 fn match_named_shadow_topic_string() {
193 let topic = "$aws/things/chloe/shadow/name/common/get/rejected";
194 let shadow = shadow::match_topic(topic).unwrap();
195 assert_eq!(shadow.thing_name, "chloe");
196 assert_eq!(shadow.shadow_name.unwrap(), "common");
197 assert_eq!(shadow.shadow_op, shadow::Topic::GetRejected);
198 }
199}