1use crate::common::*;
2use arrayvec::{ArrayString, ArrayVec};
3
4use self::Topic::*;
5
6const API_BRIDGE: &str = "/jobs/";
7const API_JOBSCHANGED: &str = "notify";
8const API_NEXTJOBCHANGED: &str = "notify-next";
9const API_GETPENDING: &str = "get";
10const API_STARTNEXT: &str = "start-next";
11const API_DESCRIBE: &str = "get";
12const API_UPDATE: &str = "update";
13pub struct ThingJobs<'a> {
18 pub thing_name: &'a str,
19 pub api: Topic,
20 pub id: Option<ArrayString<JOBID_MAX_LENGTH>>,
21}
22
23#[derive(Debug, PartialEq, PartialOrd)]
27pub enum Topic {
28 JobsChanged,
29 NextJobChanged,
30 GetPendingSuccess,
31 GetPendingFailed,
32 StartNextSuccess,
33 StartNextFailed,
34 DescribeSuccess,
36 DescribeFailed,
37 UpdateSuccess,
38 UpdateFailed,
39}
40
41pub fn get_topic(
53 thing_name: &str,
54 api: Topic,
55) -> Result<ArrayString<JOBS_TOPIC_MAX_LENGTH>, Error> {
56 is_valid_thing_name(thing_name)?;
57 let mut s = ArrayString::<JOBS_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(id(&api));
62 s.push_str(op(&api));
63 s.push_str(suffix(&api));
64
65 Ok(s)
66}
67
68fn id(api: &Topic) -> &str {
69 match api {
70 DescribeSuccess | DescribeFailed | UpdateSuccess | UpdateFailed => "+/",
71 _ => "",
72 }
73}
74
75fn op(api: &Topic) -> &str {
76 match api {
77 JobsChanged => API_JOBSCHANGED,
78 NextJobChanged => API_NEXTJOBCHANGED,
79 GetPendingSuccess => API_GETPENDING,
80 GetPendingFailed => API_GETPENDING,
81 StartNextSuccess => API_STARTNEXT,
82 StartNextFailed => API_STARTNEXT,
83 DescribeSuccess => API_DESCRIBE,
85 DescribeFailed => API_DESCRIBE,
86 UpdateSuccess => API_UPDATE,
87 UpdateFailed => API_UPDATE,
88 }
89}
90
91fn suffix(topic_type: &Topic) -> &str {
92 match topic_type {
93 GetPendingSuccess | StartNextSuccess | DescribeSuccess | UpdateSuccess => SUFFIX_ACCEPTED,
94 GetPendingFailed | StartNextFailed | DescribeFailed | UpdateFailed => SUFFIX_REJECTED,
95 _ => "",
96 }
97}
98pub fn match_topic(topic: &str) -> Result<ThingJobs, Error> {
113 is_valid_mqtt_topic(topic)?;
114
115 let s = is_valid_prefix(topic, AWS_THINGS_PREFIX)?;
116
117 let mid = s.find('/').ok_or(Error::FAIL);
118 let (thing_name, mut s) = s.split_at(mid?);
119 is_valid_thing_name(thing_name)?;
120
121 s = is_valid_bridge(s, API_BRIDGE)?;
122
123 let v: ArrayVec<&str, 16> = s.split('/').collect();
124 let api: Topic;
125 let jobs_id;
126 match v[..] {
127 [op] => {
130 if op == API_JOBSCHANGED {
131 api = JobsChanged;
132 } else {
133 api = NextJobChanged;
134 }
135 Ok(ThingJobs {
136 thing_name,
137 api,
138 id: None,
139 })
140 }
141 [op, suffix] => {
143 match (op, suffix) {
144 (API_GETPENDING, ACCEPTED) => api = GetPendingSuccess,
145 (API_GETPENDING, REJECTED) => api = GetPendingFailed,
146 (API_STARTNEXT, ACCEPTED) => api = StartNextSuccess,
147 (API_STARTNEXT, REJECTED) => api = StartNextFailed,
148 _ => return Err(Error::NoMatch),
149 }
150 Ok(ThingJobs {
151 thing_name,
152 api,
153 id: None,
154 })
155 }
156 [id, op, suffix] => {
158 match (op, suffix) {
159 (API_DESCRIBE, ACCEPTED) => api = DescribeSuccess,
160 (API_DESCRIBE, REJECTED) => api = DescribeFailed,
161 (API_UPDATE, ACCEPTED) => api = UpdateSuccess,
162 (API_UPDATE, REJECTED) => api = UpdateFailed,
163 _ => return Err(Error::NoMatch),
164 }
165 jobs_id = Some(ArrayString::<JOBID_MAX_LENGTH>::from(id).unwrap());
166 Ok(ThingJobs {
167 thing_name,
168 api,
169 id: jobs_id,
170 })
171 }
172 _ => Err(Error::NoMatch),
174 }
175}
176pub fn get_pending(thing_name: &str) -> Result<ArrayString<THINGNAME_MAX_LENGTH>, Error> {
179 is_valid_thing_name(thing_name)?;
180 let mut s = ArrayString::<THINGNAME_MAX_LENGTH>::new();
181 s.push_str(AWS_THINGS_PREFIX);
182 s.push_str(thing_name);
183 s.push_str(API_BRIDGE);
184 s.push_str(API_GETPENDING);
185
186 Ok(s)
187}
188pub fn start_next(thing_name: &str) -> Result<ArrayString<THINGNAME_MAX_LENGTH>, Error> {
191 is_valid_thing_name(thing_name)?;
192 let mut s = ArrayString::<THINGNAME_MAX_LENGTH>::new();
193 s.push_str(AWS_THINGS_PREFIX);
194 s.push_str(thing_name);
195 s.push_str(API_BRIDGE);
196 s.push_str(API_STARTNEXT);
197
198 Ok(s)
199}
200pub fn describe(thing_name: &str, id: &str) -> Result<ArrayString<THINGNAME_MAX_LENGTH>, Error> {
203 is_valid_thing_name(thing_name)?;
204 is_valid_job_id(id)?;
205 let mut s = ArrayString::<THINGNAME_MAX_LENGTH>::new();
206 s.push_str(AWS_THINGS_PREFIX);
207 s.push_str(thing_name);
208 s.push_str(API_BRIDGE);
209 s.push_str(id);
210 s.push_str("/");
211 s.push_str(API_DESCRIBE);
212
213 Ok(s)
214}
215pub fn update(thing_name: &str, id: &str) -> Result<ArrayString<THINGNAME_MAX_LENGTH>, Error> {
218 is_valid_thing_name(thing_name)?;
219 is_valid_job_id(id)?;
220 let mut s = ArrayString::<THINGNAME_MAX_LENGTH>::new();
221 s.push_str(AWS_THINGS_PREFIX);
222 s.push_str(thing_name);
223 s.push_str(API_BRIDGE);
224 s.push_str(id);
225 s.push_str("/");
226 s.push_str(API_UPDATE);
227
228 Ok(s)
229}
230
231#[cfg(test)]
232mod tests {
233 use crate::jobs;
234 #[test]
235 fn get_topic_notify_next() {
236 let topic = jobs::get_topic("chloe", jobs::Topic::NextJobChanged).unwrap();
237 assert_eq!(&topic[..], "$aws/things/chloe/jobs/notify-next");
238 }
239
240 #[test]
241 fn get_topic_get_rejected() {
242 let topic = jobs::get_topic("chloe", jobs::Topic::GetPendingFailed).unwrap();
243 assert_eq!(&topic[..], "$aws/things/chloe/jobs/get/rejected");
244 }
245
246 #[test]
247 fn get_topic_id_update_rejected() {
248 let topic = jobs::get_topic("chloe", jobs::Topic::UpdateFailed).unwrap();
249 assert_eq!(&topic[..], "$aws/things/chloe/jobs/+/update/rejected");
250 }
251
252 #[test]
253 fn match_topic() {
254 let jobs = jobs::match_topic("$aws/things/chloe/jobs/notify-next").unwrap();
255 assert_eq!(jobs.api, jobs::Topic::NextJobChanged);
256 assert_eq!(jobs.id, None);
257 }
258 #[test]
259 fn match_topic_with_op() {
260 let jobs = jobs::match_topic("$aws/things/chloe/jobs/get/rejected").unwrap();
261 assert_eq!(jobs.api, jobs::Topic::GetPendingFailed);
262 assert_eq!(jobs.id, None);
263 }
264 #[test]
265 fn match_topic_with_id_op() {
266 let jobs = jobs::match_topic("$aws/things/chloe/jobs/example-job-01/get/accepted").unwrap();
267 assert_eq!(jobs.api, jobs::Topic::DescribeSuccess);
268 let id = jobs.id.unwrap();
269 assert_eq!(&id[..], "example-job-01");
270 }
271 #[test]
272 fn get_pending() {
273 let topic = jobs::get_pending("chloe").unwrap();
274 assert_eq!(&topic[..], "$aws/things/chloe/jobs/get");
275 }
276 #[test]
277 fn start_next() {
278 let topic = jobs::start_next("chloe").unwrap();
279 assert_eq!(&topic[..], "$aws/things/chloe/jobs/start-next");
280 }
281 #[test]
282 fn describe() {
283 let topic = jobs::describe("chloe", "example-job-01").unwrap();
284 assert_eq!(&topic[..], "$aws/things/chloe/jobs/example-job-01/get");
285 }
286 #[test]
287 fn update() {
288 let topic = jobs::update("chloe", "example-job-01").unwrap();
289 assert_eq!(&topic[..], "$aws/things/chloe/jobs/example-job-01/update");
290 }
291}