1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
use std::borrow::Cow;

use crate::error::{Id3v2Error, Id3v2ErrorKind, LoftyError, Result};
use crate::tag::item::ItemKey;
use crate::tag::TagType;

/// An `ID3v2` frame ID
#[derive(PartialEq, Clone, Debug, Eq, Hash)]
pub enum FrameId<'a> {
	/// A valid `ID3v2.3/4` frame
	Valid(Cow<'a, str>),
	/// When an `ID3v2.2` key couldn't be upgraded
	///
	/// This **will not** be written. It is up to the user to upgrade and store the key as [`Id3v2Frame::Valid`](Self::Valid).
	///
	/// The entire frame is stored as [`ItemValue::Binary`](crate::ItemValue::Binary).
	Outdated(Cow<'a, str>),
}

impl<'a> FrameId<'a> {
	/// Attempts to create a `FrameId` from an ID string
	///
	/// # Errors
	///
	/// * `id` contains invalid characters (must be 'A'..='Z' and '0'..='9')
	/// * `id` is an invalid length (must be 3 or 4)
	pub fn new<I>(id: I) -> Result<Self>
	where
		I: Into<Cow<'a, str>>,
	{
		Self::new_cow(id.into())
	}

	// Split from generic, public method to avoid code bloat by monomorphization.
	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()),
		}
	}

	/// Extracts the string from the ID
	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(())
	}

	/// Obtains a borrowed instance
	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)),
		}
	}

	/// Obtains an owned instance
	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())
			},
		}
	}
}