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