174 lines
5.9 KiB
Rust
174 lines
5.9 KiB
Rust
|
use std::fmt::{Debug, Display};
|
||
|
|
||
|
use url::Url;
|
||
|
use uuid::Uuid;
|
||
|
|
||
|
use crate::core::collection::Error;
|
||
|
|
||
|
/// An object with the [`IMbid`] trait contains a [MusicBrainz
|
||
|
/// Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) (MBID).
|
||
|
pub trait IMbid {
|
||
|
fn mbid(&self) -> &str;
|
||
|
}
|
||
|
|
||
|
/// MusicBrainz reference.
|
||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||
|
pub struct MusicBrainz(Url);
|
||
|
|
||
|
impl MusicBrainz {
|
||
|
pub fn artist_from_str<S: AsRef<str>>(url: S) -> Result<Self, Error> {
|
||
|
Self::artist_from_url(url.as_ref().try_into()?)
|
||
|
}
|
||
|
|
||
|
pub fn album_from_str<S: AsRef<str>>(url: S) -> Result<Self, Error> {
|
||
|
Self::album_from_url(url.as_ref().try_into()?)
|
||
|
}
|
||
|
|
||
|
pub fn artist_from_url(url: Url) -> Result<Self, Error> {
|
||
|
Self::new(url, "artist")
|
||
|
}
|
||
|
|
||
|
pub fn album_from_url(url: Url) -> Result<Self, Error> {
|
||
|
Self::new(url, "release-group")
|
||
|
}
|
||
|
|
||
|
fn new(url: Url, mb_type: &str) -> Result<Self, Error> {
|
||
|
if !url
|
||
|
.domain()
|
||
|
.map(|u| u.ends_with("musicbrainz.org"))
|
||
|
.unwrap_or(false)
|
||
|
{
|
||
|
return Err(Self::invalid_url_error(url, mb_type));
|
||
|
}
|
||
|
|
||
|
// path_segments only returns an empty iterator if the URL cannot-be-a-base. However, if the
|
||
|
// URL cannot-be-a-base then it will fail the check above already as it won't have a domain.
|
||
|
if url.path_segments().and_then(|mut ps| ps.nth(0)).unwrap() != mb_type {
|
||
|
return Err(Self::invalid_url_error(url, mb_type));
|
||
|
}
|
||
|
|
||
|
match url.path_segments().and_then(|mut ps| ps.nth(1)) {
|
||
|
Some(segment) => Uuid::try_parse(segment)?,
|
||
|
None => return Err(Self::invalid_url_error(url, mb_type)),
|
||
|
};
|
||
|
|
||
|
Ok(MusicBrainz(url))
|
||
|
}
|
||
|
|
||
|
fn invalid_url_error<U: Display>(url: U, mb_type: &str) -> Error {
|
||
|
Error::UrlError(format!("invalid {mb_type} MusicBrainz URL: {url}"))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl AsRef<str> for MusicBrainz {
|
||
|
fn as_ref(&self) -> &str {
|
||
|
self.0.as_ref()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl IMbid for MusicBrainz {
|
||
|
fn mbid(&self) -> &str {
|
||
|
// The URL is assumed to have been validated.
|
||
|
self.0.path_segments().and_then(|mut ps| ps.nth(1)).unwrap()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use super::*;
|
||
|
|
||
|
#[test]
|
||
|
fn artist() {
|
||
|
let uuid = "d368baa8-21ca-4759-9731-0b2753071ad8";
|
||
|
let url_str = format!("https://musicbrainz.org/artist/{uuid}");
|
||
|
|
||
|
let mb = MusicBrainz::artist_from_str(&url_str).unwrap();
|
||
|
assert_eq!(url_str, mb.as_ref());
|
||
|
assert_eq!(uuid, mb.mbid());
|
||
|
|
||
|
let url: Url = url_str.as_str().try_into().unwrap();
|
||
|
let mb = MusicBrainz::artist_from_url(url).unwrap();
|
||
|
assert_eq!(url_str, mb.as_ref());
|
||
|
assert_eq!(uuid, mb.mbid());
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn album() {
|
||
|
let uuid = "d368baa8-21ca-4759-9731-0b2753071ad8";
|
||
|
let url_str = format!("https://musicbrainz.org/release-group/{uuid}");
|
||
|
|
||
|
let mb = MusicBrainz::album_from_str(&url_str).unwrap();
|
||
|
assert_eq!(url_str, mb.as_ref());
|
||
|
assert_eq!(uuid, mb.mbid());
|
||
|
|
||
|
let url: Url = url_str.as_str().try_into().unwrap();
|
||
|
let mb = MusicBrainz::album_from_url(url).unwrap();
|
||
|
assert_eq!(url_str, mb.as_ref());
|
||
|
assert_eq!(uuid, mb.mbid());
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn not_a_url() {
|
||
|
let url = "not a url at all";
|
||
|
let expected_error: Error = url::ParseError::RelativeUrlWithoutBase.into();
|
||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||
|
assert_eq!(actual_error, expected_error);
|
||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn invalid_url() {
|
||
|
let url = "https://www.musicbutler.io/artist-page/483340948";
|
||
|
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}"));
|
||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||
|
assert_eq!(actual_error, expected_error);
|
||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn artist_invalid_type() {
|
||
|
let url = "https://musicbrainz.org/release-group/i-am-not-a-uuid";
|
||
|
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}"));
|
||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||
|
assert_eq!(actual_error, expected_error);
|
||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn album_invalid_type() {
|
||
|
let url = "https://musicbrainz.org/artist/i-am-not-a-uuid";
|
||
|
let expected_error =
|
||
|
Error::UrlError(format!("invalid release-group MusicBrainz URL: {url}"));
|
||
|
let actual_error = MusicBrainz::album_from_str(url).unwrap_err();
|
||
|
assert_eq!(actual_error, expected_error);
|
||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn invalid_uuid() {
|
||
|
let url = "https://musicbrainz.org/artist/i-am-not-a-uuid";
|
||
|
let expected_error: Error = Uuid::try_parse("i-am-not-a-uuid").unwrap_err().into();
|
||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||
|
assert_eq!(actual_error, expected_error);
|
||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn missing_type() {
|
||
|
let url = "https://musicbrainz.org";
|
||
|
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}/"));
|
||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||
|
assert_eq!(actual_error, expected_error);
|
||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn missing_uuid() {
|
||
|
let url = "https://musicbrainz.org/artist";
|
||
|
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}"));
|
||
|
let actual_error = MusicBrainz::artist_from_str(url).unwrap_err();
|
||
|
assert_eq!(actual_error, expected_error);
|
||
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
||
|
}
|
||
|
}
|