use std::fmt;
use dcbor::CBOR;
use super::map_cbor;
use crate::ur::{IntoUrType, Kind};
use crate::{CborErrorKind, Error, Result, UrType};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ur {
ur_type: UrType,
cbor: CBOR,
}
impl Ur {
pub fn new(ur_type: impl IntoUrType, cbor: impl Into<CBOR>) -> Result<Self> {
Ok(Self {
ur_type: ur_type.into_ur_type()?,
cbor: cbor.into(),
})
}
pub fn from_ur_string(s: impl AsRef<str>) -> Result<Self> {
let (ur_type, kind, data) = crate::decode_with_type(s.as_ref())?;
if kind != Kind::SinglePart {
return Err(Error::NotSinglePart);
}
Ok(Self {
ur_type,
cbor: map_cbor(CBOR::try_from_data(data), CborErrorKind::Decode)?,
})
}
#[must_use]
pub fn string(&self) -> String {
crate::encode(&self.cbor.to_cbor_data(), &self.ur_type)
}
#[must_use]
pub fn qr_string(&self) -> String {
self.string().to_ascii_uppercase()
}
#[must_use]
pub fn qr_data(&self) -> Vec<u8> {
self.qr_string().into_bytes()
}
pub fn check_type(&self, expected: impl IntoUrType) -> Result<()> {
let expected = expected.into_ur_type()?;
if self.ur_type == expected {
Ok(())
} else {
Err(Error::UnexpectedType {
expected: String::from(expected.as_str()),
found: String::from(self.ur_type.as_str()),
})
}
}
#[must_use]
pub const fn ur_type(&self) -> &UrType {
&self.ur_type
}
#[must_use]
pub fn ur_type_str(&self) -> &str {
self.ur_type.as_str()
}
#[must_use]
pub const fn cbor(&self) -> &CBOR {
&self.cbor
}
#[must_use]
pub fn into_cbor(self) -> CBOR {
self.cbor
}
}
impl fmt::Display for Ur {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.string())
}
}
impl From<Ur> for String {
fn from(ur: Ur) -> Self {
ur.string()
}
}
impl From<Ur> for CBOR {
fn from(ur: Ur) -> Self {
ur.into_cbor()
}
}
impl TryFrom<String> for Ur {
type Error = Error;
fn try_from(value: String) -> Result<Self> {
Self::from_ur_string(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bytewords::{self, Style};
const GOLDEN: &str = "ur:test/lsadaoaxjygonesw";
#[test]
fn array_123_matches_published_golden() {
let ur = Ur::new("test", vec![1, 2, 3]).unwrap();
assert_eq!(ur.string(), GOLDEN);
assert_eq!(ur.ur_type_str(), "test");
assert_eq!(ur.to_string(), GOLDEN);
let parsed = Ur::from_ur_string(GOLDEN).unwrap();
assert_eq!(parsed, ur);
assert_eq!(parsed.cbor(), ur.cbor());
let upper = Ur::from_ur_string("UR:TEST/LSADAOAXJYGONESW").unwrap();
assert_eq!(upper, ur);
assert_eq!(ur.qr_string(), "UR:TEST/LSADAOAXJYGONESW");
assert_eq!(ur.qr_data(), b"UR:TEST/LSADAOAXJYGONESW");
}
#[test]
fn rejects_empty_and_illegal_types() {
assert_eq!(Ur::new("", vec![1]).unwrap_err(), Error::InvalidType);
assert_eq!(
Ur::new("Bad_Type", vec![1]).unwrap_err(),
Error::InvalidType
);
}
#[test]
fn from_ur_string_rejects_multipart_and_bad_cbor() {
let ur = Ur::new("test", (0_u8..64).collect::<Vec<_>>()).unwrap();
let mut encoder = crate::Encoder::new(&ur.cbor().to_cbor_data(), 12, ur.ur_type()).unwrap();
let part = encoder.next_part().unwrap();
assert_eq!(Ur::from_ur_string(&part).unwrap_err(), Error::NotSinglePart);
let body = bytewords::encode(&[0xff, 0xff], Style::Minimal);
let uri = format!("ur:test/{body}");
assert!(matches!(
Ur::from_ur_string(uri).unwrap_err(),
Error::Cbor(ref e) if e.kind() == CborErrorKind::Decode
));
}
#[test]
fn check_type_and_into_cbor() {
let ur = Ur::new("test", vec![1, 2, 3]).unwrap();
ur.check_type("test").unwrap();
ur.check_type(String::from("test")).unwrap();
ur.check_type(ur.ur_type()).unwrap();
ur.check_type(ur.ur_type().clone()).unwrap();
assert!(matches!(
ur.check_type("bytes").unwrap_err(),
Error::UnexpectedType { .. }
));
let cbor: CBOR = ur.clone().into();
assert_eq!(cbor, *ur.cbor());
let via_string = Ur::try_from(ur.string()).unwrap();
assert_eq!(via_string, ur);
}
}