cuprate_rpc_types/misc/
status.rs

1//! RPC response status type.
2
3//---------------------------------------------------------------------------------------------------- Import
4use std::fmt::Display;
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9#[cfg(feature = "epee")]
10use cuprate_epee_encoding::{
11    macros::bytes::{Buf, BufMut},
12    EpeeValue, Marker,
13};
14
15use crate::constants::{
16    CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
17    CORE_RPC_STATUS_PAYMENT_REQUIRED,
18};
19
20//---------------------------------------------------------------------------------------------------- Status
21// TODO: this type needs to expand more.
22// There are a lot of RPC calls that will return a random
23// string inside, which isn't compatible with [`Status`].
24
25/// RPC response status.
26///
27/// This type represents `monerod`'s frequently appearing string field, `status`.
28///
29/// Reference: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server_commands_defs.h#L78-L81>.
30///
31/// ## Serialization and string formatting
32/// ```rust
33/// use cuprate_rpc_types::{
34///     misc::Status,
35///     CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
36///     CORE_RPC_STATUS_PAYMENT_REQUIRED
37/// };
38/// use serde_json::to_string;
39///
40/// let other = Status::Other("OTHER".into());
41///
42/// assert_eq!(to_string(&Status::Ok).unwrap(),              r#""OK""#);
43/// assert_eq!(to_string(&Status::Busy).unwrap(),            r#""BUSY""#);
44/// assert_eq!(to_string(&Status::NotMining).unwrap(),       r#""NOT MINING""#);
45/// assert_eq!(to_string(&Status::PaymentRequired).unwrap(), r#""PAYMENT REQUIRED""#);
46/// assert_eq!(to_string(&other).unwrap(),                   r#""OTHER""#);
47///
48/// assert_eq!(Status::Ok.as_ref(),              CORE_RPC_STATUS_OK);
49/// assert_eq!(Status::Busy.as_ref(),            CORE_RPC_STATUS_BUSY);
50/// assert_eq!(Status::NotMining.as_ref(),       CORE_RPC_STATUS_NOT_MINING);
51/// assert_eq!(Status::PaymentRequired.as_ref(), CORE_RPC_STATUS_PAYMENT_REQUIRED);
52/// assert_eq!(other.as_ref(),                   "OTHER");
53///
54/// assert_eq!(format!("{}", Status::Ok),              CORE_RPC_STATUS_OK);
55/// assert_eq!(format!("{}", Status::Busy),            CORE_RPC_STATUS_BUSY);
56/// assert_eq!(format!("{}", Status::NotMining),       CORE_RPC_STATUS_NOT_MINING);
57/// assert_eq!(format!("{}", Status::PaymentRequired), CORE_RPC_STATUS_PAYMENT_REQUIRED);
58/// assert_eq!(format!("{}", other),                   "OTHER");
59///
60/// assert_eq!(format!("{:?}", Status::Ok),              "Ok");
61/// assert_eq!(format!("{:?}", Status::Busy),            "Busy");
62/// assert_eq!(format!("{:?}", Status::NotMining),       "NotMining");
63/// assert_eq!(format!("{:?}", Status::PaymentRequired), "PaymentRequired");
64/// assert_eq!(format!("{:?}", other),                   r#"Other("OTHER")"#);
65/// ```
66#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
67#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
68pub enum Status {
69    // FIXME:
70    // `#[serde(rename = "")]` only takes raw string literals?
71    // We have to re-type the constants here...
72    /// Successful RPC response, everything is OK; [`CORE_RPC_STATUS_OK`].
73    #[cfg_attr(feature = "serde", serde(rename = "OK"))]
74    #[default]
75    Ok,
76
77    /// The daemon is busy, try later; [`CORE_RPC_STATUS_BUSY`].
78    #[cfg_attr(feature = "serde", serde(rename = "BUSY"))]
79    Busy,
80
81    /// The daemon is not mining; [`CORE_RPC_STATUS_NOT_MINING`].
82    #[cfg_attr(feature = "serde", serde(rename = "NOT MINING"))]
83    NotMining,
84
85    /// Payment is required for RPC; [`CORE_RPC_STATUS_PAYMENT_REQUIRED`].
86    #[cfg_attr(feature = "serde", serde(rename = "PAYMENT REQUIRED"))]
87    PaymentRequired,
88
89    /// Some unknown other string.
90    ///
91    /// This exists to act as a catch-all for all of
92    /// `monerod`'s other strings it puts in the `status` field.
93    #[cfg_attr(feature = "serde", serde(rename = "OTHER"), serde(untagged))]
94    Other(String),
95}
96
97impl From<String> for Status {
98    fn from(s: String) -> Self {
99        match s.as_str() {
100            CORE_RPC_STATUS_OK => Self::Ok,
101            CORE_RPC_STATUS_BUSY => Self::Busy,
102            CORE_RPC_STATUS_NOT_MINING => Self::NotMining,
103            CORE_RPC_STATUS_PAYMENT_REQUIRED => Self::PaymentRequired,
104            _ => Self::Other(s),
105        }
106    }
107}
108
109impl AsRef<str> for Status {
110    fn as_ref(&self) -> &str {
111        match self {
112            Self::Ok => CORE_RPC_STATUS_OK,
113            Self::Busy => CORE_RPC_STATUS_BUSY,
114            Self::NotMining => CORE_RPC_STATUS_NOT_MINING,
115            Self::PaymentRequired => CORE_RPC_STATUS_PAYMENT_REQUIRED,
116            Self::Other(s) => s,
117        }
118    }
119}
120
121impl Display for Status {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        f.write_str(self.as_ref())
124    }
125}
126
127// [`Status`] is essentially a [`String`] when it comes to
128// (de)serialization, except when writing we usually have
129// access to a `&'static str` and don't need to allocate.
130//
131// See below for more impl info:
132// <https://github.com/Cuprate/cuprate/blob/bef2a2cbd4e1194991751d1fbc96603cba8c7a51/net/epee-encoding/src/value.rs#L366-L392>.
133#[cfg(feature = "epee")]
134impl EpeeValue for Status {
135    const MARKER: Marker = <String as EpeeValue>::MARKER;
136
137    fn read<B: Buf>(r: &mut B, marker: &Marker) -> cuprate_epee_encoding::Result<Self> {
138        let string = <String as EpeeValue>::read(r, marker)?;
139        Ok(Self::from(string))
140    }
141
142    fn should_write(&self) -> bool {
143        true
144    }
145
146    fn epee_default_value() -> Option<Self> {
147        // <https://github.com/Cuprate/cuprate/pull/147#discussion_r1654992559>
148        Some(Self::Other(String::new()))
149    }
150
151    fn write<B: BufMut>(self, w: &mut B) -> cuprate_epee_encoding::Result<()> {
152        cuprate_epee_encoding::write_bytes(self.as_ref(), w)
153    }
154}
155
156//---------------------------------------------------------------------------------------------------- Tests
157#[cfg(test)]
158mod test {
159    use super::*;
160
161    // Test epee (de)serialization works.
162    #[test]
163    #[cfg(feature = "epee")]
164    fn epee() {
165        for status in [
166            Status::Ok,
167            Status::Busy,
168            Status::NotMining,
169            Status::PaymentRequired,
170            Status::Other(String::new()),
171        ] {
172            let mut buf = vec![];
173
174            <Status as EpeeValue>::write(status.clone(), &mut buf).unwrap();
175            let status2 =
176                <Status as EpeeValue>::read(&mut buf.as_slice(), &<Status as EpeeValue>::MARKER)
177                    .unwrap();
178
179            assert_eq!(status, status2);
180        }
181    }
182}