Skip to content

Commit d320371

Browse files
committed
ItemKey: Remove ItemKey::Unknown
`ItemKey::Unknown` has served its purpose, and has had better alternatives for a couple of years now. It's been a source of confusion numerous times, and its existence also requires extra verification logic on conversions. `ItemKey` is finally `Copy` now too 🎉
1 parent d9eb83b commit d320371

File tree

27 files changed

+301
-405
lines changed

27 files changed

+301
-405
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Changed
10+
11+
* **ItemKey**: `ItemKey` is now `Copy`
12+
13+
### Removed
14+
15+
* **ItemKey**: `ItemKey::Unknown`
16+
* `Tag` is now intended for generic metadata editing only, with format-specific items only being available through concrete tag types.
17+
See <https://github.com/Serial-ATA/lofty-rs/issues/521> for the rationale.
18+
919
## [0.22.4] - 2025-04-29
1020

1121
### Changed

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ repository = "https://github.com/Serial-ATA/lofty-rs"
1616
license = "MIT OR Apache-2.0"
1717

1818
[workspace.dependencies]
19-
lofty = { path = "lofty" }
20-
lofty_attr = { path = "lofty_attr" }
21-
ogg_pager = { path = "ogg_pager" }
19+
lofty = { version = "0.22.4", path = "lofty" }
20+
lofty_attr = { version = "0.11.1", path = "lofty_attr" }
21+
ogg_pager = { version = "0.7.0", path = "ogg_pager" }
2222

2323
byteorder = "1.5.0"
2424

