pexels_api/lib.rs
1/*!
2The `pexels_api` crate provides an API wrapper for Pexels. It is based on the [Pexels API Documentation](https://www.pexels.com/api/documentation/).
3
4To get the API key, you have to request access from [Request API Access – Pexels](https://www.pexels.com/api/new/).
5
6This library depends on the [serde-json](https://github.com/serde-rs/json) crate to handle the result. Thus, you have to read the documentation [serde_json - Rust](https://docs.serde.rs/serde_json/index.html), especially [serde_json::Value - Rust](https://docs.serde.rs/serde_json/enum.Value.html).
7
8# Setup
9
10Add this line to your `Cargo.toml` file, below `[dependencies]`:
11
12```toml
13pexels_api = "*"
14```
15
16and this to your crate root file, e.g., `main.rs`:
17
18```rust
19use pexels_api;
20```
21
22Done! Now you can use this API wrapper.
23
24# Example
25
26This example shows how to get the list of *mountains* photos.
27
28```rust,no_run
29use dotenvy::dotenv;
30use std::env;
31use pexels_api::{Pexels, SearchBuilder};
32
33#[tokio::main]
34async fn main() {
35 dotenv().ok();
36 let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
37 let pexels_api_client = Pexels::new(api_key);
38 let builder = SearchBuilder::new()
39 .query("mountains")
40 .per_page(15)
41 .page(1);
42 let response = pexels_api_client.search_photos(builder).await.expect("Failed to get photos");
43 println!("{:?}", response);
44}
45```
46
47and you can run it using `cargo run`! It's as simple as that.
48
49# Random photo
50
51If you want to get a random photo, you can use the `curated_photo` function and set `per_page` to 1 and `page` to a random number between 1 and 1000 to get a beautiful random photo. You can do the same with popular searches if you want to get a random photo with a specific topic.
52
53# Image formats
54
55* original - The size of the original image is given with the attribute width and height.
56* large - This image has a maximum width of 940 px and a maximum height of 650 px. It has the aspect ratio of the original image.
57* large2x - This image has a maximum width of 1880 px and a maximum height of 1300 px. It has the aspect ratio of the original image.
58* medium - This image has a height of 350 px and a flexible width. It has the aspect ratio of the original image.
59* small - This image has a height of 130 px and a flexible width. It has the aspect ratio of the original image.
60* portrait - This image has a width of 800 px and a height of 1200 px.
61* landscape - This image has a width of 1200 px and height of 627 px.
62* tiny - This image has a width of 280 px and height of 200 px.
63*/
64
65mod client;
66mod collections;
67mod domain;
68mod download;
69mod models;
70mod photos;
71mod search;
72mod videos;
73
74/// collections module
75pub use collections::featured::Featured;
76pub use collections::featured::FeaturedBuilder;
77pub use collections::items::Collections;
78pub use collections::items::CollectionsBuilder;
79pub use collections::media::Media;
80pub use collections::media::MediaBuilder;
81/// domain module
82pub use domain::models::Collection;
83pub use domain::models::CollectionsResponse;
84pub use domain::models::MediaResponse;
85pub use domain::models::Photo;
86pub use domain::models::PhotoSrc;
87pub use domain::models::PhotosResponse;
88pub use domain::models::User;
89pub use domain::models::Video;
90pub use domain::models::VideoFile;
91pub use domain::models::VideoPicture;
92pub use domain::models::VideoResponse;
93/// photos module
94pub use photos::curated::Curated;
95pub use photos::curated::CuratedBuilder;
96pub use photos::photo::FetchPhoto;
97pub use photos::photo::FetchPhotoBuilder;
98pub use photos::search::Color;
99pub use photos::search::Hex;
100pub use photos::search::Search;
101pub use photos::search::SearchBuilder;
102/// videos module
103pub use videos::popular::Popular;
104pub use videos::popular::PopularBuilder;
105pub use videos::search::Search as VideoSearch;
106pub use videos::search::SearchBuilder as VideoSearchBuilder;
107pub use videos::video::FetchVideo;
108pub use videos::video::FetchVideoBuilder;
109
110pub use client::PexelsClient;
111pub use search::{
112 CollectionMediaParams, PaginationParams, PopularVideoParams, SearchParams, VideoSearchParams,
113};
114
115pub use download::DownloadManager;
116pub use download::ProgressCallback;
117
118/// import crate
119use reqwest::Client;
120use reqwest::Error as ReqwestError;
121use serde_json::Error as JSONError;
122use serde_json::Value;
123use std::env::VarError;
124use std::fmt::Display;
125use std::str::FromStr;
126use thiserror::Error;
127use url::ParseError;
128
129/// Pexels API version
130const PEXELS_VERSION: &str = "v1";
131
132/// Path for videos
133const PEXELS_VIDEO_PATH: &str = "videos";
134
135/// Path for collections
136const PEXELS_COLLECTIONS_PATH: &str = "collections";
137
138/// Pexels API URL
139const PEXELS_API: &str = "https://api.pexels.com";
140
141/// Desired photo orientation.
142/// Supported values: `landscape`, `portrait`, `square`.
143/// Default: `landscape`.
144///
145/// # Example
146/// ```rust
147/// use pexels_api::Orientation;
148/// use std::str::FromStr;
149///
150/// let orientation = Orientation::from_str("landscape").unwrap();
151/// assert_eq!(orientation, Orientation::Landscape);
152/// ```
153#[derive(PartialEq, Debug, Clone)]
154pub enum Orientation {
155 Landscape,
156 Portrait,
157 Square,
158}
159
160impl Orientation {
161 fn as_str(&self) -> &str {
162 match self {
163 Orientation::Landscape => "landscape",
164 Orientation::Portrait => "portrait",
165 Orientation::Square => "square",
166 }
167 }
168}
169
170impl FromStr for Orientation {
171 type Err = PexelsError;
172
173 fn from_str(s: &str) -> Result<Self, Self::Err> {
174 match s.to_lowercase().as_str() {
175 "landscape" => Ok(Orientation::Landscape),
176 "portrait" => Ok(Orientation::Portrait),
177 "square" => Ok(Orientation::Square),
178 _ => Err(PexelsError::ParseMediaSortError),
179 }
180 }
181}
182
183impl Display for Orientation {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 let str = match self {
186 Orientation::Landscape => "landscape".to_string(),
187 Orientation::Portrait => "portrait".to_string(),
188 Orientation::Square => "square".to_string(),
189 };
190 write!(f, "{str}")
191 }
192}
193
194/// Specifies the order of items in the media collection.
195/// Supported values: `asc`, `desc`. Default: `asc`.
196///
197/// # Example
198/// ```rust
199/// use pexels_api::MediaSort;
200/// use std::str::FromStr;
201///
202/// let sort = MediaSort::from_str("asc").unwrap();
203/// assert_eq!(sort, MediaSort::Asc);
204/// ```
205#[derive(PartialEq, Debug, Clone)]
206pub enum MediaSort {
207 Asc,
208 Desc,
209}
210
211impl MediaSort {
212 pub(crate) fn as_str(&self) -> &str {
213 match self {
214 MediaSort::Asc => "asc",
215 MediaSort::Desc => "desc",
216 }
217 }
218}
219
220impl FromStr for MediaSort {
221 type Err = PexelsError;
222
223 fn from_str(s: &str) -> Result<Self, Self::Err> {
224 match s.to_lowercase().as_str() {
225 "asc" => Ok(MediaSort::Asc),
226 "desc" => Ok(MediaSort::Desc),
227 _ => Err(PexelsError::ParseMediaSortError),
228 }
229 }
230}
231
232/// Specifies the type of media to request.
233/// If not provided or invalid, all media types will be returned.
234/// Supported values: `photos`, `videos`.
235///
236/// # Example
237/// ```rust
238/// use pexels_api::MediaType;
239/// use std::str::FromStr;
240///
241/// let media_type = MediaType::from_str("photos");
242/// match media_type {
243/// Ok(mt) => assert_eq!(mt, MediaType::Photo),
244/// Err(e) => eprintln!("Error parsing media type: {:?}", e),
245/// }
246/// ```
247#[derive(PartialEq, Debug, Clone)]
248pub enum MediaType {
249 Photo,
250 Video,
251 Empty,
252}
253
254impl MediaType {
255 pub(crate) fn as_str(&self) -> &str {
256 match self {
257 MediaType::Photo => "photos",
258 MediaType::Video => "videos",
259 MediaType::Empty => "",
260 }
261 }
262}
263
264impl FromStr for MediaType {
265 type Err = PexelsError;
266
267 fn from_str(s: &str) -> Result<Self, Self::Err> {
268 match s.to_lowercase().as_str() {
269 "photo" | "photos" => Ok(MediaType::Photo),
270 "video" | "videos" => Ok(MediaType::Video),
271 "" => Ok(MediaType::Empty),
272 _ => Err(PexelsError::ParseMediaTypeError),
273 }
274 }
275}
276
277/// Specifies the locale for the search query.
278/// Supported values: `en-US`, `pt-BR`, `es-ES`, `ca-ES`, `de-DE`, `it-IT`, `fr-FR`, `sv-SE`, `id-ID`, `pl-PL`, `ja-JP`, `zh-TW`, `zh-CN`, `ko-KR`, `th-TH`, `nl-NL`, `hu-HU`, `vi-VN`, `cs-CZ`, `da-DK`, `fi-FI`, `uk-UA`, `el-GR`, `ro-RO`, `nb-NO`, `sk-SK`, `tr-TR`, `ru-RU`.
279/// Default: `en-US`.
280///
281/// # Example
282/// ```rust
283/// use pexels_api::Locale;
284/// use std::str::FromStr;
285///
286/// let locale = Locale::from_str("en-US").unwrap();
287/// assert_eq!(locale, Locale::en_US);
288/// ```
289#[allow(non_camel_case_types)]
290#[derive(PartialEq, Debug)]
291pub enum Locale {
292 en_US,
293 pt_BR,
294 es_ES,
295 ca_ES,
296 de_DE,
297 it_IT,
298 fr_FR,
299 sv_SE,
300 id_ID,
301 pl_PL,
302 ja_JP,
303 zh_TW,
304 zh_CN,
305 ko_KR,
306 th_TH,
307 nl_NL,
308 hu_HU,
309 vi_VN,
310 cs_CZ,
311 da_DK,
312 fi_FI,
313 uk_UA,
314 el_GR,
315 ro_RO,
316 nb_NO,
317 sk_SK,
318 tr_TR,
319 ru_RU,
320}
321
322impl Locale {
323 fn as_str(&self) -> &str {
324 match self {
325 Locale::en_US => "en-US",
326 Locale::pt_BR => "pt-BR",
327 Locale::es_ES => "es-ES",
328 Locale::ca_ES => "ca-ES",
329 Locale::de_DE => "de-DE",
330 Locale::it_IT => "it-IT",
331 Locale::fr_FR => "fr-FR",
332 Locale::sv_SE => "sv-SE",
333 Locale::id_ID => "id-ID",
334 Locale::pl_PL => "pl-PL",
335 Locale::ja_JP => "ja-JP",
336 Locale::zh_TW => "zh-TW",
337 Locale::zh_CN => "zh-CN",
338 Locale::ko_KR => "ko-KR",
339 Locale::th_TH => "th-TH",
340 Locale::nl_NL => "nl-NL",
341 Locale::hu_HU => "hu-HU",
342 Locale::vi_VN => "vi-VN",
343 Locale::cs_CZ => "cs-CZ",
344 Locale::da_DK => "da-DK",
345 Locale::fi_FI => "fi-FI",
346 Locale::uk_UA => "uk-UA",
347 Locale::el_GR => "el-GR",
348 Locale::ro_RO => "ro-RO",
349 Locale::nb_NO => "nb-NO",
350 Locale::sk_SK => "sk-SK",
351 Locale::tr_TR => "tr-TR",
352 Locale::ru_RU => "-ES",
353 }
354 }
355}
356
357impl FromStr for Locale {
358 type Err = PexelsError;
359
360 fn from_str(s: &str) -> Result<Self, Self::Err> {
361 match s.to_lowercase().as_str() {
362 "en-us" => Ok(Locale::en_US),
363 "pt-br" => Ok(Locale::pt_BR),
364 "es-es" => Ok(Locale::es_ES),
365 "ca-es" => Ok(Locale::ca_ES),
366 "de-de" => Ok(Locale::de_DE),
367 "it-it" => Ok(Locale::it_IT),
368 "fr-fr" => Ok(Locale::fr_FR),
369 "sv-se" => Ok(Locale::sv_SE),
370 "id-id" => Ok(Locale::id_ID),
371 "pl-pl" => Ok(Locale::pl_PL),
372 "ja-jp" => Ok(Locale::ja_JP),
373 "zh-tw" => Ok(Locale::zh_TW),
374 "zh-cn" => Ok(Locale::zh_CN),
375 "ko-kr" => Ok(Locale::ko_KR),
376 "th-th" => Ok(Locale::th_TH),
377 "nl-nl" => Ok(Locale::nl_NL),
378 "hu-hu" => Ok(Locale::hu_HU),
379 "vi-vn" => Ok(Locale::vi_VN),
380 "cs-cz" => Ok(Locale::cs_CZ),
381 "da-dk" => Ok(Locale::da_DK),
382 "fi-fi" => Ok(Locale::fi_FI),
383 "uk-ua" => Ok(Locale::uk_UA),
384 "el-gr" => Ok(Locale::el_GR),
385 "ro-ro" => Ok(Locale::ro_RO),
386 "nb-no" => Ok(Locale::nb_NO),
387 "sk-sk" => Ok(Locale::sk_SK),
388 "tr-tr" => Ok(Locale::tr_TR),
389 "ru-ru" => Ok(Locale::ru_RU),
390 _ => Err(PexelsError::ParseLocaleError),
391 }
392 }
393}
394
395/// Specifies the minimum size for videos or photos.
396/// Supported values: `large`, `medium`, `small`.
397///
398/// # Example
399/// ```rust
400/// use pexels_api::Size;
401/// use std::str::FromStr;
402///
403/// let size = Size::from_str("large").unwrap();
404/// assert_eq!(size, Size::Large);
405/// ```
406#[derive(PartialEq, Debug, Clone)]
407pub enum Size {
408 Large,
409 Medium,
410 Small,
411}
412
413impl Size {
414 fn as_str(&self) -> &str {
415 match self {
416 Size::Large => "large",
417 Size::Medium => "medium",
418 Size::Small => "small",
419 }
420 }
421}
422
423impl FromStr for Size {
424 type Err = PexelsError;
425
426 fn from_str(s: &str) -> Result<Self, Self::Err> {
427 match s.to_lowercase().as_str() {
428 "large" => Ok(Size::Large),
429 "medium" => Ok(Size::Medium),
430 "small" => Ok(Size::Small),
431 _ => Err(PexelsError::ParseSizeError),
432 }
433 }
434}
435
436impl Display for Size {
437 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
438 let str = match self {
439 Size::Large => "large".to_string(),
440 Size::Medium => "medium".to_string(),
441 Size::Small => "small".to_string(),
442 };
443 write!(f, "{str}")
444 }
445}
446
447/// Type alias for the result returned by builders.
448pub(crate) type BuilderResult = Result<String, PexelsError>;
449
450/// Errors that can occur while interacting with the Pexels API.
451/// This enum is used as the return type for functions that interact with the API.
452///
453/// # Example
454/// ```rust
455/// use pexels_api::PexelsError;
456/// use std::str::FromStr;
457///
458/// let error = PexelsError::ParseMediaTypeError;
459/// assert_eq!(error.to_string(), "Failed to parse media type: invalid value");
460/// ```
461#[derive(Debug, Error)]
462pub enum PexelsError {
463 #[error("Failed to send HTTP request: {0}")]
464 RequestError(#[from] ReqwestError),
465 #[error("Failed to parse JSON response: {0}")]
466 JsonParseError(#[from] JSONError),
467 #[error("API key not found in environment variables: {0}")]
468 EnvVarError(#[from] VarError),
469 #[error("API key not found in environment variables")]
470 ApiKeyNotFound,
471 #[error("Failed to parse URL: {0}")]
472 ParseError(#[from] ParseError),
473 #[error("Invalid hex color code: {0}")]
474 HexColorCodeError(String),
475 #[error("Failed to parse media type: invalid value")]
476 ParseMediaTypeError,
477 #[error("Failed to parse media sort: invalid value")]
478 ParseMediaSortError,
479 #[error("Failed to parse orientation: invalid value")]
480 ParseOrientationError,
481 #[error("Failed to parse size: invalid value")]
482 ParseSizeError,
483 #[error("Failed to parse locale: invalid value")]
484 ParseLocaleError,
485 #[error("Download error: {0}")]
486 DownloadError(String),
487 #[error("IO error: {0}")]
488 IoError(#[from] std::io::Error),
489 #[error("API error: {0}")]
490 ApiError(String),
491 #[error("Rate limit exceeded")]
492 RateLimitError,
493 #[error("Authentication error: {0}")]
494 AuthError(String),
495 #[error("Invalid parameter: {0}")]
496 InvalidParameter(String),
497 #[error("Resource not found: {0}")]
498 NotFound(String),
499 #[error("Asynchronous task error")]
500 AsyncError,
501 #[error("Unknown error: {0}")]
502 Unknown(String),
503}
504
505// Manual implementation PartialEq
506impl PartialEq for PexelsError {
507 fn eq(&self, other: &Self) -> bool {
508 match (self, other) {
509 // Compare RequestError
510 (PexelsError::RequestError(e1), PexelsError::RequestError(e2)) => {
511 e1.to_string() == e2.to_string()
512 }
513 // Compare JsonParseError
514 (PexelsError::JsonParseError(e1), PexelsError::JsonParseError(e2)) => {
515 e1.to_string() == e2.to_string()
516 }
517 // Compare ApiKeyNotFound
518 (PexelsError::ApiKeyNotFound, PexelsError::ApiKeyNotFound) => true,
519 // Compare ParseError
520 (PexelsError::ParseError(e1), PexelsError::ParseError(e2)) => {
521 e1.to_string() == e2.to_string()
522 }
523 // Compare HexColorCodeError
524 (PexelsError::HexColorCodeError(msg1), PexelsError::HexColorCodeError(msg2)) => {
525 msg1 == msg2
526 }
527 // Other things are not equal
528 _ => false,
529 }
530 }
531}
532
533/// Client for interacting with the Pexels API
534///
535/// # Example
536/// ```rust,no_run
537/// use dotenvy::dotenv;
538/// use pexels_api::Pexels;
539/// use std::env;
540///
541/// #[tokio::main]
542/// async fn main() {
543/// dotenv().ok();
544/// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
545/// let client = Pexels::new(api_key);
546/// }
547/// ```
548///
549/// # Errors
550/// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
551///
552/// # Example
553/// ```rust,no_run
554/// use dotenvy::dotenv;
555/// use pexels_api::Pexels;
556/// use pexels_api::SearchBuilder;
557/// use std::env;
558///
559/// #[tokio::main]
560/// async fn main() {
561/// dotenv().ok();
562/// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
563/// let client = Pexels::new(api_key);
564/// let response = client.search_photos(SearchBuilder::new().query("mountains").per_page(15).page(1)).await.expect("Failed to get photos");
565/// println!("{:?}", response);
566/// }
567/// ```
568pub struct Pexels {
569 client: Client,
570 api_key: String,
571}
572
573impl Pexels {
574 /// Create a new Pexels client.
575 ///
576 /// # Arguments
577 /// * `api_key` - The API key for the Pexels API.
578 ///
579 /// # Example
580 /// ```rust,no_run
581 /// use dotenvy::dotenv;
582 /// use pexels_api::Pexels;
583 /// use std::env;
584 ///
585 /// #[tokio::main]
586 /// async fn main() {
587 /// dotenv().ok();
588 /// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
589 /// let client = Pexels::new(api_key);
590 /// }
591 /// ```
592 pub fn new(api_key: String) -> Self {
593 Pexels { client: Client::new(), api_key }
594 }
595
596 /// Sends an HTTP GET request to the specified URL and returns the JSON response.
597 /// Uses the `reqwest` crate for making HTTP requests.
598 ///
599 /// # Errors
600 /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
601 async fn make_request(&self, url: &str) -> Result<Value, PexelsError> {
602 let json_response = self
603 .client
604 .get(url)
605 .header("Authorization", &self.api_key)
606 .send()
607 .await?
608 .json::<Value>()
609 .await?;
610 Ok(json_response)
611 }
612
613 /// Retrieves a list of photos from the Pexels API based on the search criteria.
614 ///
615 /// # Arguments
616 /// * `builder` - A `SearchBuilder` instance with the search parameters.
617 ///
618 /// # Errors
619 /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
620 ///
621 /// # Example
622 /// ```rust,no_run
623 /// use dotenvy::dotenv;
624 /// use pexels_api::Pexels;
625 /// use pexels_api::SearchBuilder;
626 /// use std::env;
627 ///
628 /// #[tokio::main]
629 /// async fn main() {
630 /// dotenv().ok();
631 /// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
632 /// let client = Pexels::new(api_key);
633 /// let response = client.search_photos(SearchBuilder::new().query("mountains").per_page(15).page(1)).await.expect("Failed to get photos");
634 /// println!("{:?}", response);
635 /// }
636 /// ```
637 pub async fn search_photos(
638 &self,
639 builder: SearchBuilder<'_>,
640 ) -> Result<PhotosResponse, PexelsError> {
641 builder.build().fetch(self).await
642 }
643
644 /// Retrieves a photo by its ID from the Pexels API.
645 ///
646 /// # Arguments
647 /// * `id` - The ID of the photo to retrieve.
648 ///
649 /// # Errors
650 /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
651 ///
652 /// # Example
653 /// ```rust,no_run
654 /// use dotenvy::dotenv;
655 /// use pexels_api::Pexels;
656 /// use std::env;
657 ///
658 /// #[tokio::main]
659 /// async fn main() {
660 /// dotenv().ok();
661 /// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
662 /// let client = Pexels::new(api_key);
663 /// let response = client.get_photo(10967).await.expect("Failed to get photo");
664 /// println!("{:?}", response);
665 /// }
666 /// ```
667 pub async fn get_photo(&self, id: usize) -> Result<Photo, PexelsError> {
668 FetchPhotoBuilder::new().id(id).build().fetch(self).await
669 }
670
671 /// Retrieves a random photo from the Pexels API.
672 ///
673 /// # Arguments
674 /// * `builder` - A `CuratedBuilder` instance with the search parameters.
675 ///
676 /// # Errors
677 /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
678 ///
679 /// # Example
680 /// ```rust,no_run
681 /// use dotenvy::dotenv;
682 /// use pexels_api::Pexels;
683 /// use pexels_api::CuratedBuilder;
684 /// use std::env;
685 ///
686 /// #[tokio::main]
687 /// async fn main() {
688 /// dotenv().ok();
689 /// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
690 /// let client = Pexels::new(api_key);
691 /// let response = client.curated_photo(CuratedBuilder::new().per_page(1).page(1)).await.expect("Failed to get random photo");
692 /// println!("{:?}", response);
693 /// }
694 /// ```
695 pub async fn curated_photo(
696 &self,
697 builder: CuratedBuilder,
698 ) -> Result<PhotosResponse, PexelsError> {
699 builder.build().fetch(self).await
700 }
701
702 /// Retrieves a list of videos from the Pexels API based on the search criteria.
703 ///
704 /// # Arguments
705 /// * `builder` - A `VideoSearchBuilder` instance with the search parameters.
706 ///
707 /// # Errors
708 /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
709 ///
710 /// # Example
711 /// ```rust,no_run
712 /// use dotenvy::dotenv;
713 /// use pexels_api::Pexels;
714 /// use pexels_api::VideoSearchBuilder;
715 /// use std::env;
716 ///
717 /// #[tokio::main]
718 /// async fn main() {
719 /// dotenv().ok();
720 /// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
721 /// let client = Pexels::new(api_key);
722 /// let response = client.search_videos(VideoSearchBuilder::new().query("nature").per_page(15).page(1)).await.expect("Failed to get videos");
723 /// println!("{:?}", response);
724 /// }
725 /// ```
726 pub async fn search_videos(
727 &self,
728 builder: VideoSearchBuilder<'_>,
729 ) -> Result<VideoResponse, PexelsError> {
730 builder.build().fetch(self).await
731 }
732
733 /// Retrieves a list of popular videos from the Pexels API.
734 ///
735 /// # Arguments
736 /// * `builder` - A `PopularBuilder` instance with the search parameters.
737 ///
738 /// # Errors
739 /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
740 ///
741 /// # Example
742 /// ```rust,no_run
743 /// use dotenvy::dotenv;
744 /// use pexels_api::Pexels;
745 /// use pexels_api::PopularBuilder;
746 /// use std::env;
747 ///
748 /// #[tokio::main]
749 /// async fn main() {
750 /// dotenv().ok();
751 /// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
752 /// let client = Pexels::new(api_key);
753 /// let response = client.popular_videos(PopularBuilder::new().per_page(15).page(1)).await.expect("Failed to get popular videos");
754 /// println!("{:?}", response);
755 /// }
756 /// ```
757 pub async fn popular_videos(
758 &self,
759 builder: PopularBuilder,
760 ) -> Result<VideoResponse, PexelsError> {
761 builder.build().fetch(self).await
762 }
763
764 /// Retrieves a video by its ID from the Pexels API.
765 ///
766 /// # Arguments
767 /// * `id` - The ID of the video to retrieve.
768 ///
769 /// # Errors
770 /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
771 ///
772 /// # Example
773 /// ```rust,no_run
774 /// use dotenvy::dotenv;
775 /// use pexels_api::Pexels;
776 /// use std::env;
777 ///
778 /// #[tokio::main]
779 /// async fn main() {
780 /// dotenv().ok();
781 /// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
782 /// let client = Pexels::new(api_key);
783 /// let response = client.get_video(25460961).await.expect("Failed to get video");
784 /// println!("{:?}", response);
785 /// }
786 /// ```
787 pub async fn get_video(&self, id: usize) -> Result<Video, PexelsError> {
788 FetchVideoBuilder::new().id(id).build().fetch(self).await
789 }
790
791 /// Retrieves a list of collections from the Pexels API.
792 ///
793 /// # Arguments
794 /// * `per_page` - The number of collections to retrieve per page.
795 /// * `page` - The page number to retrieve.
796 ///
797 /// # Errors
798 /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
799 ///
800 /// # Example
801 /// ```rust,no_run
802 /// use dotenvy::dotenv;
803 /// use pexels_api::Pexels;
804 /// use std::env;
805 ///
806 /// #[tokio::main]
807 /// async fn main() {
808 /// dotenv().ok();
809 /// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
810 /// let client = Pexels::new(api_key);
811 /// let response = client.search_collections(15, 1).await.expect("Failed to get collections");
812 /// println!("{:?}", response);
813 /// }
814 /// ```
815 pub async fn search_collections(
816 &self,
817 per_page: usize,
818 page: usize,
819 ) -> Result<CollectionsResponse, PexelsError> {
820 CollectionsBuilder::new().per_page(per_page).page(page).build().fetch(self).await
821 }
822
823 /// Retrieves a list of featured collections from the Pexels API.
824 ///
825 /// # Arguments
826 /// * `per_page` - The number of collections to retrieve per page.
827 /// * `page` - The page number to retrieve.
828 ///
829 /// # Errors
830 /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
831 ///
832 /// # Example
833 /// ```rust,no_run
834 /// use dotenvy::dotenv;
835 /// use pexels_api::Pexels;
836 /// use std::env;
837 ///
838 /// #[tokio::main]
839 /// async fn main() {
840 /// dotenv().ok();
841 /// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
842 /// let client = Pexels::new(api_key);
843 /// let response = client.featured_collections(15, 1).await.expect("Failed to get collections");
844 /// println!("{:?}", response);
845 /// }
846 /// ```
847 pub async fn featured_collections(
848 &self,
849 per_page: usize,
850 page: usize,
851 ) -> Result<CollectionsResponse, PexelsError> {
852 FeaturedBuilder::new().per_page(per_page).page(page).build().fetch(self).await
853 }
854
855 /// Retrieves all media (photos and videos) within a single collection.
856 ///
857 /// # Arguments
858 /// * `builder` - A `MediaBuilder` instance with the search parameters.
859 ///
860 /// # Errors
861 /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
862 ///
863 /// # Example
864 /// ```rust,no_run
865 /// use dotenvy::dotenv;
866 /// use pexels_api::Pexels;
867 /// use pexels_api::MediaBuilder;
868 /// use std::env;
869 ///
870 /// #[tokio::main]
871 /// async fn main() {
872 /// dotenv().ok();
873 /// let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
874 /// let client = Pexels::new(api_key);
875 /// let builder = MediaBuilder::new().id("tszhfva".to_string()).per_page(15).page(1);
876 /// let response = client.search_media(builder).await.expect("Failed to get media");
877 /// println!("{:?}", response);
878 /// }
879 /// ```
880 pub async fn search_media(&self, builder: MediaBuilder) -> Result<MediaResponse, PexelsError> {
881 builder.build().fetch(self).await
882 }
883}
884
885#[cfg(test)]
886mod tests {
887 use super::*;
888 use dotenvy::dotenv;
889
890 #[test]
891 fn test_pexels_error_partial_eq() {
892 let err1 = PexelsError::ApiKeyNotFound;
893 let err2 = PexelsError::ApiKeyNotFound;
894 assert_eq!(err1, err2);
895
896 let err3 = PexelsError::HexColorCodeError(String::from("Invalid color"));
897 let err4 = PexelsError::HexColorCodeError(String::from("Invalid color"));
898 assert_eq!(err3, err4);
899
900 let err9 = PexelsError::ParseError(ParseError::EmptyHost);
901 let err10 = PexelsError::ParseError(ParseError::EmptyHost);
902 assert_eq!(err9, err10);
903
904 // 测试不相等的情况
905 let err11 = PexelsError::ApiKeyNotFound;
906 let err12 = PexelsError::HexColorCodeError(String::from("Invalid color"));
907 assert_ne!(err11, err12);
908 }
909
910 #[test]
911 fn test_parse_photo() {
912 let input = "photo";
913 let media_type = input.parse::<MediaType>();
914 assert_eq!(media_type, Ok(MediaType::Photo));
915 }
916
917 #[test]
918 fn test_parse_video() {
919 let input = "video";
920 let media_type = input.parse::<MediaType>();
921 assert_eq!(media_type, Ok(MediaType::Video));
922 }
923
924 #[test]
925 fn test_parse_invalid() {
926 let input = "audio";
927 let media_type = input.parse::<MediaType>();
928 assert!(matches!(media_type, Err(PexelsError::ParseMediaTypeError)));
929 }
930
931 #[tokio::test]
932 #[ignore = "requires PEXELS_API_KEY and live Pexels API access"]
933 async fn test_make_request() {
934 dotenv().ok();
935 let api_key = std::env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
936 let client = Pexels::new(api_key);
937 let url = "https://api.pexels.com/v1/curated";
938 let response = client.make_request(url).await;
939 assert!(response.is_ok());
940 }
941}