use std::fmt;
use std::str::FromStr;
use dcbor::CBOR;
use super::map_cbor;
use crate::error::ErrorKind;
use crate::fountain::EncoderOptions;
use crate::ur::{self, ParsedUr};
use crate::{DecoderLimits, Error, Result, UrType};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ur {
ur_type: UrType,
cbor: CBOR,
}
impl Ur {
#[must_use]
pub fn new(ur_type: UrType, cbor: impl Into<CBOR>) -> Self {
Self {
ur_type,
cbor: cbor.into(),
}
}
pub fn from_cbor_data(ur_type: UrType, data: &[u8]) -> Result<Self> {
let cbor = map_cbor(CBOR::try_from_data(data), ErrorKind::CborDecode)?;
Ok(Self::new(ur_type, cbor))
}
#[must_use]
pub const fn ur_type(&self) -> &UrType {
&self.ur_type
}
#[must_use]
pub const fn cbor(&self) -> &CBOR {
&self.cbor
}
#[must_use]
pub fn into_cbor(self) -> CBOR {
self.cbor
}
#[must_use]
pub fn to_cbor_data(&self) -> Vec<u8> {
self.cbor.to_cbor_data()
}
#[must_use]
pub fn to_qr_string(&self) -> String {
ur::to_qr_string(&self.to_string())
}
pub fn encoder(&self, options: EncoderOptions) -> Result<ur::Encoder> {
ur::Encoder::new(self.ur_type.clone(), self.to_cbor_data(), options)
}
}
impl fmt::Display for Ur {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&ur::encode(&self.ur_type, &self.to_cbor_data()))
}
}
impl FromStr for Ur {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match ur::parse(s, &DecoderLimits::default())? {
ParsedUr::Single { ur_type, message } => Self::from_cbor_data(ur_type, &message),
ParsedUr::Multi { .. } => Err(Error::new(ErrorKind::NotSinglePart)),
}
}
}
impl TryFrom<ur::Decoded> for Ur {
type Error = Error;
fn try_from(decoded: ur::Decoded) -> Result<Self> {
let (ur_type, message) = decoded.into_parts();
Self::from_cbor_data(ur_type, &message)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bytewords::{self, Style};
use crate::ur_type;
const GOLDEN: &str = "ur:test/lsadaoaxjygonesw";
#[test]
fn array_123_matches_published_golden() {
let ur = Ur::new(ur_type!("test"), vec![1, 2, 3]);
assert_eq!(ur.to_string(), GOLDEN);
assert_eq!(ur.ur_type().as_str(), "test");
let parsed = Ur::from_str(GOLDEN).unwrap();
assert_eq!(parsed, ur);
assert_eq!(parsed.cbor(), ur.cbor());
let upper = Ur::from_str("UR:TEST/LSADAOAXJYGONESW").unwrap();
assert_eq!(upper, ur);
assert_eq!(ur.to_qr_string(), "UR:TEST/LSADAOAXJYGONESW");
}
#[test]
fn rejects_illegal_types() {
assert_eq!(
UrType::new("Bad_Type").unwrap_err().kind(),
ErrorKind::InvalidType
);
}
#[test]
fn from_str_rejects_multipart_and_bad_cbor() {
let ur = Ur::new(ur_type!("test"), (0_u8..64).collect::<Vec<_>>());
let mut encoder = ur.encoder(EncoderOptions::new(12)).unwrap();
let part = encoder.next().unwrap();
assert_eq!(
Ur::from_str(&part).unwrap_err().kind(),
ErrorKind::NotSinglePart
);
let body = bytewords::encode(&[0xff, 0xff], Style::Minimal);
let uri = format!("ur:test/{body}");
assert!(matches!(
Ur::from_str(&uri).unwrap_err(),
ref e if e.kind() == ErrorKind::CborDecode
));
}
#[test]
fn from_cbor_data_and_into_cbor() {
let ur = Ur::new(ur_type!("test"), vec![1, 2, 3]);
let data = ur.to_cbor_data();
let rebuilt = Ur::from_cbor_data(ur_type!("test"), &data).unwrap();
assert_eq!(rebuilt, ur);
assert!(matches!(
Ur::from_cbor_data(ur_type!("test"), &[0xff, 0xff]).unwrap_err(),
ref e if e.kind() == ErrorKind::CborDecode
));
let cbor = ur.clone().into_cbor();
assert_eq!(cbor, *ur.cbor());
}
}