1#![allow(clippy::derive_partial_eq_without_eq)]
62#![allow(clippy::too_many_arguments)]
63#![allow(clippy::nonstandard_macro_braces)]
64#![allow(clippy::large_enum_variant)]
65#![allow(clippy::tabs_in_doc_comments)]
66#![allow(missing_docs)]
67#![cfg_attr(docsrs, feature(doc_cfg))]
68
69pub mod gifs;
70pub mod stickers;
71pub mod types;
72#[doc(hidden)]
73pub mod utils;
74
75pub use reqwest::{header::HeaderMap, StatusCode};
76
77#[derive(Debug)]
78pub struct Response<T> {
79 pub status: reqwest::StatusCode,
80 pub headers: reqwest::header::HeaderMap,
81 pub body: T,
82}
83
84impl<T> Response<T> {
85 pub fn new(status: reqwest::StatusCode, headers: reqwest::header::HeaderMap, body: T) -> Self {
86 Self {
87 status,
88 headers,
89 body,
90 }
91 }
92}
93
94type ClientResult<T> = Result<T, ClientError>;
95
96use thiserror::Error;
97
98#[derive(Debug, Error)]
100pub enum ClientError {
101 #[error(transparent)]
103 FromUtf8Error(#[from] std::string::FromUtf8Error),
104 #[error(transparent)]
106 UrlParserError(#[from] url::ParseError),
107 #[error(transparent)]
109 SerdeJsonError(#[from] serde_json::Error),
110 #[error(transparent)]
112 ReqwestError(#[from] reqwest::Error),
113 #[error(transparent)]
115 InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
116 #[error(transparent)]
118 ReqwestMiddleWareError(#[from] reqwest_middleware::Error),
119 #[error("HTTP Error. Code: {status}, message: {error}")]
121 HttpError {
122 status: http::StatusCode,
123 headers: reqwest::header::HeaderMap,
124 error: String,
125 },
126}
127
128pub const FALLBACK_HOST: &str = "https://api.giphy.com/v1";
129
130mod progenitor_support {
131 use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
132
133 const PATH_SET: &AsciiSet = &CONTROLS
134 .add(b' ')
135 .add(b'"')
136 .add(b'#')
137 .add(b'<')
138 .add(b'>')
139 .add(b'?')
140 .add(b'`')
141 .add(b'{')
142 .add(b'}');
143
144 #[allow(dead_code)]
145 pub(crate) fn encode_path(pc: &str) -> String {
146 utf8_percent_encode(pc, PATH_SET).to_string()
147 }
148}
149
150#[derive(Debug, Default)]
151pub(crate) struct Message {
152 pub body: Option<reqwest::Body>,
153 pub content_type: Option<String>,
154}
155
156use std::env;
157
158#[derive(Debug, Default, Clone)]
159pub struct RootDefaultServer {}
160
161impl RootDefaultServer {
162 pub fn default_url(&self) -> &str {
163 "https://api.giphy.com/v1"
164 }
165}
166
167#[derive(Clone)]
169pub struct Client {
170 host: String,
171 host_override: Option<String>,
172 token: String,
173
174 client: reqwest_middleware::ClientWithMiddleware,
175}
176
177impl Client {
178 pub fn new<T>(token: T) -> Self
184 where
185 T: ToString,
186 {
187 let client = reqwest::Client::builder()
188 .redirect(reqwest::redirect::Policy::none())
189 .build();
190 let retry_policy =
191 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
192 match client {
193 Ok(c) => {
194 let client = reqwest_middleware::ClientBuilder::new(c)
195 .with(reqwest_tracing::TracingMiddleware::default())
197 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
199 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
200 |req: &reqwest::Request| req.try_clone().is_some(),
201 ))
202 .build();
203
204 let host = RootDefaultServer::default().default_url().to_string();
205
206 Client {
207 host,
208 host_override: None,
209 token: token.to_string(),
210
211 client,
212 }
213 }
214 Err(e) => panic!("creating reqwest client failed: {:?}", e),
215 }
216 }
217
218 pub fn with_host_override<H>(&mut self, host: H) -> &mut Self
220 where
221 H: ToString,
222 {
223 self.host_override = Some(host.to_string());
224 self
225 }
226
227 pub fn remove_host_override(&mut self) -> &mut Self {
229 self.host_override = None;
230 self
231 }
232
233 pub fn get_host_override(&self) -> Option<&str> {
234 self.host_override.as_deref()
235 }
236
237 pub(crate) fn url(&self, path: &str, host: Option<&str>) -> String {
238 format!(
239 "{}{}",
240 self.get_host_override()
241 .or(host)
242 .unwrap_or(self.host.as_str()),
243 path
244 )
245 }
246
247 pub fn new_from_env() -> Self {
256 let token = env::var("GIPHY_API_KEY").expect("must set GIPHY_API_KEY");
257
258 Client::new(token)
259 }
260
261 async fn url_and_auth(&self, uri: &str) -> ClientResult<(reqwest::Url, Option<String>)> {
262 let parsed_url = uri.parse::<reqwest::Url>()?;
263 let auth = format!("Bearer {}", self.token);
264 Ok((parsed_url, Some(auth)))
265 }
266
267 async fn request_raw(
268 &self,
269 method: reqwest::Method,
270 uri: &str,
271 message: Message,
272 ) -> ClientResult<reqwest::Response> {
273 let (url, auth) = self.url_and_auth(uri).await?;
274 let instance = <&Client>::clone(&self);
275 let mut req = instance.client.request(method.clone(), url);
276 req = req.header(
278 reqwest::header::ACCEPT,
279 reqwest::header::HeaderValue::from_static("application/json"),
280 );
281
282 if let Some(content_type) = &message.content_type {
283 req = req.header(
284 reqwest::header::CONTENT_TYPE,
285 reqwest::header::HeaderValue::from_str(content_type).unwrap(),
286 );
287 } else {
288 req = req.header(
289 reqwest::header::CONTENT_TYPE,
290 reqwest::header::HeaderValue::from_static("application/json"),
291 );
292 }
293
294 if let Some(auth_str) = auth {
295 req = req.header(http::header::AUTHORIZATION, &*auth_str);
296 }
297 if let Some(body) = message.body {
298 req = req.body(body);
299 }
300 Ok(req.send().await?)
301 }
302
303 async fn request<Out>(
304 &self,
305 method: reqwest::Method,
306 uri: &str,
307 message: Message,
308 ) -> ClientResult<crate::Response<Out>>
309 where
310 Out: serde::de::DeserializeOwned + 'static + Send,
311 {
312 let response = self.request_raw(method, uri, message).await?;
313
314 let status = response.status();
315 let headers = response.headers().clone();
316
317 let response_body = response.bytes().await?;
318
319 if status.is_success() {
320 log::debug!("Received successful response. Read payload.");
321 let parsed_response = if status == http::StatusCode::NO_CONTENT
322 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
323 {
324 serde_json::from_str("null")?
325 } else {
326 serde_json::from_slice::<Out>(&response_body)?
327 };
328 Ok(crate::Response::new(status, headers, parsed_response))
329 } else {
330 let error = if response_body.is_empty() {
331 ClientError::HttpError {
332 status,
333 headers,
334 error: "empty response".into(),
335 }
336 } else {
337 ClientError::HttpError {
338 status,
339 headers,
340 error: String::from_utf8_lossy(&response_body).into(),
341 }
342 };
343
344 Err(error)
345 }
346 }
347
348 async fn request_with_links<Out>(
349 &self,
350 method: http::Method,
351 uri: &str,
352 message: Message,
353 ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Out>)>
354 where
355 Out: serde::de::DeserializeOwned + 'static + Send,
356 {
357 let response = self.request_raw(method, uri, message).await?;
358
359 let status = response.status();
360 let headers = response.headers().clone();
361 let link = response
362 .headers()
363 .get(http::header::LINK)
364 .and_then(|l| l.to_str().ok())
365 .and_then(|l| parse_link_header::parse(l).ok())
366 .as_ref()
367 .and_then(crate::utils::next_link);
368
369 let response_body = response.bytes().await?;
370
371 if status.is_success() {
372 log::debug!("Received successful response. Read payload.");
373
374 let parsed_response = if status == http::StatusCode::NO_CONTENT
375 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
376 {
377 serde_json::from_str("null")?
378 } else {
379 serde_json::from_slice::<Out>(&response_body)?
380 };
381 Ok((link, crate::Response::new(status, headers, parsed_response)))
382 } else {
383 let error = if response_body.is_empty() {
384 ClientError::HttpError {
385 status,
386 headers,
387 error: "empty response".into(),
388 }
389 } else {
390 ClientError::HttpError {
391 status,
392 headers,
393 error: String::from_utf8_lossy(&response_body).into(),
394 }
395 };
396 Err(error)
397 }
398 }
399
400 #[allow(dead_code)]
402 async fn post_form<Out>(
403 &self,
404 uri: &str,
405 form: reqwest::multipart::Form,
406 ) -> ClientResult<crate::Response<Out>>
407 where
408 Out: serde::de::DeserializeOwned + 'static + Send,
409 {
410 let (url, auth) = self.url_and_auth(uri).await?;
411
412 let instance = <&Client>::clone(&self);
413
414 let mut req = instance.client.request(http::Method::POST, url);
415
416 req = req.header(
418 reqwest::header::ACCEPT,
419 reqwest::header::HeaderValue::from_static("application/json"),
420 );
421
422 if let Some(auth_str) = auth {
423 req = req.header(http::header::AUTHORIZATION, &*auth_str);
424 }
425
426 req = req.multipart(form);
427
428 let response = req.send().await?;
429
430 let status = response.status();
431 let headers = response.headers().clone();
432
433 let response_body = response.bytes().await?;
434
435 if status.is_success() {
436 log::debug!("Received successful response. Read payload.");
437 let parsed_response = if status == http::StatusCode::NO_CONTENT
438 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
439 {
440 serde_json::from_str("null")?
441 } else if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<String>() {
442 let s = String::from_utf8(response_body.to_vec())?;
444 serde_json::from_value(serde_json::json!(&s))?
445 } else {
446 serde_json::from_slice::<Out>(&response_body)?
447 };
448 Ok(crate::Response::new(status, headers, parsed_response))
449 } else {
450 let error = if response_body.is_empty() {
451 ClientError::HttpError {
452 status,
453 headers,
454 error: "empty response".into(),
455 }
456 } else {
457 ClientError::HttpError {
458 status,
459 headers,
460 error: String::from_utf8_lossy(&response_body).into(),
461 }
462 };
463
464 Err(error)
465 }
466 }
467
468 #[allow(dead_code)]
470 async fn request_with_accept_mime<Out>(
471 &self,
472 method: reqwest::Method,
473 uri: &str,
474 accept_mime_type: &str,
475 ) -> ClientResult<crate::Response<Out>>
476 where
477 Out: serde::de::DeserializeOwned + 'static + Send,
478 {
479 let (url, auth) = self.url_and_auth(uri).await?;
480
481 let instance = <&Client>::clone(&self);
482
483 let mut req = instance.client.request(method, url);
484
485 req = req.header(
487 reqwest::header::ACCEPT,
488 reqwest::header::HeaderValue::from_str(accept_mime_type)?,
489 );
490
491 if let Some(auth_str) = auth {
492 req = req.header(http::header::AUTHORIZATION, &*auth_str);
493 }
494
495 let response = req.send().await?;
496
497 let status = response.status();
498 let headers = response.headers().clone();
499
500 let response_body = response.bytes().await?;
501
502 if status.is_success() {
503 log::debug!("Received successful response. Read payload.");
504 let parsed_response = if status == http::StatusCode::NO_CONTENT
505 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
506 {
507 serde_json::from_str("null")?
508 } else if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<String>() {
509 let s = String::from_utf8(response_body.to_vec())?;
511 serde_json::from_value(serde_json::json!(&s))?
512 } else {
513 serde_json::from_slice::<Out>(&response_body)?
514 };
515 Ok(crate::Response::new(status, headers, parsed_response))
516 } else {
517 let error = if response_body.is_empty() {
518 ClientError::HttpError {
519 status,
520 headers,
521 error: "empty response".into(),
522 }
523 } else {
524 ClientError::HttpError {
525 status,
526 headers,
527 error: String::from_utf8_lossy(&response_body).into(),
528 }
529 };
530
531 Err(error)
532 }
533 }
534
535 #[allow(dead_code)]
537 async fn request_with_mime<Out>(
538 &self,
539 method: reqwest::Method,
540 uri: &str,
541 content: &[u8],
542 mime_type: &str,
543 ) -> ClientResult<crate::Response<Out>>
544 where
545 Out: serde::de::DeserializeOwned + 'static + Send,
546 {
547 let (url, auth) = self.url_and_auth(uri).await?;
548
549 let instance = <&Client>::clone(&self);
550
551 let mut req = instance.client.request(method, url);
552
553 req = req.header(
555 reqwest::header::ACCEPT,
556 reqwest::header::HeaderValue::from_static("application/json"),
557 );
558 req = req.header(
559 reqwest::header::CONTENT_TYPE,
560 reqwest::header::HeaderValue::from_bytes(mime_type.as_bytes()).unwrap(),
561 );
562 req = req.header(
564 reqwest::header::HeaderName::from_static("x-upload-content-type"),
565 reqwest::header::HeaderValue::from_static("application/octet-stream"),
566 );
567 req = req.header(
568 reqwest::header::HeaderName::from_static("x-upload-content-length"),
569 reqwest::header::HeaderValue::from_bytes(format!("{}", content.len()).as_bytes())
570 .unwrap(),
571 );
572
573 if let Some(auth_str) = auth {
574 req = req.header(http::header::AUTHORIZATION, &*auth_str);
575 }
576
577 if content.len() > 1 {
578 let b = bytes::Bytes::copy_from_slice(content);
579 req = req.body(b);
581 }
582
583 let response = req.send().await?;
584
585 let status = response.status();
586 let headers = response.headers().clone();
587
588 let response_body = response.bytes().await?;
589
590 if status.is_success() {
591 log::debug!("Received successful response. Read payload.");
592 let parsed_response = if status == http::StatusCode::NO_CONTENT
593 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
594 {
595 serde_json::from_str("null")?
596 } else {
597 serde_json::from_slice::<Out>(&response_body)?
598 };
599 Ok(crate::Response::new(status, headers, parsed_response))
600 } else {
601 let error = if response_body.is_empty() {
602 ClientError::HttpError {
603 status,
604 headers,
605 error: "empty response".into(),
606 }
607 } else {
608 ClientError::HttpError {
609 status,
610 headers,
611 error: String::from_utf8_lossy(&response_body).into(),
612 }
613 };
614
615 Err(error)
616 }
617 }
618
619 async fn request_entity<D>(
620 &self,
621 method: http::Method,
622 uri: &str,
623 message: Message,
624 ) -> ClientResult<crate::Response<D>>
625 where
626 D: serde::de::DeserializeOwned + 'static + Send,
627 {
628 let r = self.request(method, uri, message).await?;
629 Ok(r)
630 }
631
632 #[allow(dead_code)]
633 async fn get<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
634 where
635 D: serde::de::DeserializeOwned + 'static + Send,
636 {
637 self.request_entity(http::Method::GET, uri, message).await
638 }
639
640 #[allow(dead_code)]
641 async fn get_all_pages<D>(&self, uri: &str, _message: Message) -> ClientResult<Response<Vec<D>>>
642 where
643 D: serde::de::DeserializeOwned + 'static + Send,
644 {
645 self.unfold(uri).await
647 }
648
649 #[allow(dead_code)]
651 async fn unfold<D>(&self, uri: &str) -> ClientResult<crate::Response<Vec<D>>>
652 where
653 D: serde::de::DeserializeOwned + 'static + Send,
654 {
655 let mut global_items = Vec::new();
656 let (new_link, mut response) = self.get_pages(uri).await?;
657 let mut link = new_link;
658 while !response.body.is_empty() {
659 global_items.append(&mut response.body);
660 if let Some(url) = &link {
662 let url = reqwest::Url::parse(&url.0)?;
663 let (new_link, new_response) = self.get_pages_url(&url).await?;
664 link = new_link;
665 response = new_response;
666 }
667 }
668
669 Ok(Response::new(
670 response.status,
671 response.headers,
672 global_items,
673 ))
674 }
675
676 #[allow(dead_code)]
677 async fn get_pages<D>(
678 &self,
679 uri: &str,
680 ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Vec<D>>)>
681 where
682 D: serde::de::DeserializeOwned + 'static + Send,
683 {
684 self.request_with_links(http::Method::GET, uri, Message::default())
685 .await
686 }
687
688 #[allow(dead_code)]
689 async fn get_pages_url<D>(
690 &self,
691 url: &reqwest::Url,
692 ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Vec<D>>)>
693 where
694 D: serde::de::DeserializeOwned + 'static + Send,
695 {
696 self.request_with_links(http::Method::GET, url.as_str(), Message::default())
697 .await
698 }
699
700 #[allow(dead_code)]
701 async fn post<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
702 where
703 D: serde::de::DeserializeOwned + 'static + Send,
704 {
705 self.request_entity(http::Method::POST, uri, message).await
706 }
707
708 #[allow(dead_code)]
709 async fn patch<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
710 where
711 D: serde::de::DeserializeOwned + 'static + Send,
712 {
713 self.request_entity(http::Method::PATCH, uri, message).await
714 }
715
716 #[allow(dead_code)]
717 async fn put<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
718 where
719 D: serde::de::DeserializeOwned + 'static + Send,
720 {
721 self.request_entity(http::Method::PUT, uri, message).await
722 }
723
724 #[allow(dead_code)]
725 async fn delete<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
726 where
727 D: serde::de::DeserializeOwned + 'static + Send,
728 {
729 self.request_entity(http::Method::DELETE, uri, message)
730 .await
731 }
732
733 pub fn gifs(&self) -> gifs::Gifs {
735 gifs::Gifs::new(self.clone())
736 }
737
738 pub fn stickers(&self) -> stickers::Stickers {
740 stickers::Stickers::new(self.clone())
741 }
742}