Initial commit: immich-sdk v1.137.0

This commit is contained in:
Joakim Hulthe
2026-04-05 15:51:10 +00:00
commit 5855251f70
15 changed files with 4434 additions and 0 deletions

232
src/client.rs Normal file
View File

@@ -0,0 +1,232 @@
//! Client for interacting with the Immich API
use crate::apis::{AlbumsApi, AssetsApi, ServerApi, TimelineApi};
use crate::error::{ImmichError, Result};
use std::time::Duration;
/// Configuration for the Immich client
#[derive(Debug, Clone)]
pub struct Config {
/// Base URL of the Immich server
pub base_url: String,
/// API key for authentication
pub api_key: Option<String>,
/// Request timeout
pub timeout: Duration,
/// User agent string
pub user_agent: String,
}
impl Default for Config {
fn default() -> Self {
Self {
base_url: String::new(),
api_key: None,
timeout: Duration::from_secs(30),
user_agent: format!("immich-sdk/{} (Rust)", env!("CARGO_PKG_VERSION")),
}
}
}
impl Config {
/// Create a new config with the given base URL
pub fn new(base_url: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
..Default::default()
}
}
/// Set the API key
pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
self.api_key = Some(api_key.into());
self
}
/// Set the timeout
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
/// Set a custom user agent
pub fn with_user_agent(mut self, user_agent: impl Into<String>) -> Self {
self.user_agent = user_agent.into();
self
}
}
/// Client for making requests to the Immich API
#[derive(Debug, Clone)]
pub struct Client {
config: Config,
http: reqwest::Client,
}
impl Client {
/// Create a new client with the given configuration
pub fn new(config: Config) -> Result<Self> {
// Validate base URL
let base_url = if config.base_url.ends_with('/') {
config.base_url.trim_end_matches('/').to_string()
} else {
config.base_url.clone()
};
if base_url.is_empty() {
return Err(ImmichError::Config("Base URL cannot be empty".to_string()));
}
// Parse to validate URL
let _ = url::Url::parse(&base_url)?;
let http = reqwest::Client::builder()
.timeout(config.timeout)
.user_agent(&config.user_agent)
.build()?;
Ok(Self {
config: Config { base_url, ..config },
http,
})
}
/// Create a client from a base URL string
pub fn from_url(base_url: impl Into<String>) -> Result<Self> {
Self::new(Config::new(base_url))
}
/// Set the API key
pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
self.config.api_key = Some(api_key.into());
self
}
/// Get the configuration
pub fn config(&self) -> &Config {
&self.config
}
/// Get the base URL
pub fn base_url(&self) -> &str {
&self.config.base_url
}
/// Check if the client has an API key configured
pub fn has_api_key(&self) -> bool {
self.config.api_key.is_some()
}
/// Get the underlying HTTP client
pub fn http(&self) -> &reqwest::Client {
&self.http
}
/// Create an authenticated request builder
pub fn request(&self, method: reqwest::Method, path: &str) -> reqwest::RequestBuilder {
let url = format!("{}/api{}", self.config.base_url, path);
let mut builder = self.http.request(method, &url);
// Add API key authentication
if let Some(ref api_key) = self.config.api_key {
builder = builder.header("x-api-key", api_key);
}
builder
}
/// Execute a request and handle common error cases
pub async fn execute(&self, request: reqwest::Request) -> Result<reqwest::Response> {
let response = self.http.execute(request).await?;
if response.status().is_success() {
Ok(response)
} else {
Err(ImmichError::from_response(response).await)
}
}
/// Make a GET request
pub fn get(&self, path: &str) -> reqwest::RequestBuilder {
self.request(reqwest::Method::GET, path)
}
/// Make a POST request
pub fn post(&self, path: &str) -> reqwest::RequestBuilder {
self.request(reqwest::Method::POST, path)
}
/// Make a PUT request
pub fn put(&self, path: &str) -> reqwest::RequestBuilder {
self.request(reqwest::Method::PUT, path)
}
/// Make a DELETE request
pub fn delete(&self, path: &str) -> reqwest::RequestBuilder {
self.request(reqwest::Method::DELETE, path)
}
/// Make a PATCH request
pub fn patch(&self, path: &str) -> reqwest::RequestBuilder {
self.request(reqwest::Method::PATCH, path)
}
/// Access the albums API
pub fn albums(&self) -> AlbumsApi {
AlbumsApi::new(self.clone())
}
/// Access the assets API
pub fn assets(&self) -> AssetsApi {
AssetsApi::new(self.clone())
}
/// Access the server API
pub fn server(&self) -> ServerApi {
ServerApi::new(self.clone())
}
/// Access the timeline API
pub fn timeline(&self) -> TimelineApi {
TimelineApi::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_builder() {
let config = Config::new("https://example.com")
.with_api_key("test-key")
.with_timeout(Duration::from_secs(60));
assert_eq!(config.base_url, "https://example.com");
assert_eq!(config.api_key, Some("test-key".to_string()));
assert_eq!(config.timeout, Duration::from_secs(60));
}
#[test]
fn test_client_creation() {
let client = Client::from_url("https://example.com").unwrap();
assert_eq!(client.base_url(), "https://example.com");
assert!(!client.has_api_key());
let config = Config::new("https://example.com").with_api_key("test-key");
let client = Client::new(config).unwrap();
assert!(client.has_api_key());
}
#[test]
fn test_url_normalization() {
let client = Client::from_url("https://example.com/").unwrap();
assert_eq!(client.base_url(), "https://example.com");
}
#[test]
fn test_empty_url_error() {
let result = Client::from_url("");
assert!(result.is_err());
}
}