use serde::de::DeserializeOwned;
use serde::Deserialize;
use ureq::{
Agent,
Error::{Status, Transport},
Header, Response,
};
use url::Url;
use crate::{Client, Endpoint, Error, PageIter, Pageable};
const TMDB_BASE_URL: &str = "https://api.themoviedb.org/3/";
#[derive(Deserialize)]
struct TmdbError {
status_message: String,
}
#[derive(Deserialize)]
struct UnprocessableEntityError {
errors: Vec<String>,
}
#[derive(Debug)]
pub struct TmdbBuilder<'a> {
token: String,
base_url: Option<&'a str>,
}
impl<'a> TmdbBuilder<'a> {
fn new<S>(token: S) -> TmdbBuilder<'a>
where
S: Into<String>,
{
TmdbBuilder {
token: token.into(),
base_url: None,
}
}
pub fn base_url(&mut self, base_url: &'a str) -> &mut TmdbBuilder<'a> {
self.base_url = Some(base_url);
self
}
pub fn build(&self) -> Result<Tmdb, Error> {
let base_url = Url::parse(self.base_url.unwrap_or(TMDB_BASE_URL))?;
let auth_header =
Header::new("authorization", &format!("Bearer {}", self.token));
Ok(Tmdb {
base_url,
auth_header,
agent: Agent::new(),
})
}
}
#[derive(Debug)]
pub struct Tmdb {
base_url: Url,
auth_header: Header,
agent: Agent,
}
impl Tmdb {
pub fn new<S>(token: S) -> Tmdb
where
S: Into<String>,
{
TmdbBuilder::new(token).build().unwrap()
}
pub fn builder<'a, S>(token: S) -> TmdbBuilder<'a>
where
S: Into<String>,
{
TmdbBuilder::new(token)
}
fn call<E>(&self, endpoint: &E) -> Result<Response, Error>
where
E: Endpoint,
{
let mut url = self.base_url.join(&endpoint.path())?;
endpoint.parameters().append_to_url(&mut url);
let request = self
.agent
.request_url(endpoint.method().as_str(), &url)
.set(self.auth_header.name(), self.auth_header.value().unwrap());
let response = if let Some(body) = endpoint.body() {
request.send_bytes(&body)
} else {
request.call()
};
match response {
Ok(response) => Ok(response),
Err(Status(422, response)) => {
let error: UnprocessableEntityError = response.into_json()?;
Err(Error::Tmdb {
code: 422,
message: error.errors.join(", "),
})
}
Err(Status(code, response)) => {
let error: TmdbError = response.into_json()?;
Err(Error::Tmdb {
code,
message: error.status_message,
})
}
Err(Transport(transport)) => Err(Error::Transport(transport)),
}
}
}
impl Client for Tmdb {
fn send<E, D>(&self, endpoint: &E) -> Result<D, Error>
where
E: Endpoint,
D: DeserializeOwned,
{
let response = self.call(endpoint)?;
response.into_json::<D>().map_err(Error::Deserialize)
}
fn ignore<E>(&self, endpoint: &E) -> Result<(), Error>
where
E: Endpoint,
{
self.call(endpoint)?;
Ok(())
}
fn page<'a, E, D>(&'a self, endpoint: &'a E) -> PageIter<'a, Self, E, D>
where
E: Pageable,
D: DeserializeOwned,
{
PageIter::new(self, endpoint)
}
}