pexels_sdk/
lib.rs

1/*!
2`pexels-sdk` crate 提供了 Pexels API 的封装库。基于 [Pexels API 文档](https://www.pexels.com/api/documentation/)。
3
4要获取 API 密钥,您需要从 [申请 API 访问 - Pexels](https://www.pexels.com/api/new/) 申请。
5
6本库依赖 [serde-json](https://github.com/serde-rs/json) crate 来处理结果。因此,您需要阅读 [serde_json - Rust](https://docs.serde.rs/serde_json/index.html) 的文档,特别是 [serde_json::Value - Rust](https://docs.serde.rs/serde_json/enum.Value.html)。
7
8# 配置
9
10在 `Cargo.toml` 文件中的 `[dependencies]` 部分添加以下行:
11
12```toml
13pexels-sdk = "*"
14```
15
16并在您的 crate 根文件(如 `main.rs`)中添加:
17
18```rust
19use pexels_sdk;
20```
21
22完成!现在您可以使用此 API 封装库。
23
24# 示例
25
26此示例展示了如何获取*山脉*照片列表。
27
28```rust,no_run
29use dotenvy::dotenv;
30use std::env;
31use pexels_sdk::{PexelsClient, SearchParams};
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_client = PexelsClient::new(api_key);
38    let params = SearchParams::new()
39        .page(1)
40        .per_page(15);
41    let response = pexels_client.search_photos("mountains", &params).await.expect("Failed to get photos");
42    println!("{:?}", response);
43}
44```
45
46您可以使用 `cargo run` 来运行它!就是如此简单。
47
48# 随机照片
49
50如果您想获取随机照片,可以使用 `curated_photos` 函数并将 `per_page` 设置为 1,`page` 设置为 1 到 1000 之间的随机数,以获取漂亮的随机照片。如果您想要获取特定主题的随机照片,也可以对热门搜索使用相同的方法。
51
52# 图片格式
53
54* original - 原始图片的大小由宽度和高度属性给出。
55* large - 此图片最大宽度为 940 像素,最大高度为 650 像素。它保持原始图片的宽高比。
56* large2x - 此图片最大宽度为 1880 像素,最大高度为 1300 像素。它保持原始图片的宽高比。
57* medium - 此图片高度为 350 像素,宽度灵活。它保持原始图片的宽高比。
58* small - 此图片高度为 130 像素,宽度灵活。它保持原始图片的宽高比。
59* portrait - 此图片宽度为 800 像素,高度为 1200 像素。
60* landscape - 此图片宽度为 1200 像素,高度为 627 像素。
61* tiny - 此图片宽度为 280 像素,高度为 200 像素。
62*/
63
64mod client;
65mod collections;
66mod domain;
67mod download;
68mod models;
69mod photos;
70mod search;
71mod videos;
72
73/// collections 模块
74pub use collections::featured::Featured;
75pub use collections::featured::FeaturedBuilder;
76pub use collections::items::Collections;
77pub use collections::items::CollectionsBuilder;
78pub use collections::media::Media;
79pub use collections::media::MediaBuilder;
80/// domain 模块
81pub use domain::models::Collection;
82pub use domain::models::CollectionsResponse;
83pub use domain::models::MediaPhoto;
84pub use domain::models::MediaResponse;
85pub use domain::models::MediaType as MediaTypeResponse;
86pub use domain::models::MediaVideo;
87pub use domain::models::Photo;
88pub use domain::models::PhotoSrc;
89pub use domain::models::PhotosResponse;
90pub use domain::models::User;
91pub use domain::models::Video;
92pub use domain::models::VideoFile;
93pub use domain::models::VideoPicture;
94pub use domain::models::VideoResponse;
95/// photos 模块
96pub use photos::curated::Curated;
97pub use photos::curated::CuratedBuilder;
98pub use photos::photo::FetchPhoto;
99pub use photos::photo::FetchPhotoBuilder;
100pub use photos::search::Color;
101pub use photos::search::Hex;
102pub use photos::search::Search;
103pub use photos::search::SearchBuilder;
104/// videos 模块
105pub use videos::popular::Popular;
106pub use videos::popular::PopularBuilder;
107pub use videos::search::Search as VideoSearch;
108pub use videos::search::SearchBuilder as VideoSearchBuilder;
109pub use videos::video::FetchVideo;
110pub use videos::video::FetchVideoBuilder;
111
112pub use client::PexelsClient;
113pub use search::SearchParams;
114
115pub use download::DownloadManager;
116pub use download::ProgressCallback;
117
118/// 导入依赖包
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 版本
130const PEXELS_VERSION: &str = "v1";
131
132/// 视频路径
133const PEXELS_VIDEO_PATH: &str = "videos";
134
135/// 收藏路径
136const PEXELS_COLLECTIONS_PATH: &str = "collections";
137
138/// Pexels API URL
139const PEXELS_API: &str = "https://api.pexels.com";
140
141/// 期望的照片方向。
142/// 支持的值:`landscape`、`portrait`、`square`。
143/// 默认值:`landscape`。
144///
145/// # 示例
146/// ```rust
147/// use pexels_sdk::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/// 指定媒体集合中的项目顺序。
195/// 支持的值:`asc`、`desc`。默认值:`asc`。
196///
197/// # 示例
198/// ```rust
199/// use pexels_sdk::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)]
206pub enum MediaSort {
207    Asc,
208    Desc,
209}
210
211impl MediaSort {
212    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/// 指定要请求的媒体类型。
233/// 如果未提供或无效,将返回所有媒体类型。
234/// 支持的值:`photos`、`videos`。
235///
236/// # 示例
237/// ```rust
238/// use pexels_sdk::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)]
248pub enum MediaType {
249    Photo,
250    Video,
251    Empty,
252}
253
254impl MediaType {
255    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" => Ok(MediaType::Photo),
270            "video" => Ok(MediaType::Video),
271            "" => Ok(MediaType::Empty),
272            _ => Err(PexelsError::ParseMediaTypeError),
273        }
274    }
275}
276
277/// 指定搜索查询的语言环境。
278/// 支持的值:`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/// 默认值:`en-US`。
280///
281/// # 示例
282/// ```rust
283/// use pexels_sdk::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/// 指定视频或照片的最小尺寸。
396/// 支持的值:`large`、`medium`、`small`。
397///
398/// # 示例
399/// ```rust
400/// use pexels_sdk::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/// 构建器返回的结果的类型别名。
448pub(crate) type BuilderResult = Result<String, PexelsError>;
449
450/// 与 Pexels API 交互时可能发生的错误。
451/// 此枚举作为与 API 交互的函数的返回类型。
452///
453/// # 示例
454/// ```rust
455/// use pexels_sdk::PexelsError;
456///
457/// let error = PexelsError::ParseMediaTypeError;
458/// assert_eq!(error.to_string(), "解析媒体类型失败: 无效的值");
459/// ```
460#[derive(Debug, Error)]
461pub enum PexelsError {
462    #[error("发送 HTTP 请求失败: {0}")]
463    RequestError(#[from] ReqwestError),
464    #[error("解析 JSON 响应失败: {0}")]
465    JsonParseError(#[from] JSONError),
466    #[error("环境变量中未找到 API 密钥: {0}")]
467    EnvVarError(#[from] VarError),
468    #[error("环境变量中未找到 API 密钥")]
469    ApiKeyNotFound,
470    #[error("解析 URL 失败: {0}")]
471    ParseError(#[from] ParseError),
472    #[error("无效的十六进制颜色码: {0}")]
473    HexColorCodeError(String),
474    #[error("解析媒体类型失败: 无效的值")]
475    ParseMediaTypeError,
476    #[error("解析媒体排序失败: 无效的值")]
477    ParseMediaSortError,
478    #[error("解析方向失败: 无效的值")]
479    ParseOrientationError,
480    #[error("解析尺寸失败: 无效的值")]
481    ParseSizeError,
482    #[error("解析语言环境失败: 无效的值")]
483    ParseLocaleError,
484    #[error("下载错误: {0}")]
485    DownloadError(String),
486    #[error("IO 错误: {0}")]
487    IoError(#[from] std::io::Error),
488    #[error("API 错误: {0}")]
489    ApiError(String),
490    #[error("超出速率限制")]
491    RateLimitError,
492    #[error("认证错误: {0}")]
493    AuthError(String),
494    #[error("无效的参数: {0}")]
495    InvalidParameter(String),
496    #[error("未找到资源: {0}")]
497    NotFound(String),
498    #[error("异步任务错误")]
499    AsyncError,
500    #[error("未知错误: {0}")]
501    Unknown(String),
502}
503
504// Manual implementation PartialEq
505impl PartialEq for PexelsError {
506    fn eq(&self, other: &Self) -> bool {
507        match (self, other) {
508            // Compare RequestError
509            (PexelsError::RequestError(e1), PexelsError::RequestError(e2)) => {
510                e1.to_string() == e2.to_string()
511            }
512            // Compare JsonParseError
513            (PexelsError::JsonParseError(e1), PexelsError::JsonParseError(e2)) => {
514                e1.to_string() == e2.to_string()
515            }
516            // Compare ApiKeyNotFound
517            (PexelsError::ApiKeyNotFound, PexelsError::ApiKeyNotFound) => true,
518            // Compare ParseError
519            (PexelsError::ParseError(e1), PexelsError::ParseError(e2)) => {
520                e1.to_string() == e2.to_string()
521            }
522            // Compare HexColorCodeError
523            (PexelsError::HexColorCodeError(msg1), PexelsError::HexColorCodeError(msg2)) => {
524                msg1 == msg2
525            }
526            // Other things are not equal
527            _ => false,
528        }
529    }
530}
531
532/// 用于与 Pexels API 交互的客户端
533///
534/// # 示例
535/// ```rust,no_run
536/// use dotenvy::dotenv;
537/// use pexels_sdk::PexelsClient;
538/// use std::env;
539///
540/// #[tokio::main]
541/// async fn main() {
542///    dotenv().ok();
543///   let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
544///  let client = PexelsClient::new(api_key);
545/// }
546/// ```
547///
548/// # 错误
549/// 如果请求失败或响应无法解析为 JSON,则返回 `PexelsError`。
550///
551/// # 示例
552/// ```rust,no_run
553/// use dotenvy::dotenv;
554/// use pexels_sdk::PexelsClient;
555/// use pexels_sdk::SearchParams;
556/// use std::env;
557///
558/// #[tokio::main]
559/// async fn main() {
560///     dotenv().ok();
561///     let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
562///     let client = PexelsClient::new(api_key);
563///     let params = SearchParams::new().page(1).per_page(15);
564///     let response = client.search_photos("mountains", &params).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    /// 创建新的 Pexels 客户端。
575    ///
576    /// # 参数
577    /// * `api_key` - Pexels API 的 API 密钥。
578    ///
579    /// # 示例
580    /// ```rust,no_run
581    /// use dotenvy::dotenv;
582    /// use pexels_sdk::PexelsClient;
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 = PexelsClient::new(api_key);
590    /// }
591    /// ```
592    pub fn new(api_key: String) -> Self {
593        Pexels {
594            client: Client::new(),
595            api_key,
596        }
597    }
598
599    /// 向指定 URL 发送 HTTP GET 请求并返回 JSON 响应。
600    /// 使用 `reqwest` crate 发送 HTTP 请求。
601    ///
602    /// # 错误
603    /// 如果请求失败或响应无法解析为 JSON,则返回 `PexelsError`。
604    async fn make_request(&self, url: &str) -> Result<Value, PexelsError> {
605        let json_response = self
606            .client
607            .get(url)
608            .header("Authorization", &self.api_key)
609            .send()
610            .await?
611            .json::<Value>()
612            .await?;
613        Ok(json_response)
614    }
615
616    /// 根据搜索条件从 Pexels API 检索照片列表。
617    ///
618    /// # 参数
619    /// * `builder` - 带有搜索参数的 `SearchBuilder` 实例。
620    ///
621    /// # 错误
622    /// 如果请求失败或响应无法解析为 JSON,则返回 `PexelsError`。
623    ///
624    /// # 示例
625    /// ```rust,no_run
626    /// use dotenvy::dotenv;
627    /// use pexels_sdk::PexelsClient;
628    /// use pexels_sdk::SearchParams;
629    /// use std::env;
630    ///
631    /// #[tokio::main]
632    /// async fn main() {
633    ///     dotenv().ok();
634    ///     let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
635    ///     let client = PexelsClient::new(api_key);
636    ///     let params = SearchParams::new().page(1).per_page(15);
637    ///     let response = client.search_photos("mountains", &params).await.expect("Failed to get photos");
638    ///     println!("{:?}", response);
639    /// }
640    /// ```
641    pub async fn search_photos(
642        &self,
643        builder: SearchBuilder<'_>,
644    ) -> Result<PhotosResponse, PexelsError> {
645        builder.build().fetch(self).await
646    }
647
648    /// 根据 ID 从 Pexels API 检索照片。
649    ///
650    /// # 参数
651    /// * `id` - 要检索的照片的 ID。
652    ///
653    /// # 错误
654    /// 如果请求失败或响应无法解析为 JSON,则返回 `PexelsError`。
655    ///
656    /// # 示例
657    /// ```rust,no_run
658    /// use dotenvy::dotenv;
659    /// use pexels_sdk::PexelsClient;
660    /// use std::env;
661    ///
662    /// #[tokio::main]
663    /// async fn main() {
664    ///     dotenv().ok();
665    ///     let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
666    ///     let client = PexelsClient::new(api_key);
667    ///     let response = client.get_photo(10967).await.expect("Failed to get photo");
668    ///     println!("{:?}", response);
669    /// }
670    /// ```
671    pub async fn get_photo(&self, id: usize) -> Result<Photo, PexelsError> {
672        FetchPhotoBuilder::new().id(id).build().fetch(self).await
673    }
674
675    /// 从 Pexels API 检索随机照片。
676    ///
677    /// # 参数
678    /// * `builder` - 带有搜索参数的 `CuratedBuilder` 实例。
679    ///
680    /// # 错误
681    /// 如果请求失败或响应无法解析为 JSON,则返回 `PexelsError`。  
682    ///
683    /// # Example
684    /// ```rust,no_run
685    /// use dotenvy::dotenv;
686    /// use pexels_sdk::Pexels;
687    /// use pexels_sdk::CuratedBuilder;
688    /// use std::env;
689    ///
690    /// #[tokio::main]
691    /// async fn main() {
692    ///     dotenv().ok();
693    ///     let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
694    ///     let client = Pexels::new(api_key);
695    ///     let response = client.curated_photo(CuratedBuilder::new().per_page(1).page(1)).await.expect("Failed to get random photo");
696    ///     println!("{:?}", response);
697    /// }
698    /// ```                 
699    pub async fn curated_photo(
700        &self,
701        builder: CuratedBuilder,
702    ) -> Result<PhotosResponse, PexelsError> {
703        builder.build().fetch(self).await
704    }
705
706    /// Retrieves a list of videos from the Pexels API based on the search criteria.
707    ///
708    /// # Arguments
709    /// * `builder` - A `VideoSearchBuilder` instance with the search parameters.
710    ///
711    /// # Errors
712    /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
713    ///
714    /// # Example
715    /// ```rust,no_run
716    /// use dotenvy::dotenv;
717    /// use pexels_sdk::Pexels;
718    /// use pexels_sdk::VideoSearchBuilder;
719    /// use std::env;
720    ///
721    /// #[tokio::main]
722    /// async fn main() {
723    ///     dotenv().ok();
724    ///     let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
725    ///     let client = Pexels::new(api_key);
726    ///     let response = client.search_videos(VideoSearchBuilder::new().query("nature").per_page(15).page(1)).await.expect("Failed to get videos");
727    ///     println!("{:?}", response);
728    /// }
729    /// ```                 
730    pub async fn search_videos(
731        &self,
732        builder: VideoSearchBuilder<'_>,
733    ) -> Result<VideoResponse, PexelsError> {
734        builder.build().fetch(self).await
735    }
736
737    /// Retrieves a list of popular videos from the Pexels API.
738    ///
739    /// # Arguments
740    /// * `builder` - A `PopularBuilder` instance with the search parameters.
741    ///
742    /// # Errors
743    /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
744    ///
745    /// # Example
746    /// ```rust,no_run
747    /// use dotenvy::dotenv;
748    /// use pexels_sdk::Pexels;
749    /// use pexels_sdk::PopularBuilder;
750    /// use std::env;
751    ///
752    /// #[tokio::main]
753    /// async fn main() {
754    ///     dotenv().ok();
755    ///     let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
756    ///     let client = Pexels::new(api_key);
757    ///     let response = client.popular_videos(PopularBuilder::new().per_page(15).page(1)).await.expect("Failed to get popular videos");
758    ///     println!("{:?}", response);
759    /// }
760    /// ```                
761    pub async fn popular_videos(
762        &self,
763        builder: PopularBuilder,
764    ) -> Result<VideoResponse, PexelsError> {
765        builder.build().fetch(self).await
766    }
767
768    /// Retrieves a video by its ID from the Pexels API.
769    ///
770    /// # Arguments
771    /// * `id` - The ID of the video to retrieve.
772    ///
773    /// # Errors
774    /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
775    ///
776    /// # Example
777    /// ```rust,no_run
778    /// use dotenvy::dotenv;
779    /// use pexels_sdk::Pexels;
780    /// use std::env;
781    ///
782    /// #[tokio::main]
783    /// async fn main() {
784    ///     dotenv().ok();
785    ///     let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
786    ///     let client = Pexels::new(api_key);
787    ///     let response = client.get_video(25460961).await.expect("Failed to get video");
788    ///     println!("{:?}", response);
789    /// }
790    /// ```
791    pub async fn get_video(&self, id: usize) -> Result<Video, PexelsError> {
792        FetchVideoBuilder::new().id(id).build().fetch(self).await
793    }
794
795    /// Retrieves a list of collections from the Pexels API.
796    ///
797    /// # Arguments
798    /// * `per_page` - The number of collections to retrieve per page.
799    /// * `page` - The page number to retrieve.
800    ///
801    /// # Errors
802    /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
803    ///
804    /// # Example
805    /// ```rust,no_run
806    /// use dotenvy::dotenv;
807    /// use pexels_sdk::Pexels;
808    /// use std::env;
809    ///
810    /// #[tokio::main]
811    /// async fn main() {
812    ///     dotenv().ok();
813    ///     let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
814    ///     let client = Pexels::new(api_key);
815    ///     let response = client.search_collections(15, 1).await.expect("Failed to get collections");
816    ///     println!("{:?}", response);
817    /// }      
818    /// ```          
819    pub async fn search_collections(
820        &self,
821        per_page: usize,
822        page: usize,
823    ) -> Result<CollectionsResponse, PexelsError> {
824        CollectionsBuilder::new()
825            .per_page(per_page)
826            .page(page)
827            .build()
828            .fetch(self)
829            .await
830    }
831
832    /// Retrieves a list of featured collections from the Pexels API.
833    ///
834    /// # Arguments
835    /// * `per_page` - The number of collections to retrieve per page.
836    /// * `page` - The page number to retrieve.
837    ///
838    /// # Errors
839    /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
840    ///
841    /// # Example
842    /// ```rust,no_run
843    /// use dotenvy::dotenv;
844    /// use pexels_sdk::Pexels;
845    /// use std::env;
846    ///
847    /// #[tokio::main]
848    /// async fn main() {
849    ///     dotenv().ok();
850    ///     let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
851    ///     let client = Pexels::new(api_key);
852    ///     let response = client.featured_collections(15, 1).await.expect("Failed to get collections");
853    ///     println!("{:?}", response);
854    /// }
855    /// ```
856    pub async fn featured_collections(
857        &self,
858        per_page: usize,
859        page: usize,
860    ) -> Result<CollectionsResponse, PexelsError> {
861        FeaturedBuilder::new()
862            .per_page(per_page)
863            .page(page)
864            .build()
865            .fetch(self)
866            .await
867    }
868
869    /// Retrieves all media (photos and videos) within a single collection.
870    ///
871    /// # Arguments
872    /// * `builder` - A `MediaBuilder` instance with the search parameters.
873    ///
874    /// # Errors
875    /// Returns a `PexelsError` if the request fails or the response cannot be parsed as JSON.
876    ///
877    /// # Example
878    /// ```rust,no_run
879    /// use dotenvy::dotenv;
880    /// use pexels_sdk::Pexels;
881    /// use pexels_sdk::MediaBuilder;
882    /// use std::env;
883    ///
884    /// #[tokio::main]
885    /// async fn main() {
886    ///     dotenv().ok();
887    ///     let api_key = env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
888    ///     let client = Pexels::new(api_key);
889    ///     let builder = MediaBuilder::new().id("your_collection_id".to_string()).per_page(15).page(1);
890    ///     let response = client.search_media(builder).await.expect("Failed to get media");
891    ///     println!("Found {} media items", response.total_results);
892    /// }                 
893    pub async fn search_media(&self, builder: MediaBuilder) -> Result<MediaResponse, PexelsError> {
894        builder.build().fetch(self).await
895    }
896}
897
898#[cfg(test)]
899mod tests {
900    use super::*;
901    use dotenvy::dotenv;
902
903    #[test]
904    fn test_pexels_error_partial_eq() {
905        let err1 = PexelsError::ApiKeyNotFound;
906        let err2 = PexelsError::ApiKeyNotFound;
907        assert_eq!(err1, err2);
908
909        let err3 = PexelsError::HexColorCodeError(String::from("Invalid color"));
910        let err4 = PexelsError::HexColorCodeError(String::from("Invalid color"));
911        assert_eq!(err3, err4);
912
913        let err9 = PexelsError::ParseError(ParseError::EmptyHost);
914        let err10 = PexelsError::ParseError(ParseError::EmptyHost);
915        assert_eq!(err9, err10);
916
917        // 测试不相等的情况
918        let err11 = PexelsError::ApiKeyNotFound;
919        let err12 = PexelsError::HexColorCodeError(String::from("Invalid color"));
920        assert_ne!(err11, err12);
921    }
922
923    #[test]
924    fn test_parse_photo() {
925        let input = "photo";
926        let media_type = input.parse::<MediaType>();
927        assert_eq!(media_type, Ok(MediaType::Photo));
928    }
929
930    #[test]
931    fn test_parse_video() {
932        let input = "video";
933        let media_type = input.parse::<MediaType>();
934        assert_eq!(media_type, Ok(MediaType::Video));
935    }
936
937    #[test]
938    fn test_parse_invalid() {
939        let input = "audio";
940        let media_type = input.parse::<MediaType>();
941        assert!(matches!(media_type, Err(PexelsError::ParseMediaTypeError)));
942    }
943
944    #[tokio::test]
945    #[ignore]
946    async fn test_make_request() {
947        dotenv().ok();
948        let api_key = std::env::var("PEXELS_API_KEY").expect("PEXELS_API_KEY not set");
949        let client = Pexels::new(api_key);
950        let url = "https://api.pexels.com/v1/curated";
951        let response = client.make_request(url).await;
952        assert!(response.is_ok());
953    }
954}