use std::borrow::Cow;
use crate::error::{Id3v2Error, Id3v2ErrorKind, LoftyError, Result};
use crate::tag::item::ItemKey;
use crate::tag::TagType;
#[derive(PartialEq, Clone, Debug, Eq, Hash)]
pub enum FrameId<'a> {
Valid(Cow<'a, str>),
Outdated(Cow<'a, str>),
}
impl<'a> FrameId<'a> {
pub fn new<I>(id: I) -> Result<Self>
where
I: Into<Cow<'a, str>>,
{
Self::new_cow(id.into())
}
pub(super) fn new_cow(id: Cow<'a, str>) -> Result<Self> {
Self::verify_id(&id)?;
match id.len() {
3 => Ok(FrameId::Outdated(id)),
4 => Ok(FrameId::Valid(id)),
_ => Err(Id3v2Error::new(Id3v2ErrorKind::BadFrameId).into()),
}
}
pub fn as_str(&self) -> &str {
match self {
FrameId::Valid(v) | FrameId::Outdated(v) => v,
}
}
pub(super) fn verify_id(id_str: &str) -> Result<()> {
for c in id_str.chars() {
if !c.is_ascii_uppercase() && !c.is_ascii_digit() {
return Err(Id3v2Error::new(Id3v2ErrorKind::BadFrameId).into());
}
}
Ok(())
}
pub fn as_borrowed(&'a self) -> Self {
match self {
Self::Valid(inner) => Self::Valid(Cow::Borrowed(inner)),
Self::Outdated(inner) => Self::Outdated(Cow::Borrowed(inner)),
}
}
pub fn into_owned(self) -> FrameId<'static> {
match self {
Self::Valid(inner) => FrameId::Valid(Cow::Owned(inner.into_owned())),
Self::Outdated(inner) => FrameId::Outdated(Cow::Owned(inner.into_owned())),
}
}
}
impl<'a> TryFrom<&'a ItemKey> for FrameId<'a> {
type Error = LoftyError;
fn try_from(value: &'a ItemKey) -> std::prelude::rust_2015::Result<Self, Self::Error> {
match value {
ItemKey::Unknown(unknown) if unknown.len() == 4 => {
Self::verify_id(unknown)?;
Ok(Self::Valid(Cow::Borrowed(unknown)))
},
k => {
if let Some(mapped) = k.map_key(TagType::Id3v2, false) {
if mapped.len() == 4 {
Self::verify_id(mapped)?;
return Ok(Self::Valid(Cow::Borrowed(mapped)));
}
}
Err(Id3v2Error::new(Id3v2ErrorKind::BadFrameId).into())
},
}
}
}