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}