From cc9ac972849b316207d54b84ac1660669351c49d Mon Sep 17 00:00:00 2001 From: Arnaud_Cayrol Date: Wed, 4 Feb 2026 19:51:44 +0100 Subject: [PATCH] More user friendly immich url handling --- config.example.toml | 4 +-- src/immich_api/mod.rs | 64 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/config.example.toml b/config.example.toml index 0ca4831..a17230d 100644 --- a/config.example.toml +++ b/config.example.toml @@ -9,8 +9,8 @@ output_dir = "output" # Your Immich API key (get from Immich user settings) api_key = "your-api-key-here" -# Immich server URL (include /api suffix) -base_url = "http://192.168.1.100:2283/api" +# Immich server URL (/api suffix is optional, will be added automatically) +base_url = "http://192.168.1.100:2283" # Request timeout in seconds timeout_secs = 30 diff --git a/src/immich_api/mod.rs b/src/immich_api/mod.rs index 9c78ccb..368721e 100644 --- a/src/immich_api/mod.rs +++ b/src/immich_api/mod.rs @@ -102,11 +102,22 @@ impl ImmichClient { Ok(Self { client, - base_url: config.base_url.trim_end_matches('/').to_string(), + base_url: Self::sanitize_base_url(&config.base_url), api_key: config.api_key.clone(), }) } + /// Sanitize the base URL to ensure it ends with /api. + fn sanitize_base_url(url: &str) -> String { + let trimmed = url.trim_end_matches('/'); + + if trimmed.ends_with("/api") { + trimmed.to_string() + } else { + format!("{}/api", trimmed) + } + } + /// Validate the connection to Immich. pub async fn validate_connection(&self) -> Result { let url = format!("{}/server/about", self.base_url); @@ -272,6 +283,45 @@ impl ImmichClient { mod tests { use super::*; + #[test] + fn test_sanitize_base_url() { + // URL already ends with /api + assert_eq!( + ImmichClient::sanitize_base_url("http://localhost:2283/api"), + "http://localhost:2283/api" + ); + + // URL ends with /api/ (trailing slash) + assert_eq!( + ImmichClient::sanitize_base_url("http://localhost:2283/api/"), + "http://localhost:2283/api" + ); + + // URL without /api + assert_eq!( + ImmichClient::sanitize_base_url("http://localhost:2283"), + "http://localhost:2283/api" + ); + + // URL with trailing slash, no /api + assert_eq!( + ImmichClient::sanitize_base_url("http://localhost:2283/"), + "http://localhost:2283/api" + ); + + // URL with path but no /api + assert_eq!( + ImmichClient::sanitize_base_url("http://example.com/immich"), + "http://example.com/immich/api" + ); + + // URL with path and trailing slash + assert_eq!( + ImmichClient::sanitize_base_url("http://example.com/immich/"), + "http://example.com/immich/api" + ); + } + #[test] fn test_client_creation() { let config = ApiConfig { @@ -283,6 +333,18 @@ mod tests { assert!(client.is_ok()); } + #[test] + fn test_client_creation_without_api_suffix() { + // URL without /api should be sanitized automatically + let config = ApiConfig { + api_key: "test-key".to_string(), + base_url: "http://localhost:2283".to_string(), + timeout_secs: 30, + }; + let client = ImmichClient::new(&config).unwrap(); + assert_eq!(client.base_url, "http://localhost:2283/api"); + } + /// Helper to create a client for the demo server fn demo_client() -> ImmichClient { let config = ApiConfig {