use gazelle_api::{Media, Torrent};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) struct EditionKey {
remaster_title: String,
remaster_record_label: String,
remaster_catalogue_number: String,
media: Media,
}
impl EditionKey {
#[must_use]
pub(crate) fn from_torrent(torrent: &Torrent) -> Self {
Self {
remaster_title: torrent.remaster_title.clone(),
remaster_record_label: torrent.remaster_record_label.clone(),
remaster_catalogue_number: remove_zero_pad(&torrent.remaster_catalogue_number),
media: torrent.media.clone(),
}
}
#[must_use]
pub(crate) fn is_less_specific_than(&self, other: &EditionKey) -> bool {
if self.remaster_title != other.remaster_title || self.media != other.media {
return false;
}
let label = self.remaster_record_label == other.remaster_record_label;
let number = self.remaster_catalogue_number == other.remaster_catalogue_number;
if self.remaster_record_label.is_empty() && number {
return true;
}
if self.remaster_catalogue_number.is_empty() && label {
return true;
}
false
}
#[cfg(test)]
pub(crate) fn mock() -> Self {
Self {
remaster_title: "Test Edition".to_owned(),
remaster_record_label: "Test Label".to_owned(),
remaster_catalogue_number: "TEST-001".to_owned(),
media: Media::CD,
}
}
}
fn remove_zero_pad(input: &str) -> String {
if input.is_empty() {
return String::new();
}
let trimmed = input.trim_start_matches('0');
if trimmed.is_empty() {
"0".to_owned()
} else {
trimmed.to_owned()
}
}
#[cfg(test)]
mod tests {
use super::*;
use gazelle_api::Torrent;
#[test]
fn edition_key_same_torrent_fields() {
let left = Torrent {
remaster_catalogue_number: "1234567".to_owned(),
..Torrent::default()
};
let right = Torrent {
remaster_catalogue_number: "1234567".to_owned(),
..Torrent::default()
};
assert_eq!(
EditionKey::from_torrent(&left),
EditionKey::from_torrent(&right)
);
}
#[test]
fn edition_key_zero_padded_catalogue_numbers_match() {
let left = Torrent {
remaster_catalogue_number: "1234567".to_owned(),
..Torrent::default()
};
let right = Torrent {
remaster_catalogue_number: "01234567".to_owned(),
..Torrent::default()
};
assert_eq!(
EditionKey::from_torrent(&left),
EditionKey::from_torrent(&right)
);
}
#[test]
fn edition_key_different_catalogue_numbers_differ() {
let left = Torrent {
remaster_catalogue_number: "1234567".to_owned(),
..Torrent::default()
};
let right = Torrent {
remaster_catalogue_number: "0999999".to_owned(),
..Torrent::default()
};
assert_ne!(
EditionKey::from_torrent(&left),
EditionKey::from_torrent(&right)
);
}
#[test]
fn edition_key_different_media_differ() {
let left = Torrent {
media: Media::CD,
..Torrent::default()
};
let right = Torrent {
media: Media::WEB,
..Torrent::default()
};
assert_ne!(
EditionKey::from_torrent(&left),
EditionKey::from_torrent(&right)
);
}
#[test]
fn edition_key_different_title_differ() {
let left = Torrent {
remaster_title: "Deluxe".to_owned(),
..Torrent::default()
};
let right = Torrent {
remaster_title: "Standard".to_owned(),
..Torrent::default()
};
assert_ne!(
EditionKey::from_torrent(&left),
EditionKey::from_torrent(&right)
);
}
#[test]
fn edition_key_is_less_specific_than_missing_label() {
let source = EditionKey {
remaster_record_label: String::new(),
..EditionKey::mock()
};
let existing = EditionKey::mock();
assert!(source.is_less_specific_than(&existing));
}
#[test]
fn edition_key_is_less_specific_than_missing_catalogue() {
let source = EditionKey {
remaster_catalogue_number: String::new(),
..EditionKey::mock()
};
let existing = EditionKey::mock();
assert!(source.is_less_specific_than(&existing));
}
#[test]
fn edition_key_is_less_specific_than_source_more_specific() {
let source = EditionKey::mock();
let existing = EditionKey {
remaster_record_label: String::new(),
remaster_catalogue_number: String::new(),
..EditionKey::mock()
};
assert!(!source.is_less_specific_than(&existing));
}
#[test]
fn edition_key_is_less_specific_than_different_values() {
let source = EditionKey::mock();
let existing = EditionKey {
remaster_record_label: "Other Label".to_owned(),
remaster_catalogue_number: "OTHER-001".to_owned(),
..EditionKey::mock()
};
assert!(!source.is_less_specific_than(&existing));
}
#[test]
fn edition_key_is_less_specific_than_cross_field() {
let source = EditionKey {
remaster_catalogue_number: String::new(),
..EditionKey::mock()
};
let existing = EditionKey {
remaster_record_label: String::new(),
..EditionKey::mock()
};
assert!(!source.is_less_specific_than(&existing));
}
#[test]
fn edition_key_is_less_specific_than_exact_match() {
let source = EditionKey::mock();
let existing = EditionKey::mock();
assert!(!source.is_less_specific_than(&existing));
}
#[test]
fn edition_key_is_less_specific_than_different_media() {
let source = EditionKey {
remaster_record_label: String::new(),
..EditionKey::mock()
};
let existing = EditionKey {
media: Media::WEB,
..EditionKey::mock()
};
assert!(!source.is_less_specific_than(&existing));
}
#[test]
fn edition_key_is_less_specific_mismatch() {
let source = EditionKey {
remaster_record_label: String::new(),
remaster_catalogue_number: "TEST-002".to_owned(),
..EditionKey::mock()
};
let existing = EditionKey::mock();
assert!(!source.is_less_specific_than(&existing));
}
#[test]
fn remove_zero_pad_edge_cases() {
assert_eq!(remove_zero_pad("01234"), "1234");
assert_eq!(remove_zero_pad("1234"), "1234");
assert_eq!(remove_zero_pad("001234"), "1234");
assert_eq!(remove_zero_pad("9999990"), "9999990");
assert_eq!(remove_zero_pad("09999990"), "9999990");
assert_eq!(remove_zero_pad("-09999990"), "-09999990");
assert_eq!(remove_zero_pad("000"), "0");
assert_eq!(remove_zero_pad("0"), "0");
assert_eq!(remove_zero_pad("0abc"), "abc");
assert_eq!(remove_zero_pad(""), "");
}
}