bc_ur/
ur.rs

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