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}