commit 1d440b4ecffb159f5f281cbf49f2e843e57e106e
parent 86aed9bb1e87e3a532f80b68ced1714e6d4af530
Author: Sylvia Ivory <git@sivory.net>
Date: Tue, 17 Jun 2025 01:26:26 -0700
Add base unsplash client
Diffstat:
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> },
+}