1use crate::{Auth, Client, GlimeshError, HttpConnectionError, MutationConn, QueryConn};
11use reqwest::{header, RequestBuilder};
12use std::{sync::Arc, time::Duration};
13
14#[derive(Debug)]
15struct Config {
16 user_agent: String,
17 timeout: Duration,
18 api_url: String,
19 auth: Option<Auth>,
20}
21
22impl Default for Config {
23 fn default() -> Self {
24 Config {
25 user_agent: format!("Glimesh Rust / {}", env!("CARGO_PKG_VERSION")),
26 timeout: Duration::from_secs(30),
27 api_url: String::from("https://glimesh.tv/api/graph"),
28 auth: None,
29 }
30 }
31}
32
33#[derive(Debug, Default)]
42pub struct ConnectionBuilder {
43 config: Config,
44 http: Option<reqwest::Client>,
45}
46
47impl ConnectionBuilder {
48 pub fn finish(self) -> Connection {
54 Connection {
55 http: self.http.unwrap_or_else(move || {
56 reqwest::Client::builder()
57 .user_agent(self.config.user_agent)
58 .timeout(self.config.timeout)
59 .build()
60 .expect("failed to create http client")
61 }),
62 auth: self.config.auth.map(Arc::new),
63 api_url: Arc::new(self.config.api_url),
64 }
65 }
66
67 pub fn user_agent(mut self, value: impl Into<String>) -> Self {
73 self.config.user_agent = value.into();
74 self
75 }
76
77 pub fn timeout(mut self, value: Duration) -> Self {
83 self.config.timeout = value;
84 self
85 }
86
87 pub fn api_url(mut self, value: impl Into<String>) -> Self {
92 self.config.api_url = value.into();
93 self
94 }
95
96 pub fn auth(mut self, auth: Auth) -> Self {
98 self.config.auth = Some(auth);
99 self
100 }
101
102 pub fn http_client(mut self, http: reqwest::Client) -> Self {
104 self.http = Some(http);
105 self
106 }
107}
108
109#[derive(Debug, Clone)]
111pub struct Connection {
112 http: reqwest::Client,
113 auth: Option<Arc<Auth>>,
114 api_url: Arc<String>,
115}
116
117impl Connection {
118 pub fn builder() -> ConnectionBuilder {
120 ConnectionBuilder::default()
121 }
122
123 pub fn new(auth: Auth) -> Self {
125 ConnectionBuilder::default().auth(auth).finish()
126 }
127
128 pub fn as_client(&self) -> Client<&Self> {
130 Client::new(self)
131 }
132
133 pub fn to_client(&self) -> HttpClient {
135 Client::new(self.clone())
136 }
137
138 pub fn into_client(self) -> HttpClient {
140 Client::new(self)
141 }
142
143 pub fn clone_with_auth(&self, auth: Auth) -> Self {
145 Self {
146 api_url: self.api_url.clone(),
147 http: self.http.clone(),
148 auth: Some(Arc::new(auth)),
149 }
150 }
151
152 async fn request<Q>(
153 &self,
154 variables: Q::Variables,
155 ) -> Result<Q::ResponseData, HttpConnectionError>
156 where
157 Q: graphql_client::GraphQLQuery,
158 {
159 let req = self
160 .http
161 .post(self.api_url.as_ref())
162 .json(&Q::build_query(variables));
163
164 let res = self
165 .apply_auth(req)
166 .await?
167 .send()
168 .await
169 .map_err(anyhow::Error::from)?;
170
171 if !res.status().is_success() {
172 return Err(HttpConnectionError::BadStatus(res.status().as_u16()));
173 }
174
175 let res: graphql_client::Response<Q::ResponseData> =
176 res.json().await.map_err(anyhow::Error::from)?;
177
178 if let Some(errs) = res.errors {
179 if !errs.is_empty() {
180 return Err(GlimeshError::GraphqlErrors(errs).into());
181 }
182 }
183
184 let data = res.data.ok_or(GlimeshError::NoData)?;
185 Ok(data)
186 }
187
188 async fn apply_auth(&self, req: RequestBuilder) -> Result<RequestBuilder, HttpConnectionError> {
189 match self.auth.as_ref().map(|a| a.as_ref()) {
190 Some(Auth::ClientId(client_id)) => {
191 Ok(req.header(header::AUTHORIZATION, format!("Client-ID {}", client_id)))
192 }
193 Some(Auth::AccessToken(access_token)) => Ok(req.bearer_auth(access_token)),
194 Some(Auth::RefreshableAccessToken(token)) => {
195 let tokens = token.access_token().await?;
196 Ok(req.bearer_auth(tokens.access_token))
197 }
198 Some(Auth::ClientCredentials(client_credentials)) => {
199 let tokens = client_credentials.access_token().await?;
200 Ok(req.bearer_auth(tokens.access_token))
201 }
202 None => Ok(req),
203 }
204 }
205}
206
207#[async_trait]
208impl QueryConn for Connection {
209 type Error = HttpConnectionError;
210
211 async fn query<Q>(&self, variables: Q::Variables) -> Result<Q::ResponseData, Self::Error>
212 where
213 Q: graphql_client::GraphQLQuery,
214 Q::Variables: Send + Sync,
215 {
216 self.request::<Q>(variables).await
217 }
218}
219
220#[async_trait]
221impl MutationConn for Connection {
222 type Error = HttpConnectionError;
223
224 async fn mutate<Q>(&self, variables: Q::Variables) -> Result<Q::ResponseData, Self::Error>
225 where
226 Q: graphql_client::GraphQLQuery,
227 Q::Variables: Send + Sync,
228 {
229 self.request::<Q>(variables).await
230 }
231}
232
233pub type HttpClient = Client<Connection>;