examples/tag_reader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fn main() {
3333
// import keys from https://docs.rs/lofty/latest/lofty/tag/enum.ItemKey.html
3434
println!(
3535
"Album Artist: {}",
36-
tag.get_string(&ItemKey::AlbumArtist).unwrap_or("None")
36+
tag.get_string(ItemKey::AlbumArtist).unwrap_or("None")
3737
);
3838

3939
let properties = tagged_file.properties();

lofty/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ byteorder = { workspace = true }
1919
# ID3 compressed frames
2020
flate2 = { version = "1.0.30", optional = true }
2121
# Proc macros
22-
lofty_attr = "0.11.1"
22+
lofty_attr = { workspace = true }
2323
# Debug logging
2424
log = "0.4.22"
2525
# OGG Vorbis/Opus

lofty/src/ape/tag/item.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ pub struct ApeItem {
1717
}
1818

1919
impl ApeItem {
20+
/// Placeholder for conversions
21+
pub(crate) const EMPTY: Self = ApeItem {
22+
read_only: false,
23+
key: String::new(),
24+
value: ItemValue::Text(String::new()),
25+
};
26+
2027
/// Create an [`ApeItem`]
2128
///
2229
/// # Errors
@@ -71,7 +78,7 @@ impl TryFrom<TagItem> for ApeItem {
7178
Self::new(
7279
value
7380
.item_key
74-
.map_key(TagType::Ape, false)
81+
.map_key(TagType::Ape)
7582
.ok_or_else(|| decode_err!(Ape, "Attempted to convert an unsupported item key"))?
7683
.to_string(),
7784
value.item_value,

lofty/src/ape/tag/mod.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -421,22 +421,26 @@ impl SplitTag for ApeTag {
421421

422422
let mut tag = Tag::new(TagType::Ape);
423423

424-
for item in std::mem::take(&mut self.items) {
425-
let item_key = ItemKey::from_key(TagType::Ape, item.key());
424+
self.items.retain_mut(|item| {
425+
let Some(item_key) = ItemKey::from_key(TagType::Ape, item.key()) else {
426+
return true;
427+
};
426428

427429
// The text pairs need some special treatment
428430
match (item_key, item.value()) {
429431
(ItemKey::TrackNumber | ItemKey::TrackTotal, ItemValue::Text(val))
430432
if split_pair(val, &mut tag, ItemKey::TrackNumber, ItemKey::TrackTotal)
431433
.is_some() =>
432434
{
433-
continue; // Item consumed
435+
*item = ApeItem::EMPTY;
436+
false // Item consumed
434437
},
435438
(ItemKey::DiscNumber | ItemKey::DiscTotal, ItemValue::Text(val))
436439
if split_pair(val, &mut tag, ItemKey::DiscNumber, ItemKey::DiscTotal)
437440
.is_some() =>
438441
{
439-
continue; // Item consumed
442+
*item = ApeItem::EMPTY;
443+
false // Item consumed
440444
},
441445
(ItemKey::MovementNumber | ItemKey::MovementTotal, ItemValue::Text(val))
442446
if split_pair(
@@ -447,13 +451,16 @@ impl SplitTag for ApeTag {
447451
)
448452
.is_some() =>
449453
{
450-
continue; // Item consumed
454+
*item = ApeItem::EMPTY;
455+
false // Item consumed
451456
},
452457
(k, _) => {
458+
let item = std::mem::replace(item, ApeItem::EMPTY);
453459
tag.items.push(TagItem::new(k, item.value));
460+
false // Item consumed
454461
},
455462
}
456-
}
463+
});
457464

458465
(SplitTagRemainder(self), tag)
459466
}
@@ -541,22 +548,22 @@ pub(crate) fn tagitems_into_ape(tag: &Tag) -> impl Iterator<Item = ApeItemRef<'_
541548
}
542549

543550
tag.items()
544-
.filter(|item| !NUMBER_PAIR_KEYS.contains(item.key()))
551+
.filter(|item| !NUMBER_PAIR_KEYS.contains(&item.key()))
545552
.filter_map(|i| {
546-
i.key().map_key(TagType::Ape, true).map(|key| ApeItemRef {
553+
i.key().map_key(TagType::Ape).map(|key| ApeItemRef {
547554
read_only: false,
548555
key,
549556
value: (&i.item_value).into(),
550557
})
551558
})
552559
.chain(create_apeitemref_for_number_pair(
553-
tag.get_string(&ItemKey::TrackNumber),
554-
tag.get_string(&ItemKey::TrackTotal),
560+
tag.get_string(ItemKey::TrackNumber),
561+
tag.get_string(ItemKey::TrackTotal),
555562
"Track",
556563
))
557564
.chain(create_apeitemref_for_number_pair(
558-
tag.get_string(&ItemKey::DiscNumber),
559-
tag.get_string(&ItemKey::DiscTotal),
565+
tag.get_string(ItemKey::DiscNumber),
566+
tag.get_string(ItemKey::DiscTotal),
560567
"Disk",
561568
))
562569
}

lofty/src/id3/v1/tag.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -347,23 +347,23 @@ impl From<Id3v1Tag> for Tag {
347347

348348
impl From<Tag> for Id3v1Tag {
349349
fn from(mut input: Tag) -> Self {
350-
let title = input.take_strings(&ItemKey::TrackTitle).next();
351-
let artist = input.take_strings(&ItemKey::TrackArtist).next();
352-
let album = input.take_strings(&ItemKey::AlbumTitle).next();
350+
let title = input.take_strings(ItemKey::TrackTitle).next();
351+
let artist = input.take_strings(ItemKey::TrackArtist).next();
352+
let album = input.take_strings(ItemKey::AlbumTitle).next();
353353
let year = input.year().map(|y| y.to_string());
354-
let comment = input.take_strings(&ItemKey::Comment).next();
354+
let comment = input.take_strings(ItemKey::Comment).next();
355355
Self {
356356
title,
357357
artist,
358358
album,
359359
year,
360360
comment,
361361
track_number: input
362-
.get_string(&ItemKey::TrackNumber)
362+
.get_string(ItemKey::TrackNumber)
363363
.map(|g| g.parse::<u8>().ok())
364364
.and_then(|g| g),
365365
genre: input
366-
.get_string(&ItemKey::Genre)
366+
.get_string(ItemKey::Genre)
367367
.map(|g| {
368368
GENRES
369369
.iter()
@@ -402,17 +402,17 @@ impl<'a> Into<Id3v1TagRef<'a>> for &'a Id3v1Tag {
402402
impl<'a> Into<Id3v1TagRef<'a>> for &'a Tag {
403403
fn into(self) -> Id3v1TagRef<'a> {
404404
Id3v1TagRef {
405-
title: self.get_string(&ItemKey::TrackTitle),
406-
artist: self.get_string(&ItemKey::TrackArtist),
407-
album: self.get_string(&ItemKey::AlbumTitle),
408-
year: self.get_string(&ItemKey::Year),
409-
comment: self.get_string(&ItemKey::Comment),
405+
title: self.get_string(ItemKey::TrackTitle),
406+
artist: self.get_string(ItemKey::TrackArtist),
407+
album: self.get_string(ItemKey::AlbumTitle),
408+
year: self.get_string(ItemKey::Year),
409+
comment: self.get_string(ItemKey::Comment),
410410
track_number: self
411-
.get_string(&ItemKey::TrackNumber)
411+
.get_string(ItemKey::TrackNumber)
412412
.map(|g| g.parse::<u8>().ok())
413413
.and_then(|g| g),
414414
genre: self
415-
.get_string(&ItemKey::Genre)
415+
.get_string(ItemKey::Genre)
416416
.map(|g| {
417417
GENRES
418418
.iter()

lofty/src/id3/v2/frame/conversion.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ impl From<TagItem> for Option<Frame<'static>> {
3535
return frame_from_unknown_item(id, input.item_value).ok();
3636
}
3737

38-
match input.item_key.map_key(TagType::Id3v2, true) {
38+
match input.item_key.map_key(TagType::Id3v2) {
3939
Some(desc) => match input.item_value {
4040
ItemValue::Text(text) => {
4141
value = Frame::UserText(ExtendedTextFrame::new(
@@ -114,11 +114,10 @@ impl<'a> TryFrom<&'a TagItem> for FrameRef<'a> {
114114
},
115115
Err(_) => {
116116
let item_key = tag_item.key();
117-
let Some(desc) = item_key.map_key(TagType::Id3v2, true) else {
118-
return Err(Id3v2Error::new(Id3v2ErrorKind::UnsupportedFrameId(
119-
item_key.clone(),
120-
))
121-
.into());
117+
let Some(desc) = item_key.map_key(TagType::Id3v2) else {
118+
return Err(
119+
Id3v2Error::new(Id3v2ErrorKind::UnsupportedFrameId(item_key)).into(),
120+
);
122121
};
123122

124123
match tag_item.value() {
@@ -129,10 +128,9 @@ impl<'a> TryFrom<&'a TagItem> for FrameRef<'a> {
129128
value = new_user_url_frame(String::from(desc), locator.clone());
130129
},
131130
_ => {
132-
return Err(Id3v2Error::new(Id3v2ErrorKind::UnsupportedFrameId(
133-
item_key.clone(),
134-
))
135-
.into());
131+
return Err(
132+
Id3v2Error::new(Id3v2ErrorKind::UnsupportedFrameId(item_key)).into(),
133+
);
136134
},
137135
}
138136
},

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

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -134,25 +134,17 @@ impl<'a> Into<Cow<'a, str>> for FrameId<'a> {
134134
}
135135
}
136136

137-
impl<'a> TryFrom<&'a ItemKey> for FrameId<'a> {
137+
impl TryFrom<ItemKey> for FrameId<'_> {
138138
type Error = LoftyError;
139139

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-
},
140+
fn try_from(value: ItemKey) -> std::prelude::rust_2015::Result<Self, Self::Error> {
141+
if let Some(mapped) = value.map_key(TagType::Id3v2) {
142+
if mapped.len() == 4 {
143+
Self::verify_id(mapped)?;
144+
return Ok(Self::Valid(Cow::Borrowed(mapped)));
145+
}
156146
}
147+
148+
Err(Id3v2Error::new(Id3v2ErrorKind::UnsupportedFrameId(value)).into())
157149
}
158150
}

0 commit comments

Comments
 (0)