lib.rs (4376B)
1 use std::fmt::Debug; 2 3 use bytes::Bytes; 4 use reqwest::{ 5 Client, StatusCode, 6 header::{self, HeaderMap, HeaderValue}, 7 }; 8 9 pub use error::Error; 10 use error::Result; 11 use model::*; 12 use serde::{Serialize, de::DeserializeOwned}; 13 mod error; 14 pub mod model; 15 16 const USER_AGENT: &str = concat!("fjordgard/", env!("CARGO_PKG_VERSION")); 17 const UNSPLASH_API_HOST: &str = "https://api.unsplash.com/"; 18 19 #[derive(Clone)] 20 pub struct UnsplashClient { 21 client: Client, 22 } 23 24 impl UnsplashClient { 25 pub fn new(api_key: &str) -> Result<Self> { 26 let mut headers = HeaderMap::new(); 27 headers.insert("Accept-Version", HeaderValue::from_static("v1")); 28 29 let mut api_key = HeaderValue::from_str(&format!("Client-ID {api_key}")) 30 .map_err(|_| Error::InvalidAPIKey)?; 31 api_key.set_sensitive(true); 32 headers.insert(header::AUTHORIZATION, api_key); 33 34 let client = Client::builder() 35 .default_headers(headers) 36 .user_agent(USER_AGENT) 37 .build()?; 38 39 Ok(Self { client }) 40 } 41 42 async fn request<Q: Serialize, T: DeserializeOwned + Debug>( 43 &self, 44 route: &str, 45 query: Option<Q>, 46 ) -> Result<(T, HeaderMap)> { 47 let mut req = self.client.get(format!("{UNSPLASH_API_HOST}/{route}")); 48 49 if let Some(ref query) = query { 50 req = req.query(query) 51 }; 52 53 let res = req.send().await?; 54 55 if res.status() == StatusCode::UNAUTHORIZED { 56 return Err(Error::InvalidAPIKey); 57 } 58 59 let headers = res.headers().clone(); 60 let body: UnsplashResponse = res.json().await?; 61 62 match body { 63 UnsplashResponse::Error { errors } => Err(Error::Unsplash(errors.join(", "))), 64 UnsplashResponse::Success(v) => match serde_json::from_value(v) { 65 Ok(o) => Ok((o, headers)), 66 Err(e) => Err(Error::SerdeJson(e)), 67 }, 68 } 69 } 70 71 // Endpoint: `/collections/:id/photos` 72 pub async fn collection_photos( 73 &self, 74 id: &str, 75 opt: Option<CollectionPhotosOptions>, 76 ) -> Result<CollectionPhotos> { 77 let (photos, headers) = self 78 .request(&format!("collections/{id}/photos"), opt) 79 .await?; 80 81 Ok(CollectionPhotos { 82 collection_total: headers 83 .get("X-Total") 84 .ok_or(Error::MissingHeader("X-Total"))? 85 .to_str() 86 .map_err(|_| Error::MalformedResponse)? 87 .parse::<usize>() 88 .map_err(|_| Error::MalformedResponse)?, 89 per_page: headers 90 .get("X-Per-Page") 91 .ok_or(Error::MissingHeader("X-Per-Page"))? 92 .to_str() 93 .map_err(|_| Error::MalformedResponse)? 94 .parse::<usize>() 95 .map_err(|_| Error::MalformedResponse)?, 96 photos, 97 }) 98 } 99 100 pub async fn collection(&self, id: &str) -> Result<Collection> { 101 let (collection, _) = self 102 .request(&format!("collections/{id}"), None::<()>) 103 .await?; 104 105 Ok(collection) 106 } 107 108 pub async fn download_photo( 109 &self, 110 photo: &Photo, 111 opts: Option<PhotoFetchOptions>, 112 ) -> Result<Bytes> { 113 let mut req = self.client.get(&photo.urls.raw); 114 115 if let Some(ref query) = opts { 116 req = req.query(query); 117 } 118 119 Ok(req.send().await?.error_for_status()?.bytes().await?) 120 } 121 } 122 123 #[cfg(test)] 124 mod tests { 125 use std::env; 126 127 use super::*; 128 129 fn api_key() -> String { 130 env::var("UNSPLASH_KEY").expect("expected env:UNSPLASH_KEY") 131 } 132 133 #[tokio::test] 134 async fn collection_photos() { 135 let client = UnsplashClient::new(&api_key()).unwrap(); 136 let collection = client 137 .collection_photos( 138 "1053828", 139 Some(CollectionPhotosOptions { 140 per_page: Some(5), 141 ..Default::default() 142 }), 143 ) 144 .await 145 .unwrap(); 146 147 assert_eq!(collection.per_page, 5); 148 } 149 150 #[tokio::test] 151 async fn collection() { 152 let client = UnsplashClient::new(&api_key()).unwrap(); 153 let collection = client.collection("1053828").await.unwrap(); 154 155 assert_eq!(collection.title, "Tabliss Official"); 156 } 157 }