use serde::de::DeserializeOwned;
use std::marker::PhantomData;
use crate::config::*;
pub mod config;
mod deserialization;
pub mod entity;
pub mod prelude;
pub mod error;
#[cfg(feature = "rate_limit")]
pub(crate) mod rate_limit;
use crate::entity::api::MusicbrainzResult;
use crate::entity::search::{SearchResult, Searchable};
use deserialization::date_format;
use entity::Browsable;
use entity::BrowseResult;
use entity::Include;
use entity::{CoverartResolution, CoverartResponse, CoverartTarget, CoverartType};
use std::fmt::Write as _;
pub use crate::error::Error;
#[derive(Clone, Debug)]
struct Query<T> {
path: String,
include: Vec<Include>,
phantom: PhantomData<T>,
}
#[derive(Clone, Debug)]
pub struct FetchQuery<T>(Query<T>);
#[derive(Clone, Debug)]
struct CoverartQuery<T> {
path: String,
target: CoverartTarget,
phantom: PhantomData<T>,
}
#[derive(Clone, Debug)]
pub struct FetchCoverartQuery<T>(CoverartQuery<T>);
#[derive(Clone, Debug)]
pub struct BrowseQuery<T> {
inner: Query<T>,
offset: Option<u16>,
limit: Option<u8>,
}
#[derive(Clone, Debug)]
pub struct SearchQuery<T> {
inner: Query<T>,
offset: Option<u16>,
limit: Option<u8>,
}
impl<'a, T> FetchQuery<T>
where
T: Clone,
{
pub fn id(&mut self, id: &str) -> &mut Self {
let _ = write!(self.0.path, "/{id}");
self
}
#[cfg(feature = "blocking")]
pub fn execute(&mut self) -> Result<T, Error>
where
T: Fetch<'a> + DeserializeOwned,
{
use entity::api::MusicbrainzResult;
self.0.path.push_str(FMT_JSON);
self.include_to_path();
let request = HTTP_CLIENT.get(&self.0.path);
HTTP_CLIENT
.send_with_retries(request)?
.json::<MusicbrainzResult<T>>()?
.into_result(self.0.path.clone())
}
#[cfg(feature = "async")]
pub async fn execute(&mut self) -> Result<T, Error>
where
T: Fetch<'a> + DeserializeOwned,
{
self.0.path.push_str(FMT_JSON);
self.include_to_path();
let request = HTTP_CLIENT.get(&self.0.path);
HTTP_CLIENT
.send_with_retries(request)
.await?
.json::<MusicbrainzResult<T>>()
.await?
.into_result(self.0.path.clone())
}
fn include_to_path(&mut self) {
self.0.include_to_path()
}
}
impl<'a, T> FetchCoverartQuery<T>
where
T: Clone + FetchCoverart<'a>,
{
pub fn id(&mut self, id: &str) -> &mut Self {
let _ = write!(self.0.path, "/{id}");
self
}
pub fn front(&mut self) -> &mut Self {
if self.0.target.img_type.is_some() {
println!("ignoring call to `front`, since coverart type has already been set");
}
self.0.target.img_type = Some(CoverartType::Front);
self
}
pub fn back(&mut self) -> &mut Self {
if self.0.target.img_type.is_some() {
println!("ignoring call to `back`, since coverart type has already been set");
}
self.0.target.img_type = Some(CoverartType::Back);
self
}
pub fn res_250(&mut self) -> &mut Self {
if self.0.target.img_res.is_some() {
println!("ignoring call to `res_250`, since resolution has already been set");
}
self.0.target.img_res = Some(CoverartResolution::Res250);
self
}
pub fn res_500(&mut self) -> &mut Self {
if self.0.target.img_res.is_some() {
println!("ignoring call to `res_500`, since resolution has already been set");
}
self.0.target.img_res = Some(CoverartResolution::Res500);
self
}
pub fn res_1200(&mut self) -> &mut Self {
if self.0.target.img_res.is_some() {
println!("ignoring call to `res_1200`, since resolution has already been set");
}
self.0.target.img_res = Some(CoverartResolution::Res1200);
self
}
pub fn validate(&mut self) {
if let Some(img_type) = &self.0.target.img_type {
let _ = write!(self.0.path, "/{}", img_type.as_str());
if let Some(img_res) = &self.0.target.img_res {
let _ = write!(self.0.path, "-{}", img_res.as_str());
}
} else if self.0.target.img_res.is_some() {
self.front().validate();
}
}
#[cfg(feature = "blocking")]
pub fn execute(&mut self) -> Result<CoverartResponse, Error> {
self.validate();
let request = HTTP_CLIENT.get(&self.0.path);
let response = HTTP_CLIENT.send_with_retries(request)?;
let coverart_response = if self.0.target.img_type.is_some() {
let url = response.url().clone();
CoverartResponse::Url(url.to_string())
} else {
CoverartResponse::Json(response.json()?)
};
Ok(coverart_response)
}
#[cfg(feature = "async")]
pub async fn execute(&mut self) -> Result<CoverartResponse, Error> {
self.validate();
let request = HTTP_CLIENT.get(&self.0.path);
let response = HTTP_CLIENT.send_with_retries(request).await?;
let coverart_response = if self.0.target.img_type.is_some() {
let url = response.url().clone();
CoverartResponse::Url(url.to_string())
} else {
CoverartResponse::Json(response.json().await?)
};
Ok(coverart_response)
}
}
impl<'a, T> BrowseQuery<T>
where
T: Clone,
{
#[cfg(feature = "blocking")]
pub fn execute(&mut self) -> Result<BrowseResult<T>, Error>
where
T: Fetch<'a> + DeserializeOwned + Browsable,
{
self.include_to_path();
let request = HTTP_CLIENT.get(&self.inner.path);
HTTP_CLIENT
.send_with_retries(request)?
.json::<MusicbrainzResult<BrowseResult<T>>>()?
.into_result(self.inner.path.clone())
}
#[cfg(feature = "async")]
pub async fn execute(&mut self) -> Result<BrowseResult<T>, Error>
where
T: Fetch<'a> + DeserializeOwned + Browsable,
{
self.include_to_path();
let request = HTTP_CLIENT.get(&self.inner.path);
HTTP_CLIENT
.send_with_retries(request)
.await?
.json::<MusicbrainzResult<BrowseResult<T>>>()
.await?
.into_result(self.inner.path.clone())
}
fn include_to_path(&mut self) {
self.inner.include_to_path();
if let Some(limit) = self.limit {
self.inner.path.push_str(PARAM_LIMIT);
self.inner.path.push_str(&limit.to_string());
}
if let Some(offset) = self.offset {
self.inner.path.push_str(PARAM_OFFSET);
self.inner.path.push_str(&offset.to_string());
}
}
pub fn limit(&mut self, limit: u8) -> &mut Self {
self.limit = Some(limit);
self
}
pub fn offset(&mut self, offset: u16) -> &mut Self {
self.offset = Some(offset);
self
}
}
impl<'a, T> SearchQuery<T>
where
T: Search<'a> + Clone,
{
#[cfg(feature = "blocking")]
pub fn execute(&mut self) -> Result<SearchResult<T>, Error>
where
T: Search<'a> + DeserializeOwned + Searchable,
{
self.include_to_path();
let request = HTTP_CLIENT.get(&self.inner.path);
HTTP_CLIENT
.send_with_retries(request)?
.json::<MusicbrainzResult<SearchResult<T>>>()?
.into_result(self.inner.path.clone())
}
#[cfg(feature = "async")]
pub async fn execute(&mut self) -> Result<SearchResult<T>, Error>
where
T: Search<'a> + DeserializeOwned + Searchable,
{
self.include_to_path();
let request = HTTP_CLIENT.get(&self.inner.path);
HTTP_CLIENT
.send_with_retries(request)
.await?
.json::<MusicbrainzResult<SearchResult<T>>>()
.await?
.into_result(self.inner.path.clone())
}
fn include_to_path(&mut self) {
self.inner.include_to_path();
if let Some(limit) = self.limit {
self.inner.path.push_str(PARAM_LIMIT);
self.inner.path.push_str(&limit.to_string());
}
if let Some(offset) = self.offset {
self.inner.path.push_str(PARAM_OFFSET);
self.inner.path.push_str(&offset.to_string());
}
}
pub fn limit(&mut self, limit: u8) -> &mut Self {
self.limit = Some(limit);
self
}
pub fn offset(&mut self, offset: u16) -> &mut Self {
self.offset = Some(offset);
self
}
}
impl<T> Query<T> {
fn include(&mut self, include: Include) -> &mut Self {
self.include.push(include);
self
}
fn include_to_path(&mut self) {
if !self.include.is_empty() {
self.path.push_str(PARAM_INC);
}
for inc in self.include.iter() {
self.path.push_str(inc.as_str());
if Some(inc) != self.include.last() {
self.path.push('+');
}
}
}
}
pub trait Path<'a> {
fn path() -> &'static str;
}
pub trait Fetch<'a> {
fn fetch() -> FetchQuery<Self>
where
Self: Sized + Path<'a>,
{
FetchQuery(Query {
path: format!("{}/{}", BASE_URL, Self::path()),
phantom: PhantomData,
include: vec![],
})
}
}
pub trait FetchCoverart<'a> {
fn fetch_coverart() -> FetchCoverartQuery<Self>
where
Self: Sized + Path<'a>,
{
FetchCoverartQuery(CoverartQuery {
path: format!("{}/{}", BASE_COVERART_URL, Self::path()),
phantom: PhantomData,
target: CoverartTarget {
img_type: None,
img_res: None,
},
})
}
fn get_coverart(&self) -> FetchCoverartQuery<Self>
where
Self: Sized + Path<'a>,
Self: Clone,
{
FetchCoverartQuery(CoverartQuery {
path: format!("{}/{}", BASE_COVERART_URL, Self::path()),
phantom: PhantomData,
target: CoverartTarget {
img_type: None,
img_res: None,
},
})
}
}
pub trait Browse<'a> {
fn browse() -> BrowseQuery<Self>
where
Self: Sized + Path<'a>,
{
BrowseQuery {
inner: Query {
path: format!("{}/{}", BASE_URL, Self::path()),
phantom: PhantomData,
include: vec![],
},
limit: None,
offset: None,
}
}
}
pub trait Search<'a> {
fn search(query: String) -> SearchQuery<Self>
where
Self: Sized + Path<'a>,
{
SearchQuery {
inner: Query {
path: format!("{}/{}{}&{}", BASE_URL, Self::path(), FMT_JSON, query),
phantom: PhantomData,
include: vec![],
},
limit: None,
offset: None,
}
}
}