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::{PaginationParams, SearchParams, VideoSearchParams};
7use crate::PexelsError;
8
9pub struct PexelsClient {
14 api_key: String,
16
17 client: Client,
19
20 base_url: String,
22}
23
24impl PexelsClient {
25 pub fn new<S: Into<String>>(api_key: S) -> Self {
43 let client = Client::builder()
44 .timeout(Duration::from_secs(30))
45 .pool_max_idle_per_host(10)
46 .build()
47 .unwrap_or_default();
48
49 Self {
50 api_key: api_key.into(),
51 client,
52 base_url: "https://api.pexels.com/v1".to_string(),
53 }
54 }
55
56 pub fn with_config<S: Into<String>>(
68 api_key: S,
69 timeout: u64,
70 max_idle_connections: usize,
71 ) -> Self {
72 let client = Client::builder()
73 .timeout(Duration::from_secs(timeout))
74 .pool_max_idle_per_host(max_idle_connections)
75 .build()
76 .unwrap_or_default();
77
78 Self {
79 api_key: api_key.into(),
80 client,
81 base_url: "https://api.pexels.com/v1".to_string(),
82 }
83 }
84
85 pub fn with_base_url<S: Into<String>>(mut self, base_url: S) -> Self {
95 self.base_url = base_url.into();
96 self
97 }
98
99 pub async fn search_photos(
131 &self,
132 query: &str,
133 params: &SearchParams,
134 ) -> Result<PhotosPage, PexelsError> {
135 let mut url = Url::parse(&format!("{}/search", self.base_url))?;
136
137 url.query_pairs_mut().append_pair("query", query);
139
140 for (key, value) in params.to_query_params() {
142 url.query_pairs_mut().append_pair(&key, &value);
143 }
144
145 let response = self.send_request(url).await?;
146
147 match response.status() {
148 StatusCode::OK => {
149 let photos_page: PhotosPage = response.json().await?;
150 Ok(photos_page)
151 }
152 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
153 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
154 status => Err(PexelsError::ApiError(format!(
155 "Search photos failed with status: {status}"
156 ))),
157 }
158 }
159
160 pub async fn curated_photos(
170 &self,
171 params: &PaginationParams,
172 ) -> Result<PhotosPage, PexelsError> {
173 let mut url = Url::parse(&format!("{}/curated", self.base_url))?;
174
175 if let Some(page) = params.page {
177 url.query_pairs_mut().append_pair("page", &page.to_string());
178 }
179
180 if let Some(per_page) = params.per_page {
181 url.query_pairs_mut()
182 .append_pair("per_page", &per_page.to_string());
183 }
184
185 let response = self.send_request(url).await?;
186
187 match response.status() {
188 StatusCode::OK => {
189 let photos_page: PhotosPage = response.json().await?;
190 Ok(photos_page)
191 }
192 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
193 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
194 status => Err(PexelsError::ApiError(format!(
195 "Curated photos failed with status: {status}"
196 ))),
197 }
198 }
199
200 pub async fn get_photo(&self, id: u64) -> Result<Photo, PexelsError> {
210 let url = Url::parse(&format!("{}/photos/{}", self.base_url, id))?;
211
212 let response = self.send_request(url).await?;
213
214 match response.status() {
215 StatusCode::OK => {
216 let photo: Photo = response.json().await?;
217 Ok(photo)
218 }
219 StatusCode::NOT_FOUND => Err(PexelsError::NotFound(format!(
220 "Photo with ID {id} not found"
221 ))),
222 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
223 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
224 status => Err(PexelsError::ApiError(format!(
225 "Get photo failed with status: {status}"
226 ))),
227 }
228 }
229
230 pub async fn search_videos(
241 &self,
242 query: &str,
243 params: &VideoSearchParams,
244 ) -> Result<VideosPage, PexelsError> {
245 let mut url = Url::parse(&format!("{}/videos/search", self.base_url))?;
246
247 url.query_pairs_mut().append_pair("query", query);
249
250 if let Some(page) = params.page {
252 url.query_pairs_mut().append_pair("page", &page.to_string());
253 }
254
255 if let Some(per_page) = params.per_page {
256 url.query_pairs_mut()
257 .append_pair("per_page", &per_page.to_string());
258 }
259
260 if let Some(ref orientation) = params.orientation {
261 url.query_pairs_mut()
262 .append_pair("orientation", orientation.as_str());
263 }
264
265 if let Some(ref size) = params.size {
266 url.query_pairs_mut().append_pair("size", size.as_str());
267 }
268
269 if let Some(ref locale) = params.locale {
270 url.query_pairs_mut().append_pair("locale", locale);
271 }
272
273 let response = self.send_request(url).await?;
274
275 match response.status() {
276 StatusCode::OK => {
277 let videos_page: VideosPage = response.json().await?;
278 Ok(videos_page)
279 }
280 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
281 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
282 status => Err(PexelsError::ApiError(format!(
283 "Search videos failed with status: {status}"
284 ))),
285 }
286 }
287
288 pub async fn popular_videos(
298 &self,
299 params: &PaginationParams,
300 ) -> Result<VideosPage, PexelsError> {
301 let mut url = Url::parse(&format!("{}/videos/popular", self.base_url))?;
302
303 if let Some(page) = params.page {
305 url.query_pairs_mut().append_pair("page", &page.to_string());
306 }
307
308 if let Some(per_page) = params.per_page {
309 url.query_pairs_mut()
310 .append_pair("per_page", &per_page.to_string());
311 }
312
313 let response = self.send_request(url).await?;
314
315 match response.status() {
316 StatusCode::OK => {
317 let videos_page: VideosPage = response.json().await?;
318 Ok(videos_page)
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!(
323 "Popular videos failed with status: {status}"
324 ))),
325 }
326 }
327
328 pub async fn get_video(&self, id: u64) -> Result<Video, PexelsError> {
338 let url = Url::parse(&format!("{}/videos/videos/{}", self.base_url, id))?;
339
340 let response = self.send_request(url).await?;
341
342 match response.status() {
343 StatusCode::OK => {
344 let video: Video = response.json().await?;
345 Ok(video)
346 }
347 StatusCode::NOT_FOUND => Err(PexelsError::NotFound(format!(
348 "Video with ID {id} not found"
349 ))),
350 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
351 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
352 status => Err(PexelsError::ApiError(format!(
353 "Get video failed with status: {status}"
354 ))),
355 }
356 }
357
358 pub async fn get_collections(
368 &self,
369 params: &PaginationParams,
370 ) -> Result<CollectionsPage, PexelsError> {
371 let mut url = Url::parse(&format!("{}/collections", self.base_url))?;
372
373 if let Some(page) = params.page {
375 url.query_pairs_mut().append_pair("page", &page.to_string());
376 }
377
378 if let Some(per_page) = params.per_page {
379 url.query_pairs_mut()
380 .append_pair("per_page", &per_page.to_string());
381 }
382
383 let response = self.send_request(url).await?;
384
385 match response.status() {
386 StatusCode::OK => {
387 let collections_page: CollectionsPage = response.json().await?;
388 Ok(collections_page)
389 }
390 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
391 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
392 status => Err(PexelsError::ApiError(format!(
393 "Get collections failed with status: {status}"
394 ))),
395 }
396 }
397
398 pub async fn get_collection_media(
409 &self,
410 id: &str,
411 params: &PaginationParams,
412 ) -> Result<MediaPage, PexelsError> {
413 let mut url = Url::parse(&format!("{}/collections/{}", self.base_url, id))?;
414
415 if let Some(page) = params.page {
417 url.query_pairs_mut().append_pair("page", &page.to_string());
418 }
419
420 if let Some(per_page) = params.per_page {
421 url.query_pairs_mut()
422 .append_pair("per_page", &per_page.to_string());
423 }
424
425 let response = self.send_request(url).await?;
426
427 match response.status() {
428 StatusCode::OK => {
429 let media_page: MediaPage = response.json().await?;
430 Ok(media_page)
431 }
432 StatusCode::NOT_FOUND => Err(PexelsError::NotFound(format!(
433 "Collection with ID {id} not found"
434 ))),
435 StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
436 StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
437 status => Err(PexelsError::ApiError(format!(
438 "Get collection media failed with status: {status}"
439 ))),
440 }
441 }
442
443 async fn send_request(&self, url: Url) -> Result<reqwest::Response, PexelsError> {
453 let response = self
454 .client
455 .get(url)
456 .header(header::AUTHORIZATION, &self.api_key)
457 .send()
458 .await?;
459
460 Ok(response)
461 }
462}