rpc_router/
rpc_id.rs

1use crate::RpcRequestParsingError;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use serde_json::Value;
4use std::sync::Arc;
5use uuid::Uuid;
6
7/// Represents a JSON-RPC 2.0 Request ID, which can be a String, Number, or Null.
8/// Uses `Arc<str>` for strings to allow for efficient cloning, especially when the
9/// ID is part of request/response structures that might be cloned (e.g., in tracing/logging).
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub enum RpcId {
12	String(Arc<str>),
13	Number(i64),
14	Null,
15}
16
17// region:    --- ID Constructors
18
19impl RpcId {
20	/// Generate a new ID given a scheme kind and encoding.
21	pub fn from_scheme(kind: IdSchemeKind, enc: IdSchemeEncoding) -> Self {
22		let s = enc.encode(kind.generate());
23		RpcId::String(Arc::from(s))
24	}
25
26	// region:    --- Uuid Convenient Constructors
27
28	pub fn new_uuid_v4() -> Self {
29		Self::from_scheme(IdSchemeKind::UuidV4, IdSchemeEncoding::Standard)
30	}
31	pub fn new_uuid_v4_base64() -> Self {
32		Self::from_scheme(IdSchemeKind::UuidV4, IdSchemeEncoding::Base64)
33	}
34	pub fn new_uuid_v4_base64url() -> Self {
35		Self::from_scheme(IdSchemeKind::UuidV4, IdSchemeEncoding::Base64UrlNoPad)
36	}
37	pub fn new_uuid_v4_base58() -> Self {
38		Self::from_scheme(IdSchemeKind::UuidV4, IdSchemeEncoding::Base58)
39	}
40
41	pub fn new_uuid_v7() -> Self {
42		Self::from_scheme(IdSchemeKind::UuidV7, IdSchemeEncoding::Standard)
43	}
44	pub fn new_uuid_v7_base64() -> Self {
45		Self::from_scheme(IdSchemeKind::UuidV7, IdSchemeEncoding::Base64)
46	}
47	pub fn new_uuid_v7_base64url() -> Self {
48		Self::from_scheme(IdSchemeKind::UuidV7, IdSchemeEncoding::Base64UrlNoPad)
49	}
50	pub fn new_uuid_v7_base58() -> Self {
51		Self::from_scheme(IdSchemeKind::UuidV7, IdSchemeEncoding::Base58)
52	}
53
54	// endregion: --- Uuid Convenient Constructors
55}
56
57/// Pick the ID scheme you want.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub enum IdSchemeKind {
60	UuidV4,
61	UuidV7,
62	// Ulid, Snowflake, Nanoid, etc. can be added later
63}
64
65/// Pick your base-encoding:
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67pub enum IdSchemeEncoding {
68	Standard,
69	Base64,
70	Base64UrlNoPad,
71	Base58,
72}
73
74impl IdSchemeKind {
75	fn generate(&self) -> Vec<u8> {
76		match self {
77			IdSchemeKind::UuidV4 => Uuid::new_v4().as_bytes().to_vec(),
78			IdSchemeKind::UuidV7 => Uuid::now_v7().as_bytes().to_vec(),
79			// Add other generators later...
80		}
81	}
82}
83
84impl IdSchemeEncoding {
85	/// Turn a byte slice into your desired string form.
86	fn encode(&self, bytes: Vec<u8>) -> String {
87		match self {
88			IdSchemeEncoding::Standard => {
89				// Assume bytes come from UUID; reparse to UUID for pretty string.
90				Uuid::from_slice(&bytes).map(|u| u.to_string()).unwrap_or_default()
91			}
92			IdSchemeEncoding::Base64 => data_encoding::BASE64.encode(&bytes),
93			IdSchemeEncoding::Base64UrlNoPad => data_encoding::BASE64URL_NOPAD.encode(&bytes),
94			IdSchemeEncoding::Base58 => bs58::encode(&bytes).into_string(),
95		}
96	}
97}
98
99// endregion: --- ID Constructors
100
101// region:    --- std Display
102
103impl core::fmt::Display for RpcId {
104	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105		match self {
106			RpcId::String(s) => write!(f, "{}", s),
107			RpcId::Number(n) => write!(f, "{}", n),
108			RpcId::Null => write!(f, "null"),
109		}
110	}
111}
112
113// endregion: --- std Display
114
115// -- Conversions
116
117impl RpcId {
118	/// Converts the `RpcId` into a `serde_json::Value`. Infallible.
119	pub fn to_value(&self) -> Value {
120		match self {
121			RpcId::String(s) => Value::String(s.to_string()),
122			RpcId::Number(n) => Value::Number((*n).into()),
123			RpcId::Null => Value::Null,
124		}
125	}
126
127	/// Attempts to convert a `serde_json::Value` into an `RpcId`.
128	/// Returns `Error::RpcIdInvalid` if the `value` is not a String, Number, or Null.
129	pub fn from_value(value: Value) -> core::result::Result<Self, RpcRequestParsingError> {
130		match value {
131			Value::String(s) => Ok(RpcId::String(s.into())),
132			Value::Number(n) => n.as_i64().map(RpcId::Number).ok_or_else(|| RpcRequestParsingError::IdInvalid {
133				actual: format!("{n}"),
134				cause: "Number is not a valid i64".into(),
135			}),
136			Value::Null => Ok(RpcId::Null),
137			_ => Err(RpcRequestParsingError::IdInvalid {
138				actual: format!("{value:?}"),
139				cause: "ID must be a String, Number, or Null".into(),
140			}),
141		}
142	}
143}
144
145// -- Default
146
147impl Default for RpcId {
148	/// Defaults to `RpcId::Null`.
149	fn default() -> Self {
150		RpcId::Null
151	}
152}
153
154// -- Serde
155
156impl Serialize for RpcId {
157	fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
158	where
159		S: Serializer,
160	{
161		match self {
162			RpcId::String(s) => serializer.serialize_str(s),
163			RpcId::Number(n) => serializer.serialize_i64(*n),
164			RpcId::Null => serializer.serialize_none(),
165		}
166	}
167}
168
169impl<'de> Deserialize<'de> for RpcId {
170	fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
171	where
172		D: Deserializer<'de>,
173	{
174		let value = Value::deserialize(deserializer)?;
175		RpcId::from_value(value).map_err(serde::de::Error::custom)
176	}
177}
178
179// -- From Implementations
180
181impl From<String> for RpcId {
182	fn from(s: String) -> Self {
183		RpcId::String(s.into())
184	}
185}
186
187impl From<&str> for RpcId {
188	fn from(s: &str) -> Self {
189		RpcId::String(s.into())
190	}
191}
192
193impl From<i64> for RpcId {
194	fn from(n: i64) -> Self {
195		RpcId::Number(n)
196	}
197}
198
199impl From<i32> for RpcId {
200	fn from(n: i32) -> Self {
201		RpcId::Number(n as i64)
202	}
203}
204
205impl From<u32> for RpcId {
206	fn from(n: u32) -> Self {
207		RpcId::Number(n as i64)
208	}
209}
210
211// region:    --- Tests
212
213#[cfg(test)]
214mod tests {
215	use super::*;
216	use serde_json::{from_value, json, to_value};
217
218	type TestResult<T> = core::result::Result<T, Box<dyn std::error::Error>>; // For tests.
219
220	#[test]
221	fn test_rpc_id_ser_de() -> TestResult<()> {
222		// -- Setup & Fixtures
223		let ids = [
224			RpcId::String("id-1".into()),
225			RpcId::Number(123),
226			RpcId::Null,
227			RpcId::String("".into()), // Empty string
228		];
229		let expected_values = [
230			json!("id-1"),
231			json!(123),
232			json!(null),
233			json!(""), // Empty string JSON
234		];
235
236		// -- Exec & Check
237		for (i, id) in ids.iter().enumerate() {
238			let value = to_value(id)?;
239			assert_eq!(value, expected_values[i], "Serialization check for id[{i}]");
240
241			let deserialized_id: RpcId = from_value(value.clone())?;
242			assert_eq!(&deserialized_id, id, "Deserialization check for id[{i}]");
243
244			let from_value_id = RpcId::from_value(value)?;
245			assert_eq!(from_value_id, *id, "from_value check for id[{i}]");
246		}
247
248		Ok(())
249	}
250
251	#[test]
252	fn test_rpc_id_from_value_invalid() -> TestResult<()> {
253		// -- Setup & Fixtures
254		let invalid_values = vec![
255			json!(true),
256			json!(123.45), // Float number
257			json!([1, 2]),
258			json!({"a": 1}),
259		];
260
261		// -- Exec & Check
262		for value in invalid_values {
263			let res = RpcId::from_value(value.clone());
264			assert!(
265				matches!(res, Err(RpcRequestParsingError::IdInvalid { .. })),
266				"Expected RpcIdInvalid for value: {:?}",
267				value
268			);
269		}
270
271		Ok(())
272	}
273
274	#[test]
275	fn test_rpc_id_to_value() -> TestResult<()> {
276		// -- Setup & Fixtures
277		let id_str = RpcId::String("hello".into());
278		let id_num = RpcId::Number(42);
279		let id_null = RpcId::Null;
280
281		// -- Exec
282		let val_str = id_str.to_value();
283		let val_num = id_num.to_value();
284		let val_null = id_null.to_value();
285
286		// -- Check
287		assert_eq!(val_str, json!("hello"));
288		assert_eq!(val_num, json!(42));
289		assert_eq!(val_null, json!(null));
290
291		Ok(())
292	}
293
294	#[test]
295	fn test_rpc_id_from_impls() -> TestResult<()> {
296		// -- Check String/&str
297		assert_eq!(RpcId::from("test_str"), RpcId::String("test_str".into()));
298		assert_eq!(
299			RpcId::from(String::from("test_string")),
300			RpcId::String("test_string".into())
301		);
302
303		// -- Check numbers
304		assert_eq!(RpcId::from(100i64), RpcId::Number(100));
305		assert_eq!(RpcId::from(200i32), RpcId::Number(200));
306		assert_eq!(RpcId::from(300u32), RpcId::Number(300));
307
308		Ok(())
309	}
310
311	#[test]
312	fn test_rpc_id_default() -> TestResult<()> {
313		// -- Check
314		assert_eq!(RpcId::default(), RpcId::Null);
315
316		Ok(())
317	}
318}
319
320// endregion: --- Tests