bc_ur/
ur.rs

1use dcbor::prelude::*;
2use ur::decode;
3use crate::{ Error, Result, URType };
4
5/// A Uniform Resource (UR) is a URI-encoded CBOR object.
6#[derive(Debug, Clone, PartialEq)]
7pub struct UR {
8    ur_type: URType,
9    cbor: CBOR,
10}
11
12impl UR {
13    /// Creates a new UR from the provided type and CBOR.
14    pub fn new(ur_type: impl TryInto<URType, Error = Error>, cbor: impl Into<CBOR>) -> Result<UR> {
15        let ur_type = ur_type.try_into()?;
16        let cbor = cbor.into();
17        Ok(UR { ur_type, cbor })
18    }
19
20    /// Creates a new UR from the provided UR string.
21    pub fn from_ur_string(ur_string: impl Into<String>) -> Result<UR> {
22        let ur_string = ur_string.into().to_lowercase();
23        let strip_scheme = ur_string.strip_prefix("ur:").ok_or(Error::InvalidScheme)?;
24        let (ur_type, _) = strip_scheme.split_once('/').ok_or(Error::TypeUnspecified)?;
25        let ur_type = URType::new(ur_type)?;
26        let a = decode(&ur_string);
27        let (kind, data) = a.map_err(Error::UR)?;
28        if kind != ur::ur::Kind::SinglePart {
29            return Err(Error::NotSinglePart);
30        }
31        let cbor = CBOR::try_from_data(data)?;
32        Ok(UR { ur_type, cbor })
33    }
34
35    /// Returns the String representation of the UR.
36    pub fn string(&self) -> String {
37        let data = self.cbor.to_cbor_data();
38        ur::encode(&data, &ur::Type::Custom(self.ur_type.string()))
39    }
40
41    /// Returns the String representation of the UR in uppercase,
42    /// most-efficient for QR codes.
43    pub fn qr_string(&self) -> String {
44        self.string().to_uppercase()
45    }
46
47    /// Returns the data representation of the UR in uppercase,
48    /// most-efficient for QR codes.
49    pub fn qr_data(&self) -> Vec<u8> {
50        self.qr_string().as_bytes().to_vec()
51    }
52
53    /// Checks the UR type against the provided type.
54    pub fn check_type(&self, other_type: impl TryInto<URType, Error = Error>) -> Result<()> {
55        let other_type = other_type.try_into()?;
56        if self.ur_type != other_type {
57            Err(
58                Error::UnexpectedType(
59                    other_type.string().to_string(),
60                    self.ur_type.string().to_string()
61                )
62            )?;
63        }
64        Ok(())
65    }
66
67    pub fn ur_type(&self) -> &URType {
68        &self.ur_type
69    }
70
71    /// Returns the UR type.
72    pub fn ur_type_str(&self) -> &str {
73        self.ur_type.string()
74    }
75
76    pub fn cbor(&self) -> CBOR {
77        self.cbor.clone()
78    }
79}
80
81impl From<UR> for CBOR {
82    fn from(ur: UR) -> Self {
83        ur.cbor
84    }
85}
86
87impl From<UR> for String {
88    fn from(ur: UR) -> Self {
89        ur.string()
90    }
91}
92
93impl TryFrom<String> for UR {
94    type Error = Error;
95
96    fn try_from(value: String) -> Result<Self> {
97        UR::from_ur_string(value)
98    }
99}
100
101impl std::fmt::Display for UR {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        write!(f, "{}", self.string())
104    }
105}
106
107impl AsRef<UR> for UR {
108    fn as_ref(&self) -> &UR {
109        self
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_ur() {
119        let cbor: CBOR = vec![1, 2, 3].into();
120        let ur = UR::new("test", cbor.clone()).unwrap();
121        let ur_string = ur.string();
122        assert_eq!(ur_string, "ur:test/lsadaoaxjygonesw");
123        let ur = UR::from_ur_string(ur_string).unwrap();
124        assert_eq!(ur.ur_type_str(), "test");
125        assert_eq!(&ur.cbor, &cbor);
126
127        let caps_ur_string = "UR:TEST/LSADAOAXJYGONESW";
128        let ur = UR::from_ur_string(caps_ur_string).unwrap();
129        assert_eq!(ur.ur_type_str(), "test");
130        assert_eq!(&ur.cbor, &cbor);
131    }
132}