world_id_primitives/
rp.rs

1#![allow(clippy::unreadable_literal)]
2
3use std::{fmt, str::FromStr};
4
5use alloy_primitives::U160;
6use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
7
8use crate::FieldElement;
9
10/// The id of a relying party.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct RpId(U160);
13
14impl RpId {
15    /// Converts the RP id to an U160
16    #[must_use]
17    pub const fn into_inner(self) -> U160 {
18        self.0
19    }
20
21    /// Creates a new `RpId` by wrapping a `U160`
22    #[must_use]
23    pub const fn new(value: U160) -> Self {
24        Self(value)
25    }
26}
27
28impl fmt::Display for RpId {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        write!(f, "rp_{:040x}", self.0)
31    }
32}
33
34impl FromStr for RpId {
35    type Err = String;
36
37    fn from_str(s: &str) -> Result<Self, Self::Err> {
38        if let Some(id) = s.strip_prefix("rp_") {
39            Ok(Self(U160::from_str_radix(id, 16).map_err(|_| {
40                "Invalid RP ID format: expected hex string".to_string()
41            })?))
42        } else {
43            Err("A valid RP ID must start with 'rp_'".to_string())
44        }
45    }
46}
47
48impl From<U160> for RpId {
49    fn from(value: U160) -> Self {
50        Self(value)
51    }
52}
53
54impl From<RpId> for FieldElement {
55    fn from(value: RpId) -> Self {
56        Self::from(value.0)
57    }
58}
59
60impl Serialize for RpId {
61    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62    where
63        S: Serializer,
64    {
65        if serializer.is_human_readable() {
66            serializer.serialize_str(&self.to_string())
67        } else {
68            U160::serialize(&self.0, serializer)
69        }
70    }
71}
72
73impl<'de> Deserialize<'de> for RpId {
74    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75    where
76        D: Deserializer<'de>,
77    {
78        if deserializer.is_human_readable() {
79            let s = String::deserialize(deserializer)?;
80            Self::from_str(&s).map_err(D::Error::custom)
81        } else {
82            let value = U160::deserialize(deserializer)?;
83            Ok(Self(value))
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use alloy_primitives::uint;
91
92    use super::*;
93
94    #[test]
95    fn test_rpid_display() {
96        let rp_id = RpId::new(uint!(0x123456789abcdef0_U160));
97        assert_eq!(
98            rp_id.to_string(),
99            "rp_000000000000000000000000123456789abcdef0"
100        );
101
102        let rp_id = RpId::new(U160::MAX);
103        assert_eq!(
104            rp_id.to_string(),
105            "rp_ffffffffffffffffffffffffffffffffffffffff"
106        );
107
108        let rp_id = RpId::new(U160::ZERO);
109        assert_eq!(
110            rp_id.to_string(),
111            "rp_0000000000000000000000000000000000000000"
112        );
113    }
114
115    #[test]
116    fn test_rpid_from_str() {
117        let rp_id = "rp_000000000000000000000000123456789abcdef0"
118            .parse::<RpId>()
119            .unwrap();
120        assert_eq!(rp_id.0, uint!(0x123456789abcdef0_U160));
121
122        let rp_id = "rp_ffffffffffffffffffffffffffffffffffffffff"
123            .parse::<RpId>()
124            .unwrap();
125        assert_eq!(rp_id.0, U160::MAX);
126
127        let rp_id = "rp_0000000000000000000000000000000000000000"
128            .parse::<RpId>()
129            .unwrap();
130        assert_eq!(rp_id.0, 0);
131
132        let rp_id = "rp_000000000000000000000000123456789ABCDEF0"
133            .parse::<RpId>()
134            .unwrap();
135        assert_eq!(rp_id.0, uint!(0x123456789abcdef0_U160));
136    }
137
138    #[test]
139    fn test_rpid_from_str_errors() {
140        assert!("123456789abcdef0".parse::<RpId>().is_err());
141        assert!("rp_invalid".parse::<RpId>().is_err());
142        // TODO? empty string parses to 0 with U160::from_str_radix
143        // assert!("rp_".parse::<RpId>().is_err());
144    }
145
146    #[test]
147    fn test_rpid_roundtrip() {
148        let original = RpId::new(uint!(0x123456789abcdef0_U160));
149        let s = original.to_string();
150        let parsed = s.parse::<RpId>().unwrap();
151        assert_eq!(original, parsed);
152    }
153
154    #[test]
155    fn test_rpid_json_serialization() {
156        let rp_id = RpId::new(uint!(0x123456789abcdef0_U160));
157        let json = serde_json::to_string(&rp_id).unwrap();
158        assert_eq!(json, "\"rp_000000000000000000000000123456789abcdef0\"");
159
160        let deserialized: RpId = serde_json::from_str(&json).unwrap();
161        assert_eq!(rp_id, deserialized);
162    }
163
164    #[test]
165    fn test_rpid_binary_serialization() {
166        let rp_id = RpId::new(uint!(0x123456789abcdef0_U160));
167
168        let mut buffer = Vec::new();
169        ciborium::into_writer(&rp_id, &mut buffer).unwrap();
170
171        let decoded: RpId = ciborium::from_reader(&buffer[..]).unwrap();
172
173        assert_eq!(rp_id, decoded);
174    }
175}