bc_ur/
ur.rs

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