use crate::common::*;
use arrayvec::{ArrayString, ArrayVec};
use self::Topic::*;
const API_JOBSCHANGED: &str = "notify";
const API_NEXTJOBCHANGED: &str = "notify-next";
const API_GETPENDING: &str = "get";
const API_STARTNEXT: &str = "start-next";
const API_DESCRIBE: &str = "get";
const API_UPDATE: &str = "update";
const API_JOBID_NEXT: &str = "$next";
pub struct ThingJobs<'a> {
pub thing_name: &'a str,
pub api: Topic,
pub id: Option<ArrayString<JOBID_MAX_LENGTH>>,
}
#[derive(Debug, PartialEq, PartialOrd)]
pub enum Topic {
JobsChanged,
NextJobChanged,
GetPendingSuccess,
GetPendingFailed,
StartNextSuccess,
StartNextFailed,
DescribeSuccess,
DescribeFailed,
UpdateSuccess,
UpdateFailed,
}
pub fn assemble_topic(
thing_name: &str,
api: Topic,
) -> Result<ArrayString<JOBS_TOPIC_MAX_LENGTH>, Error> {
is_valid_thing_name(thing_name)?;
let mut s = ArrayString::<JOBS_TOPIC_MAX_LENGTH>::new();
s.push_str(AWS_THINGS_PREFIX);
s.push_str(thing_name);
s.push_str(JOBS_API_BRIDGE);
s.push_str(id(&api));
s.push_str(op(&api));
s.push_str(suffix(&api));
Ok(s)
}
fn id(api: &Topic) -> &str {
match api {
DescribeSuccess | DescribeFailed | UpdateSuccess | UpdateFailed => "+/",
_ => "",
}
}
fn op(api: &Topic) -> &str {
match api {
JobsChanged => API_JOBSCHANGED,
NextJobChanged => API_NEXTJOBCHANGED,
GetPendingSuccess => API_GETPENDING,
GetPendingFailed => API_GETPENDING,
StartNextSuccess => API_STARTNEXT,
StartNextFailed => API_STARTNEXT,
DescribeSuccess => API_DESCRIBE,
DescribeFailed => API_DESCRIBE,
UpdateSuccess => API_UPDATE,
UpdateFailed => API_UPDATE,
}
}
fn suffix(topic_type: &Topic) -> &str {
match topic_type {
GetPendingSuccess | StartNextSuccess | DescribeSuccess | UpdateSuccess => SUFFIX_ACCEPTED,
GetPendingFailed | StartNextFailed | DescribeFailed | UpdateFailed => SUFFIX_REJECTED,
_ => "",
}
}
pub fn match_topic(topic: &str) -> Result<ThingJobs, Error> {
is_valid_mqtt_topic(topic)?;
let s = is_valid_prefix(topic, AWS_THINGS_PREFIX)?;
let mid = s.find('/').ok_or(Error::FAIL);
let (thing_name, mut s) = s.split_at(mid?);
is_valid_thing_name(thing_name)?;
s = is_valid_bridge(s, JOBS_API_BRIDGE)?;
let v: ArrayVec<&str, 16> = s.split('/').collect();
let api: Topic;
let jobs_id;
match v[..] {
[op] => {
if op == API_JOBSCHANGED {
api = JobsChanged;
} else {
api = NextJobChanged;
}
Ok(ThingJobs {
thing_name,
api,
id: None,
})
}
[op, suffix] => {
match (op, suffix) {
(API_GETPENDING, ACCEPTED) => api = GetPendingSuccess,
(API_GETPENDING, REJECTED) => api = GetPendingFailed,
(API_STARTNEXT, ACCEPTED) => api = StartNextSuccess,
(API_STARTNEXT, REJECTED) => api = StartNextFailed,
_ => return Err(Error::NoMatch),
}
Ok(ThingJobs {
thing_name,
api,
id: None,
})
}
[id, op, suffix] => {
match (op, suffix) {
(API_DESCRIBE, ACCEPTED) => api = DescribeSuccess,
(API_DESCRIBE, REJECTED) => api = DescribeFailed,
(API_UPDATE, ACCEPTED) => api = UpdateSuccess,
(API_UPDATE, REJECTED) => api = UpdateFailed,
_ => return Err(Error::NoMatch),
}
jobs_id = Some(ArrayString::<JOBID_MAX_LENGTH>::from(id).unwrap());
Ok(ThingJobs {
thing_name,
api,
id: jobs_id,
})
}
_ => Err(Error::NoMatch),
}
}
pub fn get_pending(thing_name: &str) -> Result<ArrayString<THINGNAME_MAX_LENGTH>, Error> {
is_valid_thing_name(thing_name)?;
let mut s = ArrayString::<THINGNAME_MAX_LENGTH>::new();
s.push_str(AWS_THINGS_PREFIX);
s.push_str(thing_name);
s.push_str(JOBS_API_BRIDGE);
s.push_str(API_GETPENDING);
Ok(s)
}
pub fn start_next(thing_name: &str) -> Result<ArrayString<THINGNAME_MAX_LENGTH>, Error> {
is_valid_thing_name(thing_name)?;
let mut s = ArrayString::<THINGNAME_MAX_LENGTH>::new();
s.push_str(AWS_THINGS_PREFIX);
s.push_str(thing_name);
s.push_str(JOBS_API_BRIDGE);
s.push_str(API_STARTNEXT);
Ok(s)
}
pub fn describe(thing_name: &str, id: &str) -> Result<ArrayString<THINGNAME_MAX_LENGTH>, Error> {
is_valid_thing_name(thing_name)?;
if id != API_JOBID_NEXT {
is_valid_job_id(id)?
};
let mut s = ArrayString::<THINGNAME_MAX_LENGTH>::new();
s.push_str(AWS_THINGS_PREFIX);
s.push_str(thing_name);
s.push_str(JOBS_API_BRIDGE);
s.push_str(id);
s.push_str("/");
s.push_str(API_DESCRIBE);
Ok(s)
}
pub fn update(thing_name: &str, id: &str) -> Result<ArrayString<THINGNAME_MAX_LENGTH>, Error> {
is_valid_thing_name(thing_name)?;
is_valid_job_id(id)?;
let mut s = ArrayString::<THINGNAME_MAX_LENGTH>::new();
s.push_str(AWS_THINGS_PREFIX);
s.push_str(thing_name);
s.push_str(JOBS_API_BRIDGE);
s.push_str(id);
s.push_str("/");
s.push_str(API_UPDATE);
Ok(s)
}
#[cfg(test)]
mod tests {
use crate::jobs;
#[test]
fn assemble_topic_notify_next() {
let topic = jobs::assemble_topic("chloe", jobs::Topic::NextJobChanged).unwrap();
assert_eq!(&topic[..], "$aws/things/chloe/jobs/notify-next");
}
#[test]
fn assemble_topic_get_rejected() {
let topic = jobs::assemble_topic("chloe", jobs::Topic::GetPendingFailed).unwrap();
assert_eq!(&topic[..], "$aws/things/chloe/jobs/get/rejected");
}
#[test]
fn assemble_topic_id_update_rejected() {
let topic = jobs::assemble_topic("chloe", jobs::Topic::UpdateFailed).unwrap();
assert_eq!(&topic[..], "$aws/things/chloe/jobs/+/update/rejected");
}
#[test]
fn match_topic() {
let jobs = jobs::match_topic("$aws/things/chloe/jobs/notify-next").unwrap();
assert_eq!(jobs.api, jobs::Topic::NextJobChanged);
assert_eq!(jobs.id, None);
}
#[test]
fn match_topic_with_op() {
let jobs = jobs::match_topic("$aws/things/chloe/jobs/get/rejected").unwrap();
assert_eq!(jobs.api, jobs::Topic::GetPendingFailed);
assert_eq!(jobs.id, None);
}
#[test]
fn match_topic_with_id_op() {
let jobs = jobs::match_topic("$aws/things/chloe/jobs/example-job-01/get/accepted").unwrap();
assert_eq!(jobs.api, jobs::Topic::DescribeSuccess);
let id = jobs.id.unwrap();
assert_eq!(&id[..], "example-job-01");
}
#[test]
fn get_pending() {
let topic = jobs::get_pending("chloe").unwrap();
assert_eq!(&topic[..], "$aws/things/chloe/jobs/get");
}
#[test]
fn start_next() {
let topic = jobs::start_next("chloe").unwrap();
assert_eq!(&topic[..], "$aws/things/chloe/jobs/start-next");
}
#[test]
fn update() {
let topic = jobs::update("chloe", "example-job-01").unwrap();
assert_eq!(&topic[..], "$aws/things/chloe/jobs/example-job-01/update");
}
}