use reqwest::{header, Client, StatusCode};
use std::time::Duration;
use url::Url;
use crate::models::{CollectionsPage, MediaPage, Photo, PhotosPage, Video, VideosPage};
use crate::search::{PaginationParams, SearchParams, VideoSearchParams};
use crate::PexelsError;
pub struct PexelsClient {
api_key: String,
client: Client,
base_url: String,
}
impl PexelsClient {
pub fn new<S: Into<String>>(api_key: S) -> Self {
let client = Client::builder()
.timeout(Duration::from_secs(30))
.pool_max_idle_per_host(10)
.build()
.unwrap_or_default();
Self {
api_key: api_key.into(),
client,
base_url: "https://api.pexels.com/v1".to_string(),
}
}
pub fn with_config<S: Into<String>>(
api_key: S,
timeout: u64,
max_idle_connections: usize,
) -> Self {
let client = Client::builder()
.timeout(Duration::from_secs(timeout))
.pool_max_idle_per_host(max_idle_connections)
.build()
.unwrap_or_default();
Self {
api_key: api_key.into(),
client,
base_url: "https://api.pexels.com/v1".to_string(),
}
}
pub fn with_base_url<S: Into<String>>(mut self, base_url: S) -> Self {
self.base_url = base_url.into();
self
}
pub async fn search_photos(
&self,
query: &str,
params: &SearchParams,
) -> Result<PhotosPage, PexelsError> {
let mut url = Url::parse(&format!("{}/search", self.base_url))?;
url.query_pairs_mut().append_pair("query", query);
for (key, value) in params.to_query_params() {
url.query_pairs_mut().append_pair(&key, &value);
}
let response = self.send_request(url).await?;
match response.status() {
StatusCode::OK => {
let photos_page: PhotosPage = response.json().await?;
Ok(photos_page)
}
StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
status => Err(PexelsError::ApiError(format!(
"Search photos failed with status: {status}"
))),
}
}
pub async fn curated_photos(
&self,
params: &PaginationParams,
) -> Result<PhotosPage, PexelsError> {
let mut url = Url::parse(&format!("{}/curated", self.base_url))?;
if let Some(page) = params.page {
url.query_pairs_mut().append_pair("page", &page.to_string());
}
if let Some(per_page) = params.per_page {
url.query_pairs_mut()
.append_pair("per_page", &per_page.to_string());
}
let response = self.send_request(url).await?;
match response.status() {
StatusCode::OK => {
let photos_page: PhotosPage = response.json().await?;
Ok(photos_page)
}
StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
status => Err(PexelsError::ApiError(format!(
"Curated photos failed with status: {status}"
))),
}
}
pub async fn get_photo(&self, id: u64) -> Result<Photo, PexelsError> {
let url = Url::parse(&format!("{}/photos/{}", self.base_url, id))?;
let response = self.send_request(url).await?;
match response.status() {
StatusCode::OK => {
let photo: Photo = response.json().await?;
Ok(photo)
}
StatusCode::NOT_FOUND => Err(PexelsError::NotFound(format!(
"Photo with ID {id} not found"
))),
StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
status => Err(PexelsError::ApiError(format!(
"Get photo failed with status: {status}"
))),
}
}
pub async fn search_videos(
&self,
query: &str,
params: &VideoSearchParams,
) -> Result<VideosPage, PexelsError> {
let mut url = Url::parse(&format!("{}/videos/search", self.base_url))?;
url.query_pairs_mut().append_pair("query", query);
if let Some(page) = params.page {
url.query_pairs_mut().append_pair("page", &page.to_string());
}
if let Some(per_page) = params.per_page {
url.query_pairs_mut()
.append_pair("per_page", &per_page.to_string());
}
if let Some(ref orientation) = params.orientation {
url.query_pairs_mut()
.append_pair("orientation", orientation.as_str());
}
if let Some(ref size) = params.size {
url.query_pairs_mut().append_pair("size", size.as_str());
}
if let Some(ref locale) = params.locale {
url.query_pairs_mut().append_pair("locale", locale);
}
let response = self.send_request(url).await?;
match response.status() {
StatusCode::OK => {
let videos_page: VideosPage = response.json().await?;
Ok(videos_page)
}
StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
status => Err(PexelsError::ApiError(format!(
"Search videos failed with status: {status}"
))),
}
}
pub async fn popular_videos(
&self,
params: &PaginationParams,
) -> Result<VideosPage, PexelsError> {
let mut url = Url::parse(&format!("{}/videos/popular", self.base_url))?;
if let Some(page) = params.page {
url.query_pairs_mut().append_pair("page", &page.to_string());
}
if let Some(per_page) = params.per_page {
url.query_pairs_mut()
.append_pair("per_page", &per_page.to_string());
}
let response = self.send_request(url).await?;
match response.status() {
StatusCode::OK => {
let videos_page: VideosPage = response.json().await?;
Ok(videos_page)
}
StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
status => Err(PexelsError::ApiError(format!(
"Popular videos failed with status: {status}"
))),
}
}
pub async fn get_video(&self, id: u64) -> Result<Video, PexelsError> {
let url = Url::parse(&format!("{}/videos/videos/{}", self.base_url, id))?;
let response = self.send_request(url).await?;
match response.status() {
StatusCode::OK => {
let video: Video = response.json().await?;
Ok(video)
}
StatusCode::NOT_FOUND => Err(PexelsError::NotFound(format!(
"Video with ID {id} not found"
))),
StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
status => Err(PexelsError::ApiError(format!(
"Get video failed with status: {status}"
))),
}
}
pub async fn get_collections(
&self,
params: &PaginationParams,
) -> Result<CollectionsPage, PexelsError> {
let mut url = Url::parse(&format!("{}/collections", self.base_url))?;
if let Some(page) = params.page {
url.query_pairs_mut().append_pair("page", &page.to_string());
}
if let Some(per_page) = params.per_page {
url.query_pairs_mut()
.append_pair("per_page", &per_page.to_string());
}
let response = self.send_request(url).await?;
match response.status() {
StatusCode::OK => {
let collections_page: CollectionsPage = response.json().await?;
Ok(collections_page)
}
StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
status => Err(PexelsError::ApiError(format!(
"Get collections failed with status: {status}"
))),
}
}
pub async fn get_collection_media(
&self,
id: &str,
params: &PaginationParams,
) -> Result<MediaPage, PexelsError> {
let mut url = Url::parse(&format!("{}/collections/{}", self.base_url, id))?;
if let Some(page) = params.page {
url.query_pairs_mut().append_pair("page", &page.to_string());
}
if let Some(per_page) = params.per_page {
url.query_pairs_mut()
.append_pair("per_page", &per_page.to_string());
}
let response = self.send_request(url).await?;
match response.status() {
StatusCode::OK => {
let media_page: MediaPage = response.json().await?;
Ok(media_page)
}
StatusCode::NOT_FOUND => Err(PexelsError::NotFound(format!(
"Collection with ID {id} not found"
))),
StatusCode::UNAUTHORIZED => Err(PexelsError::AuthError("Invalid API key".to_string())),
StatusCode::TOO_MANY_REQUESTS => Err(PexelsError::RateLimitError),
status => Err(PexelsError::ApiError(format!(
"Get collection media failed with status: {status}"
))),
}
}
async fn send_request(&self, url: Url) -> Result<reqwest::Response, PexelsError> {
let response = self
.client
.get(url)
.header(header::AUTHORIZATION, &self.api_key)
.send()
.await?;
Ok(response)
}
}