use std::borrow::Cow;
use serde::Deserialize;
use serde::de::DeserializeOwned;
use crate::error::{Error, Result};
pub mod response_code;
#[cfg(doc)]
use crate::command::Command;
pub use response_code::{ResponseCode, UnknownResponseCode};
#[derive(Debug, Deserialize)]
pub(crate) struct Envelope<'a> {
pub code: ResponseCode,
pub when: u64,
#[serde(borrow)]
pub msg: &'a serde_json::value::RawValue,
#[serde(borrow)]
pub desc: Cow<'a, str>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Response<T> {
pub code: ResponseCode,
pub when: u64,
pub msg: T,
pub desc: String,
}
pub fn parse_response<T: DeserializeOwned>(raw: &[u8]) -> Result<Response<T>> {
let text = core::str::from_utf8(raw).map_err(|_| Error::InvalidUtf8)?;
let envelope: Envelope<'_> = serde_json::from_str(text)?;
if envelope.code.is_success() {
let msg: T = serde_json::from_str(envelope.msg.get())?;
Ok(Response {
code: envelope.code,
when: envelope.when,
msg,
desc: envelope.desc.into_owned(),
})
} else {
let msg_str = match serde_json::from_str::<Cow<'_, str>>(envelope.msg.get()) {
Ok(s) => s.into_owned(),
Err(_) => envelope.msg.get().trim().to_string(),
};
Err(Error::Device {
code: envelope.code,
msg: msg_str,
desc: envelope.desc.into_owned(),
})
}
}
pub fn deserialize_bool_from_string<'de, D>(deserializer: D) -> std::result::Result<bool, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.to_lowercase().as_str() {
"enable" | "true" | "1" => Ok(true),
"disable" | "false" | "0" => Ok(false),
_ => Err(serde::de::Error::custom(format!(
"invalid boolean string: {}",
s
))),
}
}
pub fn deserialize_opt_bool_from_string<'de, D>(
deserializer: D,
) -> std::result::Result<Option<bool>, D::Error>
where
D: serde::Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
match opt {
Some(s) => match s.to_lowercase().as_str() {
"enable" | "true" | "1" => Ok(Some(true)),
"disable" | "false" | "0" => Ok(Some(false)),
_ => Err(serde::de::Error::custom(format!(
"invalid boolean string: {}",
s
))),
},
None => Ok(None),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq, Eq, Deserialize)]
struct DummyPayload {
key: String,
count: u32,
}
#[test]
fn parse_response_success_typed() {
let raw =
br#"{"code":0,"when":1692685443,"msg":{"key":"abc","count":42},"desc":"test.cmd"}"#;
let r: Response<DummyPayload> = parse_response(raw).unwrap();
assert_eq!(r.code, ResponseCode::Success);
assert_eq!(r.when, 1692685443);
assert_eq!(
r.msg,
DummyPayload {
key: "abc".to_string(),
count: 42
}
);
assert_eq!(r.desc, "test.cmd");
}
#[test]
fn parse_response_success_string() {
let raw = br#"{"code":0,"when":123,"msg":"ok","desc":"test"}"#;
let r: Response<String> = parse_response(raw).unwrap();
assert_eq!(r.code, ResponseCode::Success);
assert_eq!(r.msg, "ok");
}
#[test]
fn parse_response_error_string() {
let raw = br#"{"code":-1,"when":123,"msg":"some error","desc":"test"}"#;
let err = parse_response::<String>(raw).unwrap_err();
match err {
Error::Device { code, msg, desc } => {
assert_eq!(code, ResponseCode::Fail);
assert_eq!(msg, "some error");
assert_eq!(desc, "test");
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn parse_response_error_object() {
let raw = br#"{"code":-2,"when":123,"msg":{"detail":"invalid field"},"desc":"test"}"#;
let err = parse_response::<String>(raw).unwrap_err();
match err {
Error::Device { code, msg, desc } => {
assert_eq!(code, ResponseCode::InvalidCommand);
assert_eq!(msg, r#"{"detail":"invalid field"}"#);
assert_eq!(desc, "test");
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn parse_response_out_of_order() {
let raw = br#"{"desc":"test","msg":"ok","when":999,"code":0}"#;
let r: Response<String> = parse_response(raw).unwrap();
assert_eq!(r.code, ResponseCode::Success);
assert_eq!(r.when, 999);
assert_eq!(r.msg, "ok");
assert_eq!(r.desc, "test");
}
#[test]
fn parse_response_missing_field_fails() {
let raw = br#"{"code":0,"msg":"ok","desc":"test"}"#; let err = parse_response::<String>(raw).unwrap_err();
assert!(matches!(err, Error::Json(_)));
}
#[test]
fn parse_response_invalid_code_fails() {
let raw = br#"{"code":99,"when":1,"msg":"ok","desc":"test"}"#;
let err = parse_response::<String>(raw).unwrap_err();
assert!(matches!(err, Error::Json(_)));
}
#[test]
fn parse_response_malformed_typed_payload_fails() {
let raw = br#"{"code":0,"when":1,"msg":"not an object","desc":"test"}"#;
let err = parse_response::<DummyPayload>(raw).unwrap_err();
assert!(matches!(err, Error::Json(_)));
}
#[test]
fn parse_response_invalid_utf8_fails() {
let raw = &[0xFF, 0xFE, 0xFD];
let err = parse_response::<String>(raw).unwrap_err();
assert!(matches!(err, Error::InvalidUtf8));
}
}