Skip to main content

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}