cuprate_json_rpc/
id.rs

1//! [`Id`]: request/response identification.
2
3//---------------------------------------------------------------------------------------------------- Use
4use serde::{Deserialize, Serialize};
5use std::borrow::Cow;
6
7//---------------------------------------------------------------------------------------------------- Id
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(untagged)]
10/// [Request](crate::Request)/[Response](crate::Response) identification.
11///
12/// This is the [JSON-RPC 2.0 `id` field](https://www.jsonrpc.org/specification)
13/// type found in `Request/Response`s.
14///
15/// # From
16/// This type implements [`From`] on:
17/// - [`String`]
18/// - [`str`]
19/// - [`u8`], [`u16`], [`u32`], [`u64`]
20///
21/// and all of those wrapped in [`Option`].
22///
23/// If the `Option` is [`None`], [`Id::Null`] is returned.
24///
25/// Note that the `&str` implementations will allocate, use [`Id::from_static_str`]
26/// (or just manually create the `Cow`) for a non-allocating `Id`.
27///
28/// ```rust
29/// use cuprate_json_rpc::Id;
30///
31/// assert_eq!(Id::from(String::new()), Id::Str("".into()));
32/// assert_eq!(Id::from(Some(String::new())), Id::Str("".into()));
33/// assert_eq!(Id::from(None::<String>), Id::Null);
34/// assert_eq!(Id::from(123_u64), Id::Num(123_u64));
35/// assert_eq!(Id::from(Some(123_u64)), Id::Num(123_u64));
36/// assert_eq!(Id::from(None::<u64>), Id::Null);
37/// ```
38pub enum Id {
39    /// A JSON `null` value.
40    ///
41    /// ```rust
42    /// use cuprate_json_rpc::Id;
43    /// use serde_json::{from_value,to_value,json,Value};
44    ///
45    /// assert_eq!(from_value::<Id>(json!(null)).unwrap(), Id::Null);
46    /// assert_eq!(to_value(Id::Null).unwrap(), Value::Null);
47    ///
48    /// // Not a real `null`, but a string.
49    /// assert_eq!(from_value::<Id>(json!("null")).unwrap(), Id::Str("null".into()));
50    /// ```
51    Null,
52
53    /// A JSON `number` value.
54    Num(u64),
55
56    /// A JSON `string` value.
57    ///
58    /// This is a `Cow<'static, str>` to support both 0-allocation for
59    /// `const` string ID's commonly found in programs, as well as support
60    /// for runtime [`String`]'s.
61    ///
62    /// ```rust
63    /// use std::borrow::Cow;
64    /// use cuprate_json_rpc::Id;
65    ///
66    /// /// A program's static ID.
67    /// const ID: &'static str = "my_id";
68    ///
69    /// // No allocation.
70    /// let s = Id::Str(Cow::Borrowed(ID));
71    ///
72    /// // Runtime allocation.
73    /// let s = Id::Str(Cow::Owned("runtime_id".to_string()));
74    /// ```
75    Str(Cow<'static, str>),
76}
77
78impl Id {
79    /// This returns `Some(u64)` if [`Id`] is a number.
80    ///
81    /// ```rust
82    /// use cuprate_json_rpc::Id;
83    ///
84    /// assert_eq!(Id::Num(0).as_u64(), Some(0));
85    /// assert_eq!(Id::Str("0".into()).as_u64(), None);
86    /// assert_eq!(Id::Null.as_u64(), None);
87    /// ```
88    pub const fn as_u64(&self) -> Option<u64> {
89        match self {
90            Self::Num(n) => Some(*n),
91            _ => None,
92        }
93    }
94
95    /// This returns `Some(&str)` if [`Id`] is a string.
96    ///
97    /// ```rust
98    /// use cuprate_json_rpc::Id;
99    ///
100    /// assert_eq!(Id::Str("0".into()).as_str(), Some("0"));
101    /// assert_eq!(Id::Num(0).as_str(), None);
102    /// assert_eq!(Id::Null.as_str(), None);
103    /// ```
104    pub fn as_str(&self) -> Option<&str> {
105        match self {
106            Self::Str(s) => Some(s.as_ref()),
107            _ => None,
108        }
109    }
110
111    /// Returns `true` if `self` is [`Id::Null`].
112    ///
113    /// ```rust
114    /// use cuprate_json_rpc::Id;
115    ///
116    /// assert!(Id::Null.is_null());
117    /// assert!(!Id::Num(0).is_null());
118    /// assert!(!Id::Str("".into()).is_null());
119    /// ```
120    pub fn is_null(&self) -> bool {
121        *self == Self::Null
122    }
123
124    /// Create a new [`Id::Str`] from a static string.
125    ///
126    /// ```rust
127    /// use cuprate_json_rpc::Id;
128    ///
129    /// assert_eq!(Id::from_static_str("hi"), Id::Str("hi".into()));
130    /// ```
131    pub const fn from_static_str(s: &'static str) -> Self {
132        Self::Str(Cow::Borrowed(s))
133    }
134
135    /// Inner infallible implementation of [`FromStr::from_str`]
136    const fn from_string(s: String) -> Self {
137        Self::Str(Cow::Owned(s))
138    }
139}
140
141impl std::str::FromStr for Id {
142    type Err = std::convert::Infallible;
143
144    fn from_str(s: &str) -> Result<Self, std::convert::Infallible> {
145        Ok(Self::from_string(s.to_string()))
146    }
147}
148
149impl From<String> for Id {
150    fn from(s: String) -> Self {
151        Self::from_string(s)
152    }
153}
154
155impl From<&str> for Id {
156    fn from(s: &str) -> Self {
157        Self::from_string(s.to_string())
158    }
159}
160
161impl From<Option<String>> for Id {
162    fn from(s: Option<String>) -> Self {
163        match s {
164            Some(s) => Self::from_string(s),
165            None => Self::Null,
166        }
167    }
168}
169
170impl From<Option<&str>> for Id {
171    fn from(s: Option<&str>) -> Self {
172        let s = s.map(ToString::to_string);
173        s.into()
174    }
175}
176
177/// Implement `From<unsigned integer>` for `Id`.
178///
179/// Not a generic since that clashes with `From<String>`.
180macro_rules! impl_u {
181    ($($u:ty),*) => {
182        $(
183            impl From<$u> for Id {
184                fn from(u: $u) -> Self {
185                    Self::Num(u64::from(u))
186                }
187            }
188
189            impl From<&$u> for Id {
190                fn from(u: &$u) -> Self {
191                    Self::Num(u64::from(*u))
192                }
193            }
194
195            impl From<Option<$u>> for Id {
196                fn from(u: Option<$u>) -> Self {
197                    match u {
198                        Some(u) => Self::Num(u64::from(u)),
199                        None => Self::Null,
200                    }
201                }
202            }
203        )*
204    }
205}
206
207impl_u!(u8, u16, u32);
208#[cfg(target_pointer_width = "64")]
209impl_u!(u64);
210
211//---------------------------------------------------------------------------------------------------- TESTS
212#[cfg(test)]
213mod test {
214    use super::*;
215
216    /// Basic [`Id::as_u64()`] tests.
217    #[test]
218    fn __as_u64() {
219        let id = Id::Num(u64::MIN);
220        assert_eq!(id.as_u64().unwrap(), u64::MIN);
221
222        let id = Id::Num(u64::MAX);
223        assert_eq!(id.as_u64().unwrap(), u64::MAX);
224
225        let id = Id::Null;
226        assert!(id.as_u64().is_none());
227        let id = Id::Str("".into());
228        assert!(id.as_u64().is_none());
229    }
230
231    /// Basic [`Id::as_str()`] tests.
232    #[test]
233    fn __as_str() {
234        let id = Id::Str("str".into());
235        assert_eq!(id.as_str().unwrap(), "str");
236
237        let id = Id::Null;
238        assert!(id.as_str().is_none());
239        let id = Id::Num(0);
240        assert!(id.as_str().is_none());
241    }
242}