use crate::{Auth, Client, GlimeshError, HttpConnectionError, MutationConn, QueryConn};
use reqwest::{header, RequestBuilder};
use std::{sync::Arc, time::Duration};
#[derive(Debug)]
struct Config {
user_agent: String,
timeout: Duration,
api_url: String,
auth: Option<Auth>,
}
impl Default for Config {
fn default() -> Self {
Config {
user_agent: format!("Glimesh Rust / {}", env!("CARGO_PKG_VERSION")),
timeout: Duration::from_secs(30),
api_url: String::from("https://glimesh.tv/api/graph"),
auth: None,
}
}
}
#[derive(Debug, Default)]
pub struct ConnectionBuilder {
config: Config,
http: Option<reqwest::Client>,
}
impl ConnectionBuilder {
pub fn finish(self) -> Connection {
Connection {
http: self.http.unwrap_or_else(move || {
reqwest::Client::builder()
.user_agent(self.config.user_agent)
.timeout(self.config.timeout)
.build()
.expect("failed to create http client")
}),
auth: self.config.auth.map(Arc::new),
api_url: Arc::new(self.config.api_url),
}
}
pub fn user_agent(mut self, value: impl Into<String>) -> Self {
self.config.user_agent = value.into();
self
}
pub fn timeout(mut self, value: Duration) -> Self {
self.config.timeout = value;
self
}
pub fn api_url(mut self, value: impl Into<String>) -> Self {
self.config.api_url = value.into();
self
}
pub fn auth(mut self, auth: Auth) -> Self {
self.config.auth = Some(auth);
self
}
pub fn http_client(mut self, http: reqwest::Client) -> Self {
self.http = Some(http);
self
}
}
#[derive(Debug, Clone)]
pub struct Connection {
http: reqwest::Client,
auth: Option<Arc<Auth>>,
api_url: Arc<String>,
}
impl Connection {
pub fn builder() -> ConnectionBuilder {
ConnectionBuilder::default()
}
pub fn new(auth: Auth) -> Self {
ConnectionBuilder::default().auth(auth).finish()
}
pub fn as_client(&self) -> Client<&Self> {
Client::new(self)
}
pub fn to_client(&self) -> HttpClient {
Client::new(self.clone())
}
pub fn into_client(self) -> HttpClient {
Client::new(self)
}
pub fn clone_with_auth(&self, auth: Auth) -> Self {
Self {
api_url: self.api_url.clone(),
http: self.http.clone(),
auth: Some(Arc::new(auth)),
}
}
async fn request<Q>(
&self,
variables: Q::Variables,
) -> Result<Q::ResponseData, HttpConnectionError>
where
Q: graphql_client::GraphQLQuery,
{
let req = self
.http
.post(self.api_url.as_ref())
.json(&Q::build_query(variables));
let res = self
.apply_auth(req)
.await?
.send()
.await
.map_err(anyhow::Error::from)?;
if !res.status().is_success() {
return Err(HttpConnectionError::BadStatus(res.status().as_u16()));
}
let res: graphql_client::Response<Q::ResponseData> =
res.json().await.map_err(anyhow::Error::from)?;
if let Some(errs) = res.errors {
if !errs.is_empty() {
return Err(GlimeshError::GraphqlErrors(errs).into());
}
}
let data = res.data.ok_or(GlimeshError::NoData)?;
Ok(data)
}
async fn apply_auth(&self, req: RequestBuilder) -> Result<RequestBuilder, HttpConnectionError> {
match self.auth.as_ref().map(|a| a.as_ref()) {
Some(Auth::ClientId(client_id)) => {
Ok(req.header(header::AUTHORIZATION, format!("Client-ID {}", client_id)))
}
Some(Auth::AccessToken(access_token)) => Ok(req.bearer_auth(access_token)),
Some(Auth::RefreshableAccessToken(token)) => {
let tokens = token.access_token().await?;
Ok(req.bearer_auth(tokens.access_token))
}
Some(Auth::ClientCredentials(client_credentials)) => {
let tokens = client_credentials.access_token().await?;
Ok(req.bearer_auth(tokens.access_token))
}
None => Ok(req),
}
}
}
#[async_trait]
impl QueryConn for Connection {
type Error = HttpConnectionError;
async fn query<Q>(&self, variables: Q::Variables) -> Result<Q::ResponseData, Self::Error>
where
Q: graphql_client::GraphQLQuery,
Q::Variables: Send + Sync,
{
self.request::<Q>(variables).await
}
}
#[async_trait]
impl MutationConn for Connection {
type Error = HttpConnectionError;
async fn mutate<Q>(&self, variables: Q::Variables) -> Result<Q::ResponseData, Self::Error>
where
Q: graphql_client::GraphQLQuery,
Q::Variables: Send + Sync,
{
self.request::<Q>(variables).await
}
}
pub type HttpClient = Client<Connection>;