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