1use reqwest::{header, Client, StatusCode};
2use std::time::Duration;
3use url::Url;
4
5use crate::models::{CollectionsPage, MediaPage, Photo, PhotosPage, Video, VideosPage};
6use crate::search::{
7 CollectionMediaParams, PaginationParams, PopularVideoParams, SearchParams, VideoSearchParams,
8};
9use crate::PexelsError;
10
11pub struct PexelsClient {
16 api_key: String,
18
19 client: Client,
21
22 base_url: String,
24}
25
26impl PexelsClient {
27 pub fn new<S: Into<String>>(api_key: S) -> Self {
45 let client = Client::builder()
46 .timeout(Duration::from_secs(30))
47 .pool_max_idle_per_host(10)
48 .build()
49 .unwrap_or_default();
50
51 Self { api_key: api_key.into(), client, base_url: "https://api.pexels.com/v1".to_string() }
52 }
53
54 pub fn with_config<S: Into<String>>(
66 api_key: S,
67 timeout: u64,
68 max_idle_connections: usize,
69 ) -> Self {
70 let client = Client::builder()
71 .timeout(Duration::from_secs(timeout))
72 .pool_max_idle_per_host(max_idle_connections)
73 .build()
74 .unwrap_or_default();
75
76 Self { api_key: api_key.into(), client, base_url: "https://api.pexels.com/v1".to_string() }
77 }
78
79 pub fn with_base_url<S: Into<String>>(mut self, base_url: S) -> Self {
89 self.base_url = base_url.into();
90 self
91 }
92
93 pub async fn search_photos(
123 &self,
124 query: &str,
125 params: &SearchParams,
126 ) -> Result<PhotosPage, PexelsError> {
127 let mut url = Url::parse(&format!("{}/search", self.base_url))?;
128
129 url.query_pairs_mut().append_pair("query", query);
131
132 for (key, value) in params.to_query_params() {
134 url.query_pairs_mut().append_pair(&key, &value);
135 }
136
137 let response = self.send_request(url).await?;
138
139 match response.status() {
140 StatusCode::OK => {
141 let photos_page: PhotosPage = response.json().await?;
142 Ok(photos_page)
143 }
144 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
145 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
146 status => {
147 Err(PexelsError::ApiError(format!("Search photos failed with status: {status}")))
148 }
149 }
150 }
151
152 pub async fn curated_photos(
162 &self,
163 params: &PaginationParams,
164 ) -> Result<PhotosPage, PexelsError> {
165 let mut url = Url::parse(&format!("{}/curated", self.base_url))?;
166
167 self.append_query_params(&mut url, params.to_query_params());
168
169 let response = self.send_request(url).await?;
170
171 match response.status() {
172 StatusCode::OK => {
173 let photos_page: PhotosPage = response.json().await?;
174 Ok(photos_page)
175 }
176 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
177 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
178 status => {
179 Err(PexelsError::ApiError(format!("Curated photos failed with status: {status}")))
180 }
181 }
182 }
183
184 pub async fn get_photo(&self, id: u64) -> Result<Photo, PexelsError> {
194 let url = Url::parse(&format!("{}/photos/{}", self.base_url, id))?;
195
196 let response = self.send_request(url).await?;
197
198 match response.status() {
199 StatusCode::OK => {
200 let photo: Photo = response.json().await?;
201 Ok(photo)
202 }
203 StatusCode::NOT_FOUND => {
204 Err(PexelsError::NotFound(format!("Photo with ID {id} not found")))
205 }
206 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
207 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
208 status => Err(PexelsError::ApiError(format!("Get photo failed with status: {status}"))),
209 }
210 }
211
212 pub async fn search_videos(
223 &self,
224 query: &str,
225 params: &VideoSearchParams,
226 ) -> Result<VideosPage, PexelsError> {
227 let mut url = Url::parse(&format!("{}/videos/search", self.base_url))?;
228
229 url.query_pairs_mut().append_pair("query", query);
231
232 self.append_query_params(&mut url, params.to_query_params());
233
234 let response = self.send_request(url).await?;
235
236 match response.status() {
237 StatusCode::OK => {
238 let videos_page: VideosPage = response.json().await?;
239 Ok(videos_page)
240 }
241 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
242 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
243 status => {
244 Err(PexelsError::ApiError(format!("Search videos failed with status: {status}")))
245 }
246 }
247 }
248
249 pub async fn popular_videos(
259 &self,
260 params: &PaginationParams,
261 ) -> Result<VideosPage, PexelsError> {
262 let params = PopularVideoParams::from_pagination(params);
263 self.popular_videos_with_params(¶ms).await
264 }
265
266 pub async fn popular_videos_with_params(
276 &self,
277 params: &PopularVideoParams,
278 ) -> Result<VideosPage, PexelsError> {
279 let mut url = Url::parse(&format!("{}/videos/popular", self.base_url))?;
280
281 self.append_query_params(&mut url, params.to_query_params());
282
283 let response = self.send_request(url).await?;
284
285 match response.status() {
286 StatusCode::OK => {
287 let videos_page: VideosPage = response.json().await?;
288 Ok(videos_page)
289 }
290 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
291 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
292 status => {
293 Err(PexelsError::ApiError(format!("Popular videos failed with status: {status}")))
294 }
295 }
296 }
297
298 pub async fn get_video(&self, id: u64) -> Result<Video, PexelsError> {
308 let url = Url::parse(&format!("{}/videos/videos/{}", self.base_url, id))?;
309
310 let response = self.send_request(url).await?;
311
312 match response.status() {
313 StatusCode::OK => {
314 let video: Video = response.json().await?;
315 Ok(video)
316 }
317 StatusCode::NOT_FOUND => {
318 Err(PexelsError::NotFound(format!("Video with ID {id} not found")))
319 }
320 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
321 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
322 status => Err(PexelsError::ApiError(format!("Get video failed with status: {status}"))),
323 }
324 }
325
326 pub async fn get_collections(
336 &self,
337 params: &PaginationParams,
338 ) -> Result<CollectionsPage, PexelsError> {
339 let mut url = Url::parse(&format!("{}/collections", self.base_url))?;
340
341 self.append_query_params(&mut url, params.to_query_params());
342
343 let response = self.send_request(url).await?;
344
345 match response.status() {
346 StatusCode::OK => {
347 let collections_page: CollectionsPage = response.json().await?;
348 Ok(collections_page)
349 }
350 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
351 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
352 status => {
353 Err(PexelsError::ApiError(format!("Get collections failed with status: {status}")))
354 }
355 }
356 }
357
358 pub async fn get_featured_collections(
368 &self,
369 params: &PaginationParams,
370 ) -> Result<CollectionsPage, PexelsError> {
371 let mut url = Url::parse(&format!("{}/collections/featured", self.base_url))?;
372
373 self.append_query_params(&mut url, params.to_query_params());
374
375 let response = self.send_request(url).await?;
376
377 match response.status() {
378 StatusCode::OK => {
379 let collections_page: CollectionsPage = response.json().await?;
380 Ok(collections_page)
381 }
382 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
383 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
384 status => Err(PexelsError::ApiError(format!(
385 "Get featured collections failed with status: {status}"
386 ))),
387 }
388 }
389
390 pub async fn get_collection_media(
401 &self,
402 id: &str,
403 params: &PaginationParams,
404 ) -> Result<MediaPage, PexelsError> {
405 let params = CollectionMediaParams::from_pagination(params);
406 self.get_collection_media_with_params(id, ¶ms).await
407 }
408
409 pub async fn get_collection_media_with_params(
420 &self,
421 id: &str,
422 params: &CollectionMediaParams,
423 ) -> Result<MediaPage, PexelsError> {
424 let mut url = Url::parse(&format!("{}/collections/{}", self.base_url, id))?;
425
426 self.append_query_params(&mut url, params.to_query_params());
427
428 let response = self.send_request(url).await?;
429
430 match response.status() {
431 StatusCode::OK => {
432 let media_page: MediaPage = response.json().await?;
433 Ok(media_page)
434 }
435 StatusCode::NOT_FOUND => {
436 Err(PexelsError::NotFound(format!("Collection with ID {id} not found")))
437 }
438 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
439 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
440 status => Err(PexelsError::ApiError(format!(
441 "Get collection media failed with status: {status}"
442 ))),
443 }
444 }
445
446 fn append_query_params(&self, url: &mut Url, params: Vec<(String, String)>) {
447 for (key, value) in params {
448 url.query_pairs_mut().append_pair(&key, &value);
449 }
450 }
451
452 async fn send_request(&self, url: Url) -> Result<reqwest::Response, PexelsError> {
462 let response =
463 self.client.get(url).header(header::AUTHORIZATION, &self.api_key).send().await?;
464
465 Ok(response)
466 }
467}