pub mod errors;
use std::{convert::TryInto, str::FromStr};
use crate::parse_json;
use super::{ser, HelixRequestBody, HelixRequestError, InnerResponse, Response};
use errors::*;
#[async_trait::async_trait]
pub trait Request: serde::Serialize {
const PATH: &'static str;
#[cfg(feature = "twitch_oauth2")]
const SCOPE: &'static [twitch_oauth2::Scope];
#[cfg(feature = "twitch_oauth2")]
const OPT_SCOPE: &'static [twitch_oauth2::Scope] = &[];
type Response: serde::de::DeserializeOwned + PartialEq;
fn query(&self) -> Result<String, errors::SerializeError> { ser::to_string(&self) }
fn get_uri(&self) -> Result<http::Uri, InvalidUri> {
let query = self.query()?;
let url = crate::TWITCH_HELIX_URL
.join(<Self as Request>::PATH)
.map(|mut u| {
u.set_query(Some(&query));
u
})?;
http::Uri::from_str(url.as_str()).map_err(Into::into)
}
fn get_bare_uri() -> Result<http::Uri, InvalidUri> {
let url = crate::TWITCH_HELIX_URL.join(<Self as Request>::PATH)?;
http::Uri::from_str(url.as_str()).map_err(Into::into)
}
}
pub trait RequestPost: Request {
type Body: HelixRequestBody;
fn create_request(
&self,
body: Self::Body,
token: &str,
client_id: &str,
) -> Result<http::Request<hyper::body::Bytes>, CreateRequestError> {
let uri = self.get_uri()?;
let body = body.try_to_body()?;
let mut bearer =
http::HeaderValue::from_str(&format!("Bearer {}", token)).map_err(|_| {
CreateRequestError::Custom("Could not make token into headervalue".into())
})?;
bearer.set_sensitive(true);
http::Request::builder()
.method(http::Method::POST)
.uri(uri)
.header("Client-ID", client_id)
.header("Content-Type", "application/json")
.header(http::header::AUTHORIZATION, bearer)
.body(body)
.map_err(Into::into)
}
fn parse_response<B: Into<hyper::body::Bytes>>(
request: Option<Self>,
uri: &http::Uri,
response: http::Response<B>,
) -> Result<Response<Self, <Self as Request>::Response>, HelixRequestPostError>
where
Self: Sized,
{
let response: http::Response<hyper::body::Bytes> = response.map(|b| b.into());
let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| {
HelixRequestPostError::Utf8Error(response.body().clone(), e, uri.clone())
})?;
if let Ok(HelixRequestError {
error,
status,
message,
}) = parse_json::<HelixRequestError>(text, false)
{
return Err(HelixRequestPostError::Error {
error,
status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST),
message,
uri: uri.clone(),
body: response.body().clone(),
});
}
<Self as RequestPost>::parse_inner_response(request, uri, text, response.status())
}
fn parse_inner_response(
request: Option<Self>,
uri: &http::Uri,
response: &str,
status: http::StatusCode,
) -> Result<Response<Self, <Self as Request>::Response>, HelixRequestPostError>
where
Self: Sized,
{
let response: InnerResponse<<Self as Request>::Response> = parse_json(response, true)
.map_err(|e| {
HelixRequestPostError::DeserializeError(
response.to_string(),
e,
uri.clone(),
status,
)
})?;
Ok(Response {
data: response.data,
pagination: response.pagination.cursor,
request,
total: response.total,
other: None,
})
}
}
pub trait RequestPatch: Request {
type Body: HelixRequestBody;
fn create_request(
&self,
body: Self::Body,
token: &str,
client_id: &str,
) -> Result<http::Request<hyper::body::Bytes>, CreateRequestError> {
let uri = self.get_uri()?;
let body = body.try_to_body()?;
let mut bearer =
http::HeaderValue::from_str(&format!("Bearer {}", token)).map_err(|_| {
CreateRequestError::Custom("Could not make token into headervalue".into())
})?;
bearer.set_sensitive(true);
http::Request::builder()
.method(http::Method::PATCH)
.uri(uri)
.header("Client-ID", client_id)
.header("Content-Type", "application/json")
.header(http::header::AUTHORIZATION, bearer)
.body(body)
.map_err(Into::into)
}
fn parse_response<B: Into<hyper::body::Bytes>>(
request: Option<Self>,
uri: &http::Uri,
response: http::Response<B>,
) -> Result<Response<Self, <Self as Request>::Response>, HelixRequestPatchError>
where
Self: Sized,
{
let response: http::Response<hyper::body::Bytes> = response.map(|b| b.into());
let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| {
HelixRequestPatchError::Utf8Error(response.body().clone(), e, uri.clone())
})?;
if let Ok(HelixRequestError {
error,
status,
message,
}) = parse_json::<HelixRequestError>(text, false)
{
return Err(HelixRequestPatchError::Error {
error,
status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST),
message,
uri: uri.clone(),
body: response.body().clone(),
});
}
<Self as RequestPatch>::parse_inner_response(request, uri, text, response.status())
}
fn parse_inner_response(
request: Option<Self>,
uri: &http::Uri,
response: &str,
status: http::StatusCode,
) -> Result<Response<Self, <Self as Request>::Response>, HelixRequestPatchError>
where
Self: Sized;
}
pub trait RequestDelete: Request {
fn create_request(
&self,
token: &str,
client_id: &str,
) -> Result<http::Request<hyper::body::Bytes>, CreateRequestError> {
let uri = self.get_uri()?;
let mut bearer =
http::HeaderValue::from_str(&format!("Bearer {}", token)).map_err(|_| {
CreateRequestError::Custom("Could not make token into headervalue".into())
})?;
bearer.set_sensitive(true);
http::Request::builder()
.method(http::Method::DELETE)
.uri(uri)
.header("Client-ID", client_id)
.header("Content-Type", "application/json")
.header(http::header::AUTHORIZATION, bearer)
.body(Vec::with_capacity(0).into())
.map_err(Into::into)
}
fn parse_response<B: Into<hyper::body::Bytes>>(
request: Option<Self>,
uri: &http::Uri,
response: http::Response<B>,
) -> Result<Response<Self, <Self as Request>::Response>, HelixRequestDeleteError>
where
Self: Sized,
{
let response: http::Response<hyper::body::Bytes> = response.map(|b| b.into());
let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| {
HelixRequestDeleteError::Utf8Error(response.body().clone(), e, uri.clone())
})?;
if let Ok(HelixRequestError {
error,
status,
message,
}) = parse_json::<HelixRequestError>(text, false)
{
return Err(HelixRequestDeleteError::Error {
error,
status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST),
message,
uri: uri.clone(),
body: response.body().clone(),
});
}
<Self as RequestDelete>::parse_inner_response(request, uri, text, response.status())
}
fn parse_inner_response(
request: Option<Self>,
uri: &http::Uri,
response: &str,
status: http::StatusCode,
) -> Result<Response<Self, <Self as Request>::Response>, HelixRequestDeleteError>
where
Self: Sized;
}
pub trait RequestPut: Request {
type Body: HelixRequestBody;
fn create_request(
&self,
body: Self::Body,
token: &str,
client_id: &str,
) -> Result<http::Request<hyper::body::Bytes>, CreateRequestError> {
let uri = self.get_uri()?;
let body = body.try_to_body()?;
let mut bearer =
http::HeaderValue::from_str(&format!("Bearer {}", token)).map_err(|_| {
CreateRequestError::Custom("Could not make token into headervalue".into())
})?;
bearer.set_sensitive(true);
http::Request::builder()
.method(http::Method::PUT)
.uri(uri)
.header("Client-ID", client_id)
.header("Content-Type", "application/json")
.header(http::header::AUTHORIZATION, bearer)
.body(body)
.map_err(Into::into)
}
fn parse_response<B: Into<hyper::body::Bytes>>(
request: Option<Self>,
uri: &http::Uri,
response: http::Response<B>,
) -> Result<Response<Self, <Self as Request>::Response>, HelixRequestPutError>
where
Self: Sized,
{
let response: http::Response<hyper::body::Bytes> = response.map(|b| b.into());
let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| {
HelixRequestPutError::Utf8Error(response.body().clone(), e, uri.clone())
})?;
if let Ok(HelixRequestError {
error,
status,
message,
}) = parse_json::<HelixRequestError>(text, false)
{
return Err(HelixRequestPutError::Error {
error,
status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST),
message,
uri: uri.clone(),
body: response.body().clone(),
});
}
<Self as RequestPut>::parse_inner_response(request, uri, text, response.status())
}
fn parse_inner_response(
request: Option<Self>,
uri: &http::Uri,
response: &str,
status: http::StatusCode,
) -> Result<Response<Self, <Self as Request>::Response>, HelixRequestPutError>
where
Self: Sized;
}
pub trait RequestGet: Request {
fn create_request(
&self,
token: &str,
client_id: &str,
) -> Result<http::Request<hyper::body::Bytes>, CreateRequestError> {
let uri = self.get_uri()?;
let mut bearer =
http::HeaderValue::from_str(&format!("Bearer {}", token)).map_err(|_| {
CreateRequestError::Custom("Could not make token into headervalue".into())
})?;
bearer.set_sensitive(true);
http::Request::builder()
.method(http::Method::GET)
.uri(uri)
.header("Client-ID", client_id)
.header("Content-Type", "application/json")
.header(http::header::AUTHORIZATION, bearer)
.body(Vec::with_capacity(0).into())
.map_err(Into::into)
}
fn parse_response<B: Into<hyper::body::Bytes>>(
request: Option<Self>,
uri: &http::Uri,
response: http::Response<B>,
) -> Result<Response<Self, <Self as Request>::Response>, HelixRequestGetError>
where
Self: Sized,
{
let response: http::Response<hyper::body::Bytes> = response.map(|b| b.into());
let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| {
HelixRequestGetError::Utf8Error(response.body().clone(), e, uri.clone())
})?;
if let Ok(HelixRequestError {
error,
status,
message,
}) = parse_json::<HelixRequestError>(text, false)
{
return Err(HelixRequestGetError::Error {
error,
status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST),
message,
uri: uri.clone(),
});
}
<Self as RequestGet>::parse_inner_response(request, uri, text, response.status())
}
fn parse_inner_response(
request: Option<Self>,
uri: &http::Uri,
response: &str,
status: http::StatusCode,
) -> Result<Response<Self, <Self as Request>::Response>, HelixRequestGetError>
where
Self: Sized,
{
let response: InnerResponse<_> = parse_json(response, true).map_err(|e| {
HelixRequestGetError::DeserializeError(response.to_string(), e, uri.clone(), status)
})?;
Ok(Response {
data: response.data,
pagination: response.pagination.cursor,
request,
total: response.total,
other: response.other,
})
}
}