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}