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_FAILED, 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, CORE_RPC_STATUS_FAILED
37/// };
38/// use serde_json::{to_string, from_str};
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::Failed).unwrap(),          r#""Failed""#);
44/// assert_eq!(to_string(&Status::Busy).unwrap(),            r#""BUSY""#);
45/// assert_eq!(to_string(&Status::NotMining).unwrap(),       r#""NOT MINING""#);
46/// assert_eq!(to_string(&Status::PaymentRequired).unwrap(), r#""PAYMENT REQUIRED""#);
47/// assert_eq!(to_string(&other).unwrap(),                   r#""OTHER""#);
48///
49/// assert_eq!(from_str::<Status>(r#""Ok""#).unwrap(),               Status::Ok);
50/// assert_eq!(from_str::<Status>(r#""OK""#).unwrap(),               Status::Ok);
51/// assert_eq!(from_str::<Status>(r#""Failed""#).unwrap(),           Status::Failed);
52/// assert_eq!(from_str::<Status>(r#""FAILED""#).unwrap(),           Status::Failed);
53/// assert_eq!(from_str::<Status>(r#""Busy""#).unwrap(),             Status::Busy);
54/// assert_eq!(from_str::<Status>(r#""BUSY""#).unwrap(),             Status::Busy);
55/// assert_eq!(from_str::<Status>(r#""NOT MINING""#).unwrap(),       Status::NotMining);
56/// assert_eq!(from_str::<Status>(r#""PAYMENT REQUIRED""#).unwrap(), Status::PaymentRequired);
57/// assert_eq!(from_str::<Status>(r#""OTHER""#).unwrap(),            other);
58///
59/// assert_eq!(Status::Ok.as_ref(),              CORE_RPC_STATUS_OK);
60/// assert_eq!(Status::Failed.as_ref(),          CORE_RPC_STATUS_FAILED);
61/// assert_eq!(Status::Busy.as_ref(),            CORE_RPC_STATUS_BUSY);
62/// assert_eq!(Status::NotMining.as_ref(),       CORE_RPC_STATUS_NOT_MINING);
63/// assert_eq!(Status::PaymentRequired.as_ref(), CORE_RPC_STATUS_PAYMENT_REQUIRED);
64/// assert_eq!(other.as_ref(),                   "OTHER");
65///
66/// assert_eq!(format!("{}", Status::Ok),              CORE_RPC_STATUS_OK);
67/// assert_eq!(format!("{}", Status::Failed),          CORE_RPC_STATUS_FAILED);
68/// assert_eq!(format!("{}", Status::Busy),            CORE_RPC_STATUS_BUSY);
69/// assert_eq!(format!("{}", Status::NotMining),       CORE_RPC_STATUS_NOT_MINING);
70/// assert_eq!(format!("{}", Status::PaymentRequired), CORE_RPC_STATUS_PAYMENT_REQUIRED);
71/// assert_eq!(format!("{}", other),                   "OTHER");
72///
73/// assert_eq!(format!("{:?}", Status::Ok),              "Ok");
74/// assert_eq!(format!("{:?}", Status::Failed),          "Failed");
75/// assert_eq!(format!("{:?}", Status::Busy),            "Busy");
76/// assert_eq!(format!("{:?}", Status::NotMining),       "NotMining");
77/// assert_eq!(format!("{:?}", Status::PaymentRequired), "PaymentRequired");
78/// assert_eq!(format!("{:?}", other),                   r#"Other("OTHER")"#);
79/// ```
80#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
81#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
82pub enum Status {
83    // FIXME:
84    // `#[serde(rename = "")]` only takes raw string literals?
85    // We have to re-type the constants here...
86    /// Successful RPC response, everything is OK; [`CORE_RPC_STATUS_OK`].
87    #[cfg_attr(feature = "serde", serde(rename = "OK", alias = "Ok"))]
88    #[default]
89    Ok,
90
91    /// Generic request failure.
92    #[cfg_attr(feature = "serde", serde(alias = "FAILED"))]
93    Failed,
94
95    /// The daemon is busy, try later; [`CORE_RPC_STATUS_BUSY`].
96    #[cfg_attr(feature = "serde", serde(rename = "BUSY", alias = "Busy"))]
97    Busy,
98
99    /// The daemon is not mining; [`CORE_RPC_STATUS_NOT_MINING`].
100    #[cfg_attr(feature = "serde", serde(rename = "NOT MINING"))]
101    NotMining,
102
103    /// Payment is required for RPC; [`CORE_RPC_STATUS_PAYMENT_REQUIRED`].
104    #[cfg_attr(feature = "serde", serde(rename = "PAYMENT REQUIRED"))]
105    PaymentRequired,
106
107    /// Some unknown other string.
108    ///
109    /// This exists to act as a catch-all for all of
110    /// `monerod`'s other strings it puts in the `status` field.
111    #[cfg_attr(feature = "serde", serde(rename = "OTHER"), serde(untagged))]
112    Other(String),
113}
114
115impl From<String> for Status {
116    fn from(s: String) -> Self {
117        match s.as_str() {
118            CORE_RPC_STATUS_OK => Self::Ok,
119            CORE_RPC_STATUS_BUSY => Self::Busy,
120            CORE_RPC_STATUS_NOT_MINING => Self::NotMining,
121            CORE_RPC_STATUS_PAYMENT_REQUIRED => Self::PaymentRequired,
122            CORE_RPC_STATUS_FAILED => Self::Failed,
123            _ => Self::Other(s),
124        }
125    }
126}
127
128impl AsRef<str> for Status {
129    fn as_ref(&self) -> &str {
130        match self {
131            Self::Ok => CORE_RPC_STATUS_OK,
132            Self::Failed => CORE_RPC_STATUS_FAILED,
133            Self::Busy => CORE_RPC_STATUS_BUSY,
134            Self::NotMining => CORE_RPC_STATUS_NOT_MINING,
135            Self::PaymentRequired => CORE_RPC_STATUS_PAYMENT_REQUIRED,
136            Self::Other(s) => s,
137        }
138    }
139}
140
141impl Display for Status {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        f.write_str(self.as_ref())
144    }
145}
146
147// [`Status`] is essentially a [`String`] when it comes to
148// (de)serialization, except when writing we usually have
149// access to a `&'static str` and don't need to allocate.
150//
151// See below for more impl info:
152// <https://github.com/Cuprate/cuprate/blob/bef2a2cbd4e1194991751d1fbc96603cba8c7a51/net/epee-encoding/src/value.rs#L366-L392>.
153#[cfg(feature = "epee")]
154impl EpeeValue for Status {
155    const MARKER: Marker = <String as EpeeValue>::MARKER;
156
157    fn read<B: Buf>(r: &mut B, marker: &Marker) -> cuprate_epee_encoding::Result<Self> {
158        let string = <String as EpeeValue>::read(r, marker)?;
159        Ok(Self::from(string))
160    }
161
162    fn should_write(&self) -> bool {
163        true
164    }
165
166    fn epee_default_value() -> Option<Self> {
167        // <https://github.com/Cuprate/cuprate/pull/147#discussion_r1654992559>
168        Some(Self::Other(String::new()))
169    }
170
171    fn write<B: BufMut>(self, w: &mut B) -> cuprate_epee_encoding::Result<()> {
172        cuprate_epee_encoding::write_bytes(self.as_ref(), w)
173    }
174}
175
176//---------------------------------------------------------------------------------------------------- Tests
177#[cfg(test)]
178mod test {
179    use super::*;
180
181    // Test epee (de)serialization works.
182    #[test]
183    #[cfg(feature = "epee")]
184    fn epee() {
185        for status in [
186            Status::Ok,
187            Status::Busy,
188            Status::NotMining,
189            Status::PaymentRequired,
190            Status::Other(String::new()),
191        ] {
192            let mut buf = vec![];
193
194            <Status as EpeeValue>::write(status.clone(), &mut buf).unwrap();
195            let status2 =
196                <Status as EpeeValue>::read(&mut buf.as_slice(), &<Status as EpeeValue>::MARKER)
197                    .unwrap();
198
199            assert_eq!(status, status2);
200        }
201    }
202}