2024-03-08 23:28:52 +01:00
|
|
|
use std::fmt::{Debug, Display};
|
|
|
|
|
|
|
|
use url::Url;
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
use crate::core::collection::Error;
|
|
|
|
|
|
|
|
/// MusicBrainz reference.
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
2024-03-09 22:52:03 +01:00
|
|
|
pub struct MusicBrainzUrl(Url);
|
|
|
|
|
|
|
|
impl MusicBrainzUrl {
|
|
|
|
pub fn mbid(&self) -> &str {
|
|
|
|
// The URL is assumed to have been validated.
|
|
|
|
self.0.path_segments().and_then(|mut ps| ps.nth(1)).unwrap()
|
|
|
|
}
|
2024-03-08 23:28:52 +01:00
|
|
|
|
|
|
|
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)),
|
|
|
|
};
|
|
|
|
|
2024-03-09 22:52:03 +01:00
|
|
|
Ok(MusicBrainzUrl(url))
|
2024-03-08 23:28:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn invalid_url_error<U: Display>(url: U, mb_type: &str) -> Error {
|
|
|
|
Error::UrlError(format!("invalid {mb_type} MusicBrainz URL: {url}"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-09 22:52:03 +01:00
|
|
|
impl AsRef<str> for MusicBrainzUrl {
|
2024-03-08 23:28:52 +01:00
|
|
|
fn as_ref(&self) -> &str {
|
|
|
|
self.0.as_ref()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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}");
|
|
|
|
|
2024-03-09 22:52:03 +01:00
|
|
|
let mb = MusicBrainzUrl::artist_from_str(&url_str).unwrap();
|
2024-03-08 23:28:52 +01:00
|
|
|
assert_eq!(url_str, mb.as_ref());
|
|
|
|
assert_eq!(uuid, mb.mbid());
|
|
|
|
|
|
|
|
let url: Url = url_str.as_str().try_into().unwrap();
|
2024-03-09 22:52:03 +01:00
|
|
|
let mb = MusicBrainzUrl::artist_from_url(url).unwrap();
|
2024-03-08 23:28:52 +01:00
|
|
|
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}");
|
|
|
|
|
2024-03-09 22:52:03 +01:00
|
|
|
let mb = MusicBrainzUrl::album_from_str(&url_str).unwrap();
|
2024-03-08 23:28:52 +01:00
|
|
|
assert_eq!(url_str, mb.as_ref());
|
|
|
|
assert_eq!(uuid, mb.mbid());
|
|
|
|
|
|
|
|
let url: Url = url_str.as_str().try_into().unwrap();
|
2024-03-09 22:52:03 +01:00
|
|
|
let mb = MusicBrainzUrl::album_from_url(url).unwrap();
|
2024-03-08 23:28:52 +01:00
|
|
|
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();
|
2024-03-09 22:52:03 +01:00
|
|
|
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err();
|
2024-03-08 23:28:52 +01:00
|
|
|
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}"));
|
2024-03-09 22:52:03 +01:00
|
|
|
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err();
|
2024-03-08 23:28:52 +01:00
|
|
|
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}"));
|
2024-03-09 22:52:03 +01:00
|
|
|
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err();
|
2024-03-08 23:28:52 +01:00
|
|
|
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}"));
|
2024-03-09 22:52:03 +01:00
|
|
|
let actual_error = MusicBrainzUrl::album_from_str(url).unwrap_err();
|
2024-03-08 23:28:52 +01:00
|
|
|
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();
|
2024-03-09 22:52:03 +01:00
|
|
|
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err();
|
2024-03-08 23:28:52 +01:00
|
|
|
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}/"));
|
2024-03-09 22:52:03 +01:00
|
|
|
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err();
|
2024-03-08 23:28:52 +01:00
|
|
|
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}"));
|
2024-03-09 22:52:03 +01:00
|
|
|
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err();
|
2024-03-08 23:28:52 +01:00
|
|
|
assert_eq!(actual_error, expected_error);
|
|
|
|
assert_eq!(actual_error.to_string(), expected_error.to_string());
|
|
|
|
}
|
|
|
|
}
|