use dcbor::{CBORTagged, CBORTaggedDecodable, CBORTaggedEncodable};
use super::{Ur, map_cbor};
use crate::error::ErrorKind;
use crate::{Error, Result, UrType};
fn tag_ur_types<T: CBORTagged>() -> Result<Vec<UrType>> {
T::cbor_tags()
.iter()
.map(|tag| {
let name = tag
.name()
.ok_or_else(|| Error::new(ErrorKind::InvalidType))?;
UrType::new(&name)
})
.collect()
}
fn first_tag_ur_type<T: CBORTagged>() -> Result<UrType> {
let name = T::cbor_tags()
.first()
.and_then(dcbor::Tag::name)
.ok_or_else(|| Error::new(ErrorKind::InvalidType))?;
UrType::new(&name)
}
pub trait UrEncodable {
fn to_ur(&self) -> Result<Ur>;
}
pub trait UrDecodable: Sized {
fn from_ur(ur: &Ur) -> Result<Self>;
}
impl<T: CBORTaggedEncodable> UrEncodable for T {
fn to_ur(&self) -> Result<Ur> {
Ok(Ur::new(first_tag_ur_type::<T>()?, self.untagged_cbor()))
}
}
impl<T: CBORTaggedDecodable> UrDecodable for T {
fn from_ur(ur: &Ur) -> Result<Self> {
let expected = tag_ur_types::<T>()?;
if expected.is_empty() {
return Err(Error::new(ErrorKind::InvalidType));
}
if !expected.iter().any(|t| t == ur.ur_type()) {
return Err(Error::unexpected_type(expected, ur.ur_type().clone()));
}
map_cbor(
Self::from_untagged_cbor(ur.cbor().clone()),
ErrorKind::CborType,
)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use dcbor::{CBOR, CBORTagged, CBORTaggedDecodable, CBORTaggedEncodable, Tag};
use super::*;
#[derive(Debug, Clone, PartialEq, Eq)]
struct NamedNote(String);
impl CBORTagged for NamedNote {
fn cbor_tags() -> Vec<Tag> {
vec![Tag::with_static_name(40_000, "note")]
}
}
impl CBORTaggedEncodable for NamedNote {
fn untagged_cbor(&self) -> CBOR {
self.0.clone().into()
}
}
impl CBORTaggedDecodable for NamedNote {
fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
Ok(Self(cbor.try_into()?))
}
}
impl TryFrom<CBOR> for NamedNote {
type Error = dcbor::Error;
fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
Self::from_tagged_cbor(cbor)
}
}
#[derive(Debug)]
struct UnnamedByte(u8);
impl CBORTagged for UnnamedByte {
fn cbor_tags() -> Vec<Tag> {
vec![Tag::with_value(40_001)]
}
}
impl CBORTaggedEncodable for UnnamedByte {
fn untagged_cbor(&self) -> CBOR {
self.0.into()
}
}
#[derive(Debug)]
struct EmptyTags;
impl CBORTagged for EmptyTags {
fn cbor_tags() -> Vec<Tag> {
Vec::new()
}
}
impl CBORTaggedEncodable for EmptyTags {
fn untagged_cbor(&self) -> CBOR {
0_u8.into()
}
}
#[test]
fn named_tag_roundtrip() {
let note = NamedNote(String::from("hi"));
let ur = note.to_ur().unwrap();
assert_eq!(ur.ur_type().as_str(), "note");
let decoded = NamedNote::from_ur(&ur).unwrap();
assert_eq!(decoded, note);
let via_string = NamedNote::from_ur(&Ur::from_str(&ur.to_string()).unwrap()).unwrap();
assert_eq!(via_string, note);
}
#[test]
fn unnamed_or_empty_tags_are_invalid_type() {
assert_eq!(
UnnamedByte(1).to_ur().unwrap_err().kind(),
ErrorKind::InvalidType
);
assert_eq!(
EmptyTags.to_ur().unwrap_err().kind(),
ErrorKind::InvalidType
);
let ur = Ur::new(crate::ur_type!("bytes"), 1_u8);
assert_eq!(
NamedNote::from_ur(&ur).unwrap_err().kind(),
ErrorKind::UnexpectedType
);
}
#[test]
fn from_ur_rejects_wrong_type() {
let ur = Ur::new(
crate::ur_type!("bytes"),
NamedNote(String::from("x")).untagged_cbor(),
);
assert!(matches!(
NamedNote::from_ur(&ur).unwrap_err(),
ref e if e.kind() == ErrorKind::UnexpectedType
));
}
#[test]
fn from_ur_maps_untagged_type_mismatch_to_cbor_type() {
let ur = Ur::new(crate::ur_type!("note"), 1_u8);
assert!(matches!(
NamedNote::from_ur(&ur).unwrap_err(),
ref e if e.kind() == ErrorKind::CborType
));
}
}