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}