use std::fmt;
use regex::Regex;
use serde_json::Value;
use lazy_static::lazy_static;
use crate::errors::Error;
lazy_static! {
static ref MSG_RE: Regex = Regex::new(r#"(?x)
^
(?P<type>[*?\w]+) # message type (verb)
(?: \s
(?P<spec>[\w:<>]+) # spec (object)
(?: \s
(?P<json>.*) # data (json)
)?
)?
$
"#).expect("valid regex");
}
pub const IDENT_REPLY: &str = "SINE2020&ISSE,SECoP,V2019-09-16,v1.0";
#[derive(Debug, Clone)]
pub enum Msg {
Idn,
IdnReply { encoded: String },
Describe,
Describing { id: String, structure: Value },
Activate { module: String },
Active { module: String },
Deactivate { module: String },
Inactive { module: String },
Do { module: String, command: String, arg: Value },
Done { module: String, command: String, data: Value },
Change { module: String, param: String, value: Value },
Changed { module: String, param: String, data: Value },
Read { module: String, param: String },
Ping { token: String },
Pong { token: String, data: Value },
ErrMsg { class: String, report: Value },
Update { module: String, param: String, data: Value },
InitUpdates { module: String, updates: Vec<Msg> },
Quit,
}
#[derive(Clone)]
pub struct IncomingMsg(pub String, pub Msg);
use self::Msg::*;
mod wire {
pub const IDN: &str = "*IDN?";
pub const DESCRIBE: &str = "describe";
pub const DESCRIBING: &str = "describing";
pub const ACTIVATE: &str = "activate";
pub const ACTIVE: &str = "active";
pub const DEACTIVATE: &str = "deactivate";
pub const INACTIVE: &str = "inactive";
pub const PING: &str = "ping";
pub const PONG: &str = "pong";
pub const ERROR: &str = "error";
pub const DO: &str = "do";
pub const DONE: &str = "done";
pub const CHANGE: &str = "change";
pub const CHANGED: &str = "changed";
pub const READ: &str = "read";
pub const UPDATE: &str = "update";
}
impl Msg {
pub fn parse(msg: String) -> Result<IncomingMsg, Msg> {
match Self::parse_inner(&msg) {
Ok(v) => Ok(IncomingMsg(msg, v)),
Err(e) => Err(e.into_msg(msg)),
}
}
fn parse_inner(msg: &str) -> Result<Msg, Error> {
if let Some(captures) = MSG_RE.captures(msg) {
let action = captures.get(1).expect("is required").as_str();
let specifier = captures.get(2).map(|m| m.as_str()).unwrap_or("");
let mut spec_split = specifier.splitn(2, ':').map(Into::into);
let module = spec_split.next().expect("cannot be absent");
let mut param = || spec_split.next().ok_or(Error::protocol("missing parameter"));
let data = if let Some(jsonstr) = captures.get(3) {
serde_json::from_str(jsonstr.as_str())
.map_err(|_| Error::protocol("invalid JSON"))?
} else {
Value::Null
};
let parsed = match action {
wire::READ => Read { module, param: param()? },
wire::CHANGE => Change { module, param: param()?, value: data },
wire::DO => Do { module, command: param()?, arg: data },
wire::DESCRIBE => Describe,
wire::ACTIVATE => Activate { module },
wire::DEACTIVATE => Deactivate { module },
wire::PING => Ping { token: specifier.into() },
wire::IDN => Idn,
wire::UPDATE => Update { module, param: param()?, data },
wire::CHANGED => Changed { module, param: param()?, data },
wire::DONE => Done { module, command: param()?, data },
wire::DESCRIBING => Describing { id: specifier.into(), structure: data },
wire::ACTIVE => Active { module },
wire::INACTIVE => Inactive { module },
wire::PONG => Pong { token: specifier.into(), data },
wire::ERROR => ErrMsg { class: specifier.into(), report: data },
_ => return Err(Error::protocol("no such message type"))
};
Ok(parsed)
} else if msg == IDENT_REPLY {
Ok(IdnReply { encoded: IDENT_REPLY.into() })
} else {
Err(Error::protocol("invalid message format"))
}
}
}
impl fmt::Display for Msg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Update { module, param, data } =>
write!(f, "{} {}:{} {}", wire::UPDATE, module, param, data),
Changed { module, param, data } =>
write!(f, "{} {}:{} {}", wire::CHANGED, module, param, data),
Done { module, command, data } =>
write!(f, "{} {}:{} {}", wire::DONE, module, command, data),
Describing { id, structure } =>
write!(f, "{} {} {}", wire::DESCRIBING, id, structure),
Active { module } =>
if module.is_empty() { f.write_str(wire::ACTIVE) }
else { write!(f, "{} {}", wire::ACTIVE, module) },
Inactive { module } =>
if module.is_empty() { f.write_str(wire::INACTIVE) }
else { write!(f, "{} {}", wire::INACTIVE, module) },
Pong { token, data } =>
write!(f, "{} {} {}", wire::PONG, token, data),
Idn => f.write_str(wire::IDN),
IdnReply { encoded } => f.write_str(&encoded),
Read { module, param } =>
write!(f, "{} {}:{}", wire::READ, module, param),
Change { module, param, value } =>
write!(f, "{} {}:{} {}", wire::CHANGE, module, param, value),
Do { module, command, arg } =>
write!(f, "{} {}:{} {}", wire::DO, module, command, arg),
Describe => f.write_str(wire::DESCRIBE),
Activate { module } =>
if module.is_empty() { f.write_str(wire::ACTIVATE) }
else { write!(f, "{} {}", wire::ACTIVATE, module) },
Deactivate { module } =>
if module.is_empty() { f.write_str(wire::DEACTIVATE) }
else { write!(f, "{} {}", wire::DEACTIVATE, module) },
Ping { token } =>
if token.is_empty() { f.write_str(wire::PING) }
else { write!(f, "{} {}", wire::PING, token) },
ErrMsg { class, report } =>
write!(f, "{} {} {}", wire::ERROR, class, report),
InitUpdates { .. } => write!(f, "<updates>"),
Quit => write!(f, "<eof>"),
}
}
}
impl IncomingMsg {
pub fn bare(msg: Msg) -> Self {
IncomingMsg(String::new(), msg)
}
}
impl fmt::Display for IncomingMsg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}