lofty/id3/v2/frame/header/
mod.rs

1pub(super) mod parse;
2
3use crate::error;
4use crate::error::{Id3v2Error, Id3v2ErrorKind, LoftyError};
5use crate::id3::v2::FrameFlags;
6use crate::prelude::ItemKey;
7use crate::tag::TagType;
8
9use std::borrow::Cow;
10use std::fmt::{Display, Formatter};
11
12/// An ID3v2 frame header
13///
14/// These are rarely constructed by hand. Usually they are created in the background
15/// when making a new [`Frame`](crate::id3::v2::Frame).
16#[derive(Clone, Debug, PartialEq, Eq, Hash)]
17#[allow(missing_docs)]
18pub struct FrameHeader<'a> {
19	pub(crate) id: FrameId<'a>,
20	pub flags: FrameFlags,
21}
22
23impl<'a> FrameHeader<'a> {
24	/// Create a new [`FrameHeader`]
25	///
26	/// NOTE: Once the header is created, the ID becomes immutable.
27	pub const fn new(id: FrameId<'a>, flags: FrameFlags) -> Self {
28		Self { id, flags }
29	}
30
31	/// Get the ID of the frame
32	pub const fn id(&'a self) -> &'a FrameId<'a> {
33		&self.id
34	}
35}
36
37/// An `ID3v2` frame ID
38///
39/// ⚠ WARNING ⚠: Be very careful when constructing this by hand. It is recommended to use [`FrameId::new`].
40#[derive(PartialEq, Clone, Debug, Eq, Hash)]
41pub enum FrameId<'a> {
42	/// A valid `ID3v2.3/4` frame
43	Valid(Cow<'a, str>),
44	/// When an `ID3v2.2` key couldn't be upgraded
45	///
46	/// This **will not** be written. It is up to the user to upgrade and store the key as [`Id3v2Frame::Valid`](Self::Valid).
47	///
48	/// The entire frame is stored as [`ItemValue::Binary`](crate::tag::ItemValue::Binary).
49	Outdated(Cow<'a, str>),
50}
51
52impl<'a> FrameId<'a> {
53	/// Attempts to create a `FrameId` from an ID string
54	///
55	/// NOTE: This will not upgrade IDs.
56	///
57	/// # Errors
58	///
59	/// * `id` contains invalid characters (must be 'A'..='Z' and '0'..='9')
60	/// * `id` is an invalid length (must be 3 or 4)
61	pub fn new<I>(id: I) -> error::Result<Self>
62	where
63		I: Into<Cow<'a, str>>,
64	{
65		Self::new_cow(id.into())
66	}
67
68	// Split from generic, public method to avoid code bloat by monomorphization.
69	pub(in crate::id3::v2::frame) fn new_cow(id: Cow<'a, str>) -> error::Result<Self> {
70		Self::verify_id(&id)?;
71
72		match id.len() {
73			3 => Ok(FrameId::Outdated(id)),
74			4 => Ok(FrameId::Valid(id)),
75			_ => Err(
76				Id3v2Error::new(Id3v2ErrorKind::BadFrameId(id.into_owned().into_bytes())).into(),
77			),
78		}
79	}
80
81	/// Extracts the string from the ID
82	pub fn as_str(&self) -> &str {
83		match self {
84			FrameId::Valid(v) | FrameId::Outdated(v) => v,
85		}
86	}
87
88	pub(in crate::id3::v2::frame) fn verify_id(id_str: &str) -> error::Result<()> {
89		for c in id_str.chars() {
90			if !c.is_ascii_uppercase() && !c.is_ascii_digit() {
91				return Err(Id3v2Error::new(Id3v2ErrorKind::BadFrameId(
92					id_str.as_bytes().to_vec(),
93				))
94				.into());
95			}
96		}
97
98		Ok(())
99	}
100
101	/// Obtains a borrowed instance
102	pub fn as_borrowed(&'a self) -> Self {
103		match self {
104			Self::Valid(inner) => Self::Valid(Cow::Borrowed(inner)),
105			Self::Outdated(inner) => Self::Outdated(Cow::Borrowed(inner)),
106		}
107	}
108
109	/// Obtains an owned instance
110	pub fn into_owned(self) -> FrameId<'static> {
111		match self {
112			Self::Valid(inner) => FrameId::Valid(Cow::Owned(inner.into_owned())),
113			Self::Outdated(inner) => FrameId::Outdated(Cow::Owned(inner.into_owned())),
114		}
115	}
116
117	/// Consumes the [`FrameId`], returning the inner value
118	pub fn into_inner(self) -> Cow<'a, str> {
119		match self {
120			FrameId::Valid(v) | FrameId::Outdated(v) => v,
121		}
122	}
123}
124
125impl Display for FrameId<'_> {
126	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
127		f.write_str(self.as_str())
128	}
129}
130
131impl<'a> Into<Cow<'a, str>> for FrameId<'a> {
132	fn into(self) -> Cow<'a, str> {
133		self.into_inner()
134	}
135}
136
137impl<'a> TryFrom<&'a ItemKey> for FrameId<'a> {
138	type Error = LoftyError;
139
140	fn try_from(value: &'a ItemKey) -> std::prelude::rust_2015::Result<Self, Self::Error> {
141		match value {
142			ItemKey::Unknown(unknown) if unknown.len() == 4 => {
143				Self::verify_id(unknown)?;
144				Ok(Self::Valid(Cow::Borrowed(unknown)))
145			},
146			k => {
147				if let Some(mapped) = k.map_key(TagType::Id3v2, false) {
148					if mapped.len() == 4 {
149						Self::verify_id(mapped)?;
150						return Ok(Self::Valid(Cow::Borrowed(mapped)));
151					}
152				}
153
154				Err(Id3v2Error::new(Id3v2ErrorKind::UnsupportedFrameId(k.clone())).into())
155			},
156		}
157	}
158}