use std::fmt;
use serde::de::{self, Visitor};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i8)]
pub enum ResponseCode {
Success = 0,
Fail = -1,
InvalidCommand = -2,
InvalidJsonItem = -3,
NoPermission = -4,
OutOfMemory = -5,
}
impl ResponseCode {
pub const fn is_success(self) -> bool {
matches!(self, Self::Success)
}
}
impl fmt::Display for ResponseCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (code, desc) = match self {
Self::Success => (0, "Success"),
Self::Fail => (-1, "Fail"),
Self::InvalidCommand => (-2, "Invalid command"),
Self::InvalidJsonItem => (-3, "Invalid JSON item"),
Self::NoPermission => (-4, "No permission"),
Self::OutOfMemory => (-5, "Out of memory"),
};
write!(f, "{code} ({desc})")
}
}
impl TryFrom<i8> for ResponseCode {
type Error = UnknownResponseCode;
fn try_from(value: i8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Success),
-1 => Ok(Self::Fail),
-2 => Ok(Self::InvalidCommand),
-3 => Ok(Self::InvalidJsonItem),
-4 => Ok(Self::NoPermission),
-5 => Ok(Self::OutOfMemory),
other => Err(UnknownResponseCode(other)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnknownResponseCode(pub i8);
impl fmt::Display for UnknownResponseCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unknown response code: {}", self.0)
}
}
impl std::error::Error for UnknownResponseCode {}
#[derive(Debug)]
struct ResponseCodeVisitor;
fn code_from_int<E: de::Error>(v: impl TryInto<i8>) -> Result<ResponseCode, E> {
let code: i8 = v
.try_into()
.map_err(|_| E::custom("response code out of i8 range"))?;
ResponseCode::try_from(code).map_err(|e| E::custom(e.to_string()))
}
impl Visitor<'_> for ResponseCodeVisitor {
type Value = ResponseCode;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a known WhatsMiner response code (0, -1, -2, -3, -4, -5)")
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
code_from_int(v)
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
code_from_int(v)
}
}
impl<'de> serde::Deserialize<'de> for ResponseCode {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_any(ResponseCodeVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_success_code() {
let json = r#"0"#;
let code: ResponseCode = serde_json::from_str(json).unwrap();
assert_eq!(code, ResponseCode::Success);
assert!(code.is_success());
}
#[test]
fn deserialize_fail_codes() {
for (raw, expected) in [
(-1i8, ResponseCode::Fail),
(-2, ResponseCode::InvalidCommand),
(-3, ResponseCode::InvalidJsonItem),
(-4, ResponseCode::NoPermission),
(-5, ResponseCode::OutOfMemory),
] {
let json = raw.to_string();
let code: ResponseCode = serde_json::from_str(&json).unwrap();
assert_eq!(code, expected, "mismatch for code {raw}");
assert!(!code.is_success());
}
}
#[test]
fn deserialize_unknown_code_returns_error() {
let json = r#"42"#;
let result = serde_json::from_str::<ResponseCode>(json);
assert!(result.is_err(), "expected error for unknown code 42");
}
#[test]
fn try_from_roundtrip() {
assert_eq!(ResponseCode::try_from(0).unwrap(), ResponseCode::Success);
assert_eq!(
ResponseCode::try_from(-4).unwrap(),
ResponseCode::NoPermission
);
assert!(ResponseCode::try_from(99).is_err());
}
#[test]
fn display() {
assert_eq!(ResponseCode::Success.to_string(), "0 (Success)");
assert_eq!(ResponseCode::NoPermission.to_string(), "-4 (No permission)");
}
}