fjordgard

A desktop clock application
Log | Files | Refs | README | LICENSE

commit 1d440b4ecffb159f5f281cbf49f2e843e57e106e
parent 86aed9bb1e87e3a532f80b68ced1714e6d4af530
Author: Sylvia Ivory <git@sivory.net>
Date:   Tue, 17 Jun 2025 01:26:26 -0700

Add base unsplash client

Diffstat:
MCargo.lock | 6++++++
Mcrates/unsplash/Cargo.toml | 4++++
Acrates/unsplash/src/error.rs | 13+++++++++++++
Mcrates/unsplash/src/lib.rs | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Acrates/unsplash/src/model.rs | 8++++++++
5 files changed, 92 insertions(+), 9 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -231,6 +231,12 @@ dependencies = [ [[package]] name = "fjordgard-unsplash" version = "0.1.0" +dependencies = [ + "reqwest", + "serde", + "serde_json", + "thiserror", +] [[package]] name = "fjordgard-weather" diff --git a/crates/unsplash/Cargo.toml b/crates/unsplash/Cargo.toml @@ -4,3 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +reqwest = { version = "0.12.20", features = ["json"] } +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +thiserror = "2.0.12" diff --git a/crates/unsplash/src/error.rs b/crates/unsplash/src/error.rs @@ -0,0 +1,13 @@ +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("reqwest error: {0}")] + Reqwest(#[from] reqwest::Error), + #[error("invalid API key provided")] + InvalidAPIKey, + #[error("unsplash error: {0}")] + Unsplash(String), + #[error("json: {0}")] + SerdeJson(#[from] serde_json::Error), +} + +pub type Result<T, E = Error> = std::result::Result<T, E>; diff --git a/crates/unsplash/src/lib.rs b/crates/unsplash/src/lib.rs @@ -1,14 +1,66 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +use std::fmt::Debug; + +use reqwest::{ + header::{self, HeaderMap, HeaderValue}, Client, StatusCode +}; + +pub use error::Error; +use error::Result; +use model::*; +use serde::{Serialize, de::DeserializeOwned}; +mod error; +pub mod model; + +const USER_AGENT: &str = concat!("fjordgard/", env!("CARGO_PKG_VERSION")); +const UNSPLASH_API_HOST: &str = "https://api.unsplash.com/"; + +pub struct UnsplashClient { + client: Client, } -#[cfg(test)] -mod tests { - use super::*; +impl UnsplashClient { + pub fn new(api_key: &str) -> Result<Self> { + let mut headers = HeaderMap::new(); + headers.insert("Accept-Version", HeaderValue::from_static("v1")); + + let mut api_key = HeaderValue::from_str(&format!("Client-ID {api_key}")) + .map_err(|_| Error::InvalidAPIKey)?; + api_key.set_sensitive(true); + headers.insert(header::AUTHORIZATION, api_key); + + let client = Client::builder() + .default_headers(headers) + .user_agent(USER_AGENT) + .build()?; + + Ok(Self { client }) + } + + async fn request<Q: Serialize, T: DeserializeOwned + Debug>( + &self, + route: &str, + query: Option<Q>, + ) -> Result<T> { + let mut req = self.client.get(format!("{UNSPLASH_API_HOST}/{route}")); + + if let Some(ref query) = query { + req = req.query(query) + }; + + let res = req.send().await?; + + if res.status() == StatusCode::UNAUTHORIZED { + return Err(Error::InvalidAPIKey) + } + + let body: UnsplashResponse = res.json().await?; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + match body { + UnsplashResponse::Error { errors } => Err(Error::Unsplash(errors.join(", "))), + UnsplashResponse::Success(v) => match serde_json::from_value(v) { + Ok(o) => Ok(o), + Err(e) => Err(Error::SerdeJson(e)) + } + } } } diff --git a/crates/unsplash/src/model.rs b/crates/unsplash/src/model.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub(crate) enum UnsplashResponse { + Success(serde_json::Value), + Error { errors: Vec<String> }, +}