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", ¶ms).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", ¶ms).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", ¶ms).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}