Commit a9b90269 authored by Leonardo Schwarz's avatar Leonardo Schwarz

Brand new entity API, implement for release entity

parent 3813c011
//! Contains the types and functions to communicate with the MusicBrainz API.
use crate::errors::{Error, ErrorKind};
use crate::entities::{Mbid, Resource};
use crate::entities::{Mbid, ResourceOld, Resource};
use reqwest_mock::Client as MockClient;
use reqwest_mock::GenericClient as HttpClient;
......@@ -92,6 +92,16 @@ pub struct Client {
last_request: Instant,
}
/// A request to be performed on the client.
///
/// Note: You most likely won't have to use it directly, it's public for trait visibility
/// reasons.
#[derive(Clone, Debug)]
pub struct Request {
pub name: String,
pub include: String,
}
impl Client {
/// Create a new `Client` instance.
pub fn new(config: ClientConfig) -> Self {
......@@ -114,9 +124,7 @@ impl Client {
last_request: past_instant(),
}
}
}
impl Client {
/// Waits until we are allowed to make the next request to the MusicBrainz
/// API.
fn wait_if_needed(&mut self) {
......@@ -128,10 +136,27 @@ impl Client {
self.last_request = now;
}
/// Fetch the specified ressource from the server and parse it.
pub fn get_by_mbid<Res>(&mut self, mbid: &Mbid) -> Result<Res, Error>
pub fn get_by_mbid<Res, Resp, Opt>(&mut self, mbid: &Mbid, options: Opt) -> Result<Res, Error>
where
Res: Resource + FromXml,
Res: Resource<Options = Opt, Response = Resp>,
Resp: FromXml,
{
let request = Res::request(&options);
let url = request.get_by_mbid_url(mbid);
let response_body = self.get_body(url.parse()?)?;
let context = crate::util::musicbrainz_context();
let reader = Reader::from_str(response_body.as_str(), Some(&context))?;
check_response_error(&reader)?;
let response = Resp::from_xml(&reader)?;
Ok(Res::from_response(response, options))
}
/// Fetch the specified resource from the server and parse it.
pub fn get_by_mbid_old<Res>(&mut self, mbid: &Mbid) -> Result<Res, Error>
where
Res: ResourceOld + FromXml,
{
let url = Res::get_url(mbid);
let response_body = self.get_body(url.parse()?)?;
......@@ -188,6 +213,17 @@ impl Client {
}
}
impl Request {
/// Returns the url where one can get a resource in the valid format for
/// parsing from.
fn get_by_mbid_url(&self, mbid: &Mbid) -> String {
format!(
"https://musicbrainz.org/ws/2/{}/{}?inc={}",
self.name, mbid, self.include
)
}
}
#[cfg(test)]
mod tests {
use super::*;
......
use xpath_reader::{FromXml, Reader};
use crate::entities::Language;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Alias {
type_: String,
sort_name: String,
name: String,
locale: Option<Language>,
primary: bool,
}
impl FromXml for Alias {
fn from_xml<'d>(reader: &'d Reader<'d>) -> Result<Self, xpath_reader::Error> {
let primary: Option<String> = reader.read(".//@primary")?;
Ok(Alias {
type_: reader.read(".//@type")?,
sort_name: reader.read(".//@sort-name")?,
name: reader.read(".//text()")?,
locale: reader.read(".//@locale")?,
primary: primary == Some("primary".into()),
})
}
}
use xpath_reader::{FromXml, Error, Reader};
use crate::entities::{Mbid, Resource};
use crate::entities::{Mbid, ResourceOld};
enum_mb_xml! {
/// Specifies what a specific `Area` instance actually is.
......@@ -71,7 +71,7 @@ impl FromXml for Area {
}
}
impl Resource for Area {
impl ResourceOld for Area {
const NAME: &'static str = "area";
const INCL: &'static str = "";
}
......@@ -84,7 +84,7 @@ mod tests {
#[test]
fn area_read_xml1() {
let mbid = Mbid::from_str("a1411661-be21-4290-8dc1-50f3d8e3ea67").unwrap();
let area: Area = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let area: Area = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
assert_eq!(area.mbid, mbid);
assert_eq!(area.name, "Honolulu".to_string());
......@@ -96,7 +96,7 @@ mod tests {
#[test]
fn area_read_xml2() {
let mbid = Mbid::from_str("2db42837-c832-3c27-b4a3-08198f75693c").unwrap();
let area: Area = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let area: Area = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
assert_eq!(area.mbid, mbid);
assert_eq!(area.name, "Japan".to_string());
......
use xpath_reader::{FromXml, FromXmlOptional, Error, Reader};
use crate::entities::{Mbid, Resource};
use crate::entities::{Mbid, ResourceOld};
use crate::entities::date::PartialDate;
use crate::entities::refs::AreaRef;
......@@ -109,7 +109,7 @@ impl FromXml for Artist {
}
}
impl Resource for Artist {
impl ResourceOld for Artist {
const NAME: &'static str = "artist";
const INCL: &'static str = "aliases";
}
......@@ -122,7 +122,7 @@ mod tests {
#[test]
fn artist_read_xml1() {
let mbid = Mbid::from_str("90e7c2f9-273b-4d6c-a662-ab2d73ea4b8e").unwrap();
let artist: Artist = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let artist: Artist = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
assert_eq!(artist.mbid, mbid);
assert_eq!(artist.name, "NECRONOMIDOL".to_string());
......@@ -153,7 +153,7 @@ mod tests {
#[test]
fn artist_read_xml2() {
let mbid = Mbid::from_str("650e7db6-b795-4eb5-a702-5ea2fc46c848").unwrap();
let artist: Artist = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let artist: Artist = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
assert_eq!(artist.mbid, mbid);
assert_eq!(artist.name, "Lady Gaga".to_string());
......
......@@ -28,11 +28,7 @@ pub struct FullDate {
impl PartialDate {
pub fn new(year: Option<u16>, month: Option<u8>, day: Option<u8>) -> PartialDate {
PartialDate {
year: year,
month: month,
day: day,
}
PartialDate { year, month, day }
}
pub fn year(&self) -> Option<u16> {
......@@ -79,11 +75,7 @@ impl PartialDate {
impl FullDate {
pub fn new(year: u16, month: u8, day: u8) -> FullDate {
FullDate {
year: year,
month: month,
day: day,
}
FullDate { year, month, day }
}
pub fn year(&self) -> u16 {
......
use xpath_reader::{FromXml, FromXmlOptional, Error, Reader};
use crate::entities::{Mbid, Resource};
use crate::entities::{Mbid, ResourceOld};
use crate::entities::date::PartialDate;
enum_mb_xml_optional! {
......@@ -52,7 +52,7 @@ pub struct Event {
pub annotation: Option<String>,
}
impl Resource for Event {
impl ResourceOld for Event {
const NAME: &'static str = "event";
const INCL: &'static str = "aliases+annotation";
}
......@@ -81,7 +81,7 @@ mod tests {
#[test]
fn read_1() {
let mbid = Mbid::from_str("6e2ab7d5-f340-4c41-99a3-c901733402b4").unwrap();
let event: Event = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let event: Event = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
assert_eq!(event.mbid, mbid);
assert_eq!(event.name, "25. Wave-Gotik-Treffen".to_string());
......@@ -97,7 +97,7 @@ mod tests {
#[test]
fn read_2() {
let mbid = Mbid::from_str("9754f4dd-6fad-49b7-8f30-940c9af6b776").unwrap();
let event: Event = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let event: Event = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
assert_eq!(event.event_type, Some(EventType::Concert));
assert_eq!(event.setlist.unwrap().len(), 225);
......
use xpath_reader::{FromXml, FromXmlOptional, Error, Reader};
use crate::entities::{Mbid, Resource};
use crate::entities::{Mbid, ResourceOld};
use crate::entities::date::PartialDate;
/// A label entity in the MusicBrainz database.
......@@ -56,7 +56,7 @@ pub struct Label {
pub end_date: Option<PartialDate>,
}
impl Resource for Label {
impl ResourceOld for Label {
const NAME: &'static str = "label";
const INCL: &'static str = "aliases";
}
......@@ -120,7 +120,7 @@ mod tests {
#[test]
fn label_read_xml1() {
let mbid = Mbid::from_str("c029628b-6633-439e-bcee-ed02e8a338f7").unwrap();
let label: Label = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let label: Label = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
assert_eq!(label.mbid, mbid);
assert_eq!(label.name, "EMI".to_string());
......@@ -153,7 +153,7 @@ mod tests {
#[test]
fn read_aliases() {
let mbid = Mbid::from_str("168f48c8-057e-4974-9600-aa9956d21e1a").unwrap();
let label: Label = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let label: Label = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
let mut expected = vec![
"Avex Trax Japan".to_string(),
......
......@@ -14,6 +14,7 @@ pub use self::lang::Language;
pub mod refs;
pub use self::refs::{AreaRef, ArtistRef, LabelRef, RecordingRef, ReleaseRef, FetchFull};
mod alias;
mod area;
mod artist;
mod event;
......@@ -27,13 +28,14 @@ mod series;
// mod track
// mod url
// mod work
pub use self::alias::Alias;
pub use self::area::{Area, AreaType};
pub use self::artist::{Artist, ArtistType, Gender};
pub use self::event::{Event, EventType};
pub use self::label::Label;
pub use self::place::{Coordinates, Place, PlaceType};
pub use self::recording::Recording;
pub use self::release::{LabelInfo, Release, ReleaseMedium, ReleaseStatus, ReleaseTrack};
pub use self::release::{LabelInfo, Release, ReleaseMedium, ReleaseStatus, ReleaseTrack, ReleaseOptions};
pub use self::release_group::{ReleaseGroup, ReleaseGroupPrimaryType, ReleaseGroupSecondaryType,
ReleaseGroupType};
pub use self::series::Series;
......@@ -44,6 +46,8 @@ use std::marker::PhantomData;
mod mbid;
pub use self::mbid::Mbid;
use xpath_reader::FromXml;
use crate::client::Request;
/// Represents an instance of an entity from the database.
///
......@@ -66,7 +70,7 @@ pub struct Relationship<E> {
/// We define this trait for the sake of using the `Client` type more
/// efficiently, users of the `musicbrainz` crate shouldn't need to use this
/// type directly.
pub trait Resource {
pub trait ResourceOld {
/// Name of the resource for inclusion in api paths, e.g. `artist`.
const NAME: &'static str;
/// Query string component of includes to be requested by default.
......@@ -84,6 +88,84 @@ pub trait Resource {
}
}
pub trait Resource {
type Options;
type Response: FromXml;
const NAME: &'static str;
fn request(options: &Self::Options) -> Request;
fn from_response(response: Self::Response, options: Self::Options) -> Self;
}
#[derive(Debug)]
pub enum OnRequest<T> {
Some(T),
NotAvailable,
NotRequested,
}
impl<T> OnRequest<T> {
pub(crate) fn from_option(option: Option<T>, requested: bool) -> OnRequest<T> {
match (option, requested) {
(Some(val), _) => OnRequest::Some(val),
(None, true) => OnRequest::NotAvailable,
(None, false) => OnRequest::NotRequested,
}
}
pub fn unwrap(self) -> T {
match self {
OnRequest::Some(val) => val,
OnRequest::NotAvailable => panic!("Value not returned by server."),
OnRequest::NotRequested => panic!("Value not requested by options."),
}
}
}
impl<T: Clone> Clone for OnRequest<T> {
fn clone(&self) -> Self {
match self {
OnRequest::Some(val) => OnRequest::Some(val.clone()),
OnRequest::NotAvailable => OnRequest::NotAvailable,
OnRequest::NotRequested => OnRequest::NotRequested,
}
}
}
impl<T: PartialEq> PartialEq for OnRequest<T> {
fn eq(&self, other: &Self) -> bool {
match self {
OnRequest::Some(val1) => match other {
OnRequest::Some(val2) => val1.eq(val2),
OnRequest::NotRequested | OnRequest::NotAvailable => false,
},
OnRequest::NotRequested => match other {
OnRequest::NotRequested => true,
_ => false,
},
OnRequest::NotAvailable => match other {
OnRequest::NotAvailable => true,
_ => false,
},
}
}
}
impl<T: Eq> Eq for OnRequest<T> {}
/*
impl<T> From<OnRequest<T>> for Option<T> {
fn from(o: OnRequest<T>) -> Option<T> {
match o {
OnRequest::Some(t) => Some(t),
OnRequest::NotAvailable | OnRequest::NotRequested => None,
}
}
}
*/
// TODO pub struct Work {}
// TODO pub struct Url {}
......
use crate::entities::{Mbid, PartialDate, Resource};
use crate::entities::{Mbid, PartialDate, ResourceOld};
use crate::entities::refs::AreaRef;
use xpath_reader::{FromXml, FromXmlOptional, Error, Reader};
......@@ -98,7 +98,7 @@ impl FromXml for Place {
}
}
impl Resource for Place {
impl ResourceOld for Place {
const NAME: &'static str = "place";
const INCL: &'static str = "annotation+aliases";
}
......@@ -111,7 +111,7 @@ mod tests {
#[test]
fn place_read_1() {
let mbid = Mbid::from_str("d1ab65f8-d082-492a-bd70-ce375548dabf").unwrap();
let p: Place = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let p: Place = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
// Check parsed values.
assert_eq!(p.mbid, mbid);
......
use std::time::Duration;
use xpath_reader::{FromXml, Error, Reader};
use crate::entities::{Mbid, Resource};
use crate::entities::{Mbid, ResourceOld};
use crate::entities::refs::ArtistRef;
/// Represents a unique audio that has been used to produce at least one
......@@ -49,7 +49,7 @@ impl FromXml for Recording {
}
}
impl Resource for Recording {
impl ResourceOld for Recording {
const NAME: &'static str = "recording";
const INCL: &'static str = "artists+annotation+isrcs";
}
......@@ -62,7 +62,7 @@ mod tests {
#[test]
fn read_xml1() {
let mbid = Mbid::from_str("fbe3d0b9-3990-4a76-bddb-12f4a0447a2c").unwrap();
let recording: Recording = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let recording: Recording = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
assert_eq!(recording.mbid, mbid);
assert_eq!(
......
......@@ -8,12 +8,19 @@ use xpath_reader::{FromXml, FromXmlOptional, Reader};
use crate::entities::Mbid;
use crate::entities::date::PartialDate;
use crate::entities::release::ReleaseStatus;
use crate::entities::release::{ReleaseStatus, ReleaseOptions};
use crate::client::Client;
use crate::Error;
pub trait FetchFull {
type Full;
type Options;
fn fetch_full(&self, client: &mut Client, options: Self::Options) -> Result<Self::Full, Error>;
}
pub trait FetchFullOld {
type Full;
fn fetch_full(&self, client: &mut Client) -> Result<Self::Full, Error>;
}
......@@ -118,16 +125,35 @@ impl FromXml for ReleaseRef {
macro_rules! ref_fetch_full
{
($($ref:ty, $full:ty);+)
($($ref:ty, $full:ty, $opts:ty);+)
=>
{
$(
impl FetchFull for $ref {
type Full = $full;
type Options = $opts;
fn fetch_full(&self, client: &mut Client, options: $opts) -> Result<Self::Full, Error>
{
client.get_by_mbid(&self.mbid, options)
}
}
)+
}
}
macro_rules! ref_fetch_full_old
{
($($ref:ty, $full:ty);+)
=>
{
$(
impl FetchFullOld for $ref {
type Full = $full;
fn fetch_full(&self, client: &mut Client) -> Result<Self::Full, Error>
{
client.get_by_mbid(&self.mbid)
client.get_by_mbid_old(&self.mbid)
}
}
)+
......@@ -135,9 +161,12 @@ macro_rules! ref_fetch_full
}
ref_fetch_full!(
ReleaseRef, crate::entities::Release, crate::entities::ReleaseOptions
);
ref_fetch_full_old!(
AreaRef, crate::entities::Area;
ArtistRef, crate::entities::Artist;
LabelRef, crate::entities::Label;
RecordingRef, crate::entities::Recording;
ReleaseRef, crate::entities::Release
RecordingRef, crate::entities::Recording
);
This diff is collapsed.
use xpath_reader::{FromXml, FromXmlOptional, Error, Reader};
use crate::entities::{Mbid, Resource};
use crate::entities::{Mbid, ResourceOld};
use crate::entities::refs::{ArtistRef, ReleaseRef};
enum_mb_xml_optional! {
......@@ -78,7 +78,7 @@ pub struct ReleaseGroup {
pub annotation: Option<String>,
}
impl Resource for ReleaseGroup {
impl ResourceOld for ReleaseGroup {
const NAME: &'static str = "release-group";
const INCL: &'static str = "annotation+artists+releases";
}
......@@ -107,7 +107,7 @@ mod tests {
#[test]
fn read_1() {
let mbid = Mbid::from_str("76a4e2c2-bf7a-445e-8081-5a1e291f3b16").unwrap();
let rg: ReleaseGroup = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let rg: ReleaseGroup = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
assert_eq!(rg.mbid, mbid);
assert_eq!(rg.title, "Mixtape".to_string());
......
use crate::entities::{Mbid, PartialDate, Resource};
use crate::entities::{Mbid, PartialDate, ResourceOld};
use crate::entities::refs::AreaRef;
use xpath_reader::{FromXml, Error, Reader};
......@@ -47,7 +47,7 @@ impl FromXml for Series {
}
}
impl Resource for Series {
impl ResourceOld for Series {
const NAME: &'static str = "series";
const INCL: &'static str = "annotation+aliases+work-rels";
}
......@@ -60,7 +60,7 @@ mod tests {
#[test]
fn read_series_1() {
let mbid = Mbid::from_str("d977f7fd-96c9-4e3e-83b5-eb484a9e6582").unwrap();
let series: Series = crate::util::test_utils::fetch_entity(&mbid).unwrap();
let series: Series = crate::util::test_utils::fetch_entity_old(&mbid).unwrap();
assert_eq!(series.mbid, mbid);
assert_eq!(series.series_type, SeriesType::Catalogue);
......
......@@ -14,7 +14,7 @@
//! search entity.
use crate::entities as full_entities;
use crate::entities::Resource;
use crate::entities::ResourceOld;
use crate::errors::Error;
use crate::client::Client;
......@@ -38,7 +38,7 @@ pub trait SearchBuilder {
type Entity: SearchEntity;
/// The full entity a search entity can be expanded into.
type FullEntity: Resource + FromXml;
type FullEntity: ResourceOld + FromXml;
/// Perform the search.
fn search(self) -> SearchResult<Self::Entity>;
......
......@@ -7,12 +7,12 @@
use super::{Client, full_entities};
use crate::Error;
use self::full_entities::refs::*;
use self::full_entities::{Mbid, Resource};
use self::full_entities::{Mbid, ResourceOld};
use xpath_reader::reader::{FromXml, Reader};
pub trait SearchEntity {
/// The full entity that is refered by this search entity.
type FullEntity: Resource + FromXml;
type FullEntity: ResourceOld + FromXml;
/// Fetch the full entity from the API.
fn fetch_full(&self, client: &mut Client) -> Result<Self::FullEntity, Error>;
......@@ -49,13 +49,15 @@ pub struct Release {
// release group refs (TODO)
}
/*
impl SearchEntity for Release {
type FullEntity = full_entities::Release;
fn fetch_full(&self, client: &mut Client) -> Result<Self::FullEntity, Error> {
client.get_by_mbid(&self.mbid)
client.get_by_mbid_old(&self.mbid)
}
}
*/
pub struct ReleaseGroup {
pub mbid: Mbid,
......@@ -68,7 +70,7 @@ impl SearchEntity for ReleaseGroup {
type FullEntity = full_entities::ReleaseGroup;
fn fetch_full(&self, client: &mut Client) -> Result<Self::FullEntity, Error> {
client.get_by_mbid(&self.mbid)
client.get_by_mbid_old(&self.mbid)
}
}
......
......@@ -9,12 +9,27 @@ pub fn musicbrainz_context<'d>() -> Context<'d> {
#[cfg(test)]
pub mod test_utils {
use crate::client::{Client, ClientConfig, ClientWaits};
use crate::entities::{Mbid, Resource};
use crate::entities::{Mbid, ResourceOld, Resource};
use crate::errors::Error;
use reqwest_mock::GenericClient as HttpClient;
use xpath_reader::reader::FromXml;
pub fn fetch_entity<E: Resource + FromXml>(mbid: &Mbid) -> Result<E, Error> {
pub fn fetch_entity<Res, Opt>(mbid: &Mbid, options: Opt) -> Result<Res, Error>
where
Res: Resource<Options = Opt>,
{
let mut client = Client::with_http_client(
ClientConfig {
user_agent: "MusicBrainz-Rust/Testing".to_string(),
max_retries: 5,
waits: ClientWaits::default(),
},
HttpClient::replay_file(format!("replay/test_entities/{}/{}.json", Res::NAME, mbid)),
);
client.get_by_mbid(mbid, options)
}
pub fn fetch_entity_old<E: ResourceOld + FromXml>(mbid: &Mbid) -> Result<E, Error> {
let mut client = Client::with_http_client(
ClientConfig {
user_agent: "MusicBrainz-Rust/Testing".to_string(),
......@@ -23,7 +38,7 @@ pub mod test_utils {
},
HttpClient::replay_file(format!("replay/test_entities/{}/{}.json", E::NAME, mbid)),
);
client.get_by_mbid(mbid)
client.get_by_mbid_old(mbid)
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment