From a5b05ce3b75a16f34762f4866219aaafbd45fd92 Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Fri, 27 Mar 2026 15:33:34 +0530 Subject: [PATCH 1/7] feat(rust): add From impl to error type --- templates/rust/src/error.rs.twig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/templates/rust/src/error.rs.twig b/templates/rust/src/error.rs.twig index 26ee0be65f..0fb3d8edc4 100644 --- a/templates/rust/src/error.rs.twig +++ b/templates/rust/src/error.rs.twig @@ -69,6 +69,12 @@ impl From for {{ spec.title | caseUcfirst }}Error { } } +impl From for {{ spec.title | caseUcfirst }}Error { + fn from(err: reqwest::header::InvalidHeaderValue) -> Self { + Self::new(400, format!("Invalid header value: {}", err), None, String::new()) + } +} + /// {{ spec.title }} specific error response structure #[derive(Debug, serde::Deserialize)] pub struct ErrorResponse { From ed9237e219ea0ef283701aed1738e0800162eb1a Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Fri, 27 Mar 2026 15:33:43 +0530 Subject: [PATCH 2/7] fix(rust): replace unwrap/panic with Result in client setters --- templates/rust/src/client.rs.twig | 77 ++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/templates/rust/src/client.rs.twig b/templates/rust/src/client.rs.twig index b9a613cc10..652561f49f 100644 --- a/templates/rust/src/client.rs.twig +++ b/templates/rust/src/client.rs.twig @@ -88,6 +88,7 @@ impl Client { /// Create a new {{ spec.title }} client pub fn new() -> Self { let mut headers = HeaderMap::new(); + // SAFETY: Header values below are compile-time constants from the SDK spec and always valid ASCII. {% for key, header in spec.global.defaultHeaders %} headers.insert("{{ key }}", "{{ header }}".parse().unwrap()); {% endfor %} @@ -138,72 +139,92 @@ impl Client { } /// Set the API endpoint - pub fn set_endpoint>(&self, endpoint: S) -> Self { + pub fn set_endpoint>(&self, endpoint: S) -> Result { let endpoint = endpoint.into(); if !endpoint.starts_with("http://") && !endpoint.starts_with("https://") { - panic!("Invalid endpoint URL: {}. Endpoint must start with http:// or https://", endpoint); + return Err({{ spec.title | caseUcfirst }}Error::new( + 400, + format!("Invalid endpoint URL: {}", endpoint), + None, + String::new(), + )); } self.state.rcu(|state| { let mut next = (**state).clone(); next.config.endpoint = endpoint.clone(); Arc::new(next) }); - self.clone() + Ok(self.clone()) } /// Set the project ID - pub fn set_project>(&self, project: S) -> Self { + pub fn set_project>(&self, project: S) -> Result { let project = project.into(); + let value = project.parse().map_err(|_| {{ spec.title | caseUcfirst }}Error::new( + 400, "Invalid header value for x-appwrite-project", None, String::new(), + ))?; self.state.rcu(|state| { let mut next = (**state).clone(); - next.config.headers.insert("x-appwrite-project", project.clone().parse().unwrap()); + next.config.headers.insert("x-appwrite-project", value.clone()); Arc::new(next) }); - self.clone() + Ok(self.clone()) } /// Set the API key - pub fn set_key>(&self, key: S) -> Self { + pub fn set_key>(&self, key: S) -> Result { let key = key.into(); + let value = key.parse().map_err(|_| {{ spec.title | caseUcfirst }}Error::new( + 400, "Invalid header value for x-appwrite-key", None, String::new(), + ))?; self.state.rcu(|state| { let mut next = (**state).clone(); - next.config.headers.insert("x-appwrite-key", key.clone().parse().unwrap()); + next.config.headers.insert("x-appwrite-key", value.clone()); Arc::new(next) }); - self.clone() + Ok(self.clone()) } /// Set the JWT token - pub fn set_jwt>(&self, jwt: S) -> Self { + pub fn set_jwt>(&self, jwt: S) -> Result { let jwt = jwt.into(); + let value = jwt.parse().map_err(|_| {{ spec.title | caseUcfirst }}Error::new( + 400, "Invalid header value for x-appwrite-jwt", None, String::new(), + ))?; self.state.rcu(|state| { let mut next = (**state).clone(); - next.config.headers.insert("x-appwrite-jwt", jwt.clone().parse().unwrap()); + next.config.headers.insert("x-appwrite-jwt", value.clone()); Arc::new(next) }); - self.clone() + Ok(self.clone()) } /// Set the locale - pub fn set_locale>(&self, locale: S) -> Self { + pub fn set_locale>(&self, locale: S) -> Result { let locale = locale.into(); + let value = locale.parse().map_err(|_| {{ spec.title | caseUcfirst }}Error::new( + 400, "Invalid header value for x-appwrite-locale", None, String::new(), + ))?; self.state.rcu(|state| { let mut next = (**state).clone(); - next.config.headers.insert("x-appwrite-locale", locale.clone().parse().unwrap()); + next.config.headers.insert("x-appwrite-locale", value.clone()); Arc::new(next) }); - self.clone() + Ok(self.clone()) } /// Set the session - pub fn set_session>(&self, session: S) -> Self { + pub fn set_session>(&self, session: S) -> Result { let session = session.into(); + let value = session.parse().map_err(|_| {{ spec.title | caseUcfirst }}Error::new( + 400, "Invalid header value for x-appwrite-session", None, String::new(), + ))?; self.state.rcu(|state| { let mut next = (**state).clone(); - next.config.headers.insert("x-appwrite-session", session.clone().parse().unwrap()); + next.config.headers.insert("x-appwrite-session", value.clone()); Arc::new(next) }); - self.clone() + Ok(self.clone()) } /// Enable or disable self-signed certificates @@ -897,10 +918,24 @@ mod tests { #[test] fn test_client_builder_pattern() { let client = Client::new() - .set_endpoint("https://custom.example.com/v1") - .set_project("test-project") - .set_key("test-key"); + .set_endpoint("https://custom.example.com/v1").unwrap() + .set_project("test-project").unwrap() + .set_key("test-key").unwrap(); assert_eq!(client.endpoint(), "https://custom.example.com/v1"); } + + #[test] + fn test_invalid_endpoint() { + let client = Client::new(); + let err = client.set_endpoint("htp://cloud.appwrite.io/v1").unwrap_err(); + assert_eq!(err.code, 400); + assert!(err.message.contains("Invalid endpoint URL")); + } + + #[test] + fn test_invalid_header_value() { + let client = Client::new(); + assert!(client.set_key("my\nkey").is_err()); + } } From 7635e2c4b56a1613fe839098d81a8de81ee8ad5d Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Fri, 27 Mar 2026 15:33:51 +0530 Subject: [PATCH 3/7] fix(rust): update tests and examples for Result-based setters --- templates/rust/examples/basic_usage.rs.twig | 12 ++++++------ templates/rust/src/lib.rs.twig | 6 +++--- templates/rust/tests/tests.rs | 11 +++++++---- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/templates/rust/examples/basic_usage.rs.twig b/templates/rust/examples/basic_usage.rs.twig index 16b6cff627..6afb9b9e77 100644 --- a/templates/rust/examples/basic_usage.rs.twig +++ b/templates/rust/examples/basic_usage.rs.twig @@ -11,9 +11,9 @@ use {{ sdk.cratePackage | default('appwrite') | rustCrateName }}::{ async fn main() -> Result<(), Box> { // Initialize the client let client = Client::new() - .set_endpoint("{{ spec.endpoint }}") // Your API Endpoint - .set_project("5df5acd0d48c2") // Your project ID - .set_key("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key + .set_endpoint("{{ spec.endpoint }}")? // Your API Endpoint + .set_project("5df5acd0d48c2")? // Your project ID + .set_key("919c2d18fb5d4...a2ae413da83346ad2")?; // Your secret API key println!("🚀 {{ spec.title }} Rust SDK Example"); println!("Connected to: {}", client.endpoint()); @@ -129,9 +129,9 @@ async fn main() -> Result<(), Box> { // This will likely fail with invalid credentials let invalid_client = Client::new() - .set_endpoint("{{ spec.endpoint }}") - .set_project("invalid-project") - .set_key("invalid-key"); + .set_endpoint("{{ spec.endpoint }}")? + .set_project("invalid-project")? + .set_key("invalid-key")?; let invalid_users = Users::new(&invalid_client); diff --git a/templates/rust/src/lib.rs.twig b/templates/rust/src/lib.rs.twig index 3616be4a57..cb443cba1c 100644 --- a/templates/rust/src/lib.rs.twig +++ b/templates/rust/src/lib.rs.twig @@ -19,9 +19,9 @@ //! #[tokio::main] //! async fn main() -> Result<(), Box> { //! let client = Client::new() -//! .set_endpoint("{{ spec.endpoint }}") -//! .set_project("your-project-id") -//! .set_key("your-api-key"); +//! .set_endpoint("{{ spec.endpoint }}")? +//! .set_project("your-project-id")? +//! .set_key("your-api-key")?; //! //! // Use the client to make API calls //! Ok(()) diff --git a/templates/rust/tests/tests.rs b/templates/rust/tests/tests.rs index ffbed8e7b2..d175cf22fc 100644 --- a/templates/rust/tests/tests.rs +++ b/templates/rust/tests/tests.rs @@ -8,9 +8,9 @@ async fn main() -> Result<(), Box> { let string_in_array = vec!["string in array".to_string()]; let client = Client::new() - .set_endpoint("http://mockapi/v1") - .set_project("appwrite") - .set_key("apikey") + .set_endpoint("http://mockapi/v1").unwrap() + .set_project("appwrite").unwrap() + .set_key("apikey").unwrap() .add_header("Origin", "http://localhost"); println!("\n\nTest Started"); @@ -126,7 +126,10 @@ async fn test_general_service(client: &Client, string_in_array: &[String]) -> Re }, } - println!("Invalid endpoint URL: htp://cloud.appwrite.io/v1"); + match Client::new().set_endpoint("htp://cloud.appwrite.io/v1") { + Ok(_) => println!("Expected error for invalid endpoint"), + Err(e) => println!("{}", e.message), + } let _ = general.empty().await; From b071ceff29f507200f30e9a97df025577eca547b Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Sat, 4 Apr 2026 20:52:13 +0530 Subject: [PATCH 4/7] Improve error handling for endpoint validation and update test cases for better clarity --- templates/rust/src/client.rs.twig | 30 +++++++++++++----------------- templates/rust/tests/tests.rs | 8 ++++---- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/templates/rust/src/client.rs.twig b/templates/rust/src/client.rs.twig index 84194f6687..5d0db642f7 100644 --- a/templates/rust/src/client.rs.twig +++ b/templates/rust/src/client.rs.twig @@ -141,10 +141,16 @@ impl Client { /// Set the API endpoint pub fn set_endpoint>(&self, endpoint: S) -> Result { let endpoint = endpoint.into(); - if !endpoint.starts_with("http://") && !endpoint.starts_with("https://") { + let parsed = url::Url::parse(&endpoint).map_err(|e| {{ spec.title | caseUcfirst }}Error::new( + 400, + format!("Invalid endpoint URL: {}", e), + None, + String::new(), + ))?; + if parsed.scheme() != "http" && parsed.scheme() != "https" { return Err({{ spec.title | caseUcfirst }}Error::new( 400, - format!("Invalid endpoint URL: {}", endpoint), + format!("Invalid endpoint URL scheme '{}': must be http or https", parsed.scheme()), None, String::new(), )); @@ -160,9 +166,7 @@ impl Client { /// Set the project ID pub fn set_project>(&self, project: S) -> Result { let project = project.into(); - let value = project.parse().map_err(|_| {{ spec.title | caseUcfirst }}Error::new( - 400, "Invalid header value for x-appwrite-project", None, String::new(), - ))?; + let value: reqwest::header::HeaderValue = project.parse()?; self.state.rcu(|state| { let mut next = (**state).clone(); next.config.headers.insert("x-appwrite-project", value.clone()); @@ -174,9 +178,7 @@ impl Client { /// Set the API key pub fn set_key>(&self, key: S) -> Result { let key = key.into(); - let value = key.parse().map_err(|_| {{ spec.title | caseUcfirst }}Error::new( - 400, "Invalid header value for x-appwrite-key", None, String::new(), - ))?; + let value: reqwest::header::HeaderValue = key.parse()?; self.state.rcu(|state| { let mut next = (**state).clone(); next.config.headers.insert("x-appwrite-key", value.clone()); @@ -188,9 +190,7 @@ impl Client { /// Set the JWT token pub fn set_jwt>(&self, jwt: S) -> Result { let jwt = jwt.into(); - let value = jwt.parse().map_err(|_| {{ spec.title | caseUcfirst }}Error::new( - 400, "Invalid header value for x-appwrite-jwt", None, String::new(), - ))?; + let value: reqwest::header::HeaderValue = jwt.parse()?; self.state.rcu(|state| { let mut next = (**state).clone(); next.config.headers.insert("x-appwrite-jwt", value.clone()); @@ -202,9 +202,7 @@ impl Client { /// Set the locale pub fn set_locale>(&self, locale: S) -> Result { let locale = locale.into(); - let value = locale.parse().map_err(|_| {{ spec.title | caseUcfirst }}Error::new( - 400, "Invalid header value for x-appwrite-locale", None, String::new(), - ))?; + let value: reqwest::header::HeaderValue = locale.parse()?; self.state.rcu(|state| { let mut next = (**state).clone(); next.config.headers.insert("x-appwrite-locale", value.clone()); @@ -216,9 +214,7 @@ impl Client { /// Set the session pub fn set_session>(&self, session: S) -> Result { let session = session.into(); - let value = session.parse().map_err(|_| {{ spec.title | caseUcfirst }}Error::new( - 400, "Invalid header value for x-appwrite-session", None, String::new(), - ))?; + let value: reqwest::header::HeaderValue = session.parse()?; self.state.rcu(|state| { let mut next = (**state).clone(); next.config.headers.insert("x-appwrite-session", value.clone()); diff --git a/templates/rust/tests/tests.rs b/templates/rust/tests/tests.rs index 724cd6b81d..5d76bbe31d 100644 --- a/templates/rust/tests/tests.rs +++ b/templates/rust/tests/tests.rs @@ -8,9 +8,9 @@ async fn main() -> Result<(), Box> { let string_in_array = vec!["string in array".to_string()]; let client = Client::new() - .set_endpoint("http://mockapi/v1").unwrap() - .set_project("appwrite").unwrap() - .set_key("apikey").unwrap() + .set_endpoint("http://mockapi/v1")? + .set_project("appwrite")? + .set_key("apikey")? .add_header("Origin", "http://localhost"); println!("\n\nTest Started"); @@ -135,7 +135,7 @@ async fn test_general_service(client: &Client, string_in_array: &[String]) -> Re } match Client::new().set_endpoint("htp://cloud.appwrite.io/v1") { - Ok(_) => println!("Expected error for invalid endpoint"), + Ok(_) => println!("ERROR: Expected validation failure for invalid endpoint but got Ok"), Err(e) => println!("{}", e.message), } From f6e331cbfea757eb6d310873901814835f8dc149 Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Tue, 7 Apr 2026 13:11:35 +0530 Subject: [PATCH 5/7] Fix SAFETY comment to accurately scope unwrap justifications --- templates/rust/src/client.rs.twig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/rust/src/client.rs.twig b/templates/rust/src/client.rs.twig index 5d0db642f7..22658abd4c 100644 --- a/templates/rust/src/client.rs.twig +++ b/templates/rust/src/client.rs.twig @@ -88,10 +88,12 @@ impl Client { /// Create a new {{ spec.title }} client pub fn new() -> Self { let mut headers = HeaderMap::new(); - // SAFETY: Header values below are compile-time constants from the SDK spec and always valid ASCII. + // SAFETY: Spec-defined header values are compile-time constants and always valid ASCII. {% for key, header in spec.global.defaultHeaders %} headers.insert("{{ key }}", "{{ header }}".parse().unwrap()); {% endfor %} + // SAFETY: SDK metadata values are also compile-time constants; OS/ARCH are + // guaranteed-valid ASCII from std::env::consts. headers.insert("user-agent", format!("{{ spec.title }}RustSDK/{{ sdk.version }} ({}; {})", std::env::consts::OS, std::env::consts::ARCH).parse().unwrap()); headers.insert("x-sdk-name", "{{ sdk.name }}".parse().unwrap()); headers.insert("x-sdk-platform", "{{ sdk.platform }}".parse().unwrap()); From dd66a3eea9b40112e21942d38a444b282330232b Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Tue, 7 Apr 2026 13:11:39 +0530 Subject: [PATCH 6/7] Make add_header return Result for consistent error handling with setters --- templates/rust/src/client.rs.twig | 14 ++++++-------- templates/rust/src/error.rs.twig | 6 ++++++ templates/rust/tests/tests.rs | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/templates/rust/src/client.rs.twig b/templates/rust/src/client.rs.twig index 22658abd4c..43c2055ce0 100644 --- a/templates/rust/src/client.rs.twig +++ b/templates/rust/src/client.rs.twig @@ -264,23 +264,21 @@ impl Client { } /// Add a custom header - pub fn add_header, V: AsRef>(&self, key: K, value: V) -> Self { + pub fn add_header, V: AsRef>(&self, key: K, value: V) -> Result { use reqwest::header::{HeaderName, HeaderValue}; let key = key.as_ref().to_string(); let value = value.as_ref().to_string(); + let header_name: HeaderName = key.parse()?; + let header_value: HeaderValue = value.parse()?; + self.state.rcu(|state| { let mut next = (**state).clone(); - if let (Ok(header_name), Ok(header_value)) = ( - key.parse::(), - value.parse::(), - ) { - next.config.headers.insert(header_name, header_value); - } + next.config.headers.insert(header_name.clone(), header_value.clone()); Arc::new(next) }); - self.clone() + Ok(self.clone()) } /// Get a copy of the current request headers diff --git a/templates/rust/src/error.rs.twig b/templates/rust/src/error.rs.twig index 0fb3d8edc4..4e5c37f77e 100644 --- a/templates/rust/src/error.rs.twig +++ b/templates/rust/src/error.rs.twig @@ -75,6 +75,12 @@ impl From for {{ spec.title | caseUcfirst } } } +impl From for {{ spec.title | caseUcfirst }}Error { + fn from(err: reqwest::header::InvalidHeaderName) -> Self { + Self::new(400, format!("Invalid header name: {}", err), None, String::new()) + } +} + /// {{ spec.title }} specific error response structure #[derive(Debug, serde::Deserialize)] pub struct ErrorResponse { diff --git a/templates/rust/tests/tests.rs b/templates/rust/tests/tests.rs index 5d76bbe31d..0e18779442 100644 --- a/templates/rust/tests/tests.rs +++ b/templates/rust/tests/tests.rs @@ -11,7 +11,7 @@ async fn main() -> Result<(), Box> { .set_endpoint("http://mockapi/v1")? .set_project("appwrite")? .set_key("apikey")? - .add_header("Origin", "http://localhost"); + .add_header("Origin", "http://localhost")?; println!("\n\nTest Started"); let sdk_headers = client.get_headers(); From 1d5abdadd7c7280d082859b33edfe89e39c619b8 Mon Sep 17 00:00:00 2001 From: yashksaini-coder Date: Tue, 7 Apr 2026 13:53:52 +0530 Subject: [PATCH 7/7] Improve error handling for header value parsing and update test for invalid endpoint --- templates/rust/src/client.rs.twig | 15 ++++++++++----- templates/rust/tests/tests.rs | 8 +++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/templates/rust/src/client.rs.twig b/templates/rust/src/client.rs.twig index 43c2055ce0..4fb7f8d439 100644 --- a/templates/rust/src/client.rs.twig +++ b/templates/rust/src/client.rs.twig @@ -168,7 +168,8 @@ impl Client { /// Set the project ID pub fn set_project>(&self, project: S) -> Result { let project = project.into(); - let value: reqwest::header::HeaderValue = project.parse()?; + let value: reqwest::header::HeaderValue = project.parse() + .map_err(|e| {{ spec.title | caseUcfirst }}Error::new(400, format!("Invalid header value for x-appwrite-project: {}", e), None, String::new()))?; self.state.rcu(|state| { let mut next = (**state).clone(); next.config.headers.insert("x-appwrite-project", value.clone()); @@ -180,7 +181,8 @@ impl Client { /// Set the API key pub fn set_key>(&self, key: S) -> Result { let key = key.into(); - let value: reqwest::header::HeaderValue = key.parse()?; + let value: reqwest::header::HeaderValue = key.parse() + .map_err(|e| {{ spec.title | caseUcfirst }}Error::new(400, format!("Invalid header value for x-appwrite-key: {}", e), None, String::new()))?; self.state.rcu(|state| { let mut next = (**state).clone(); next.config.headers.insert("x-appwrite-key", value.clone()); @@ -192,7 +194,8 @@ impl Client { /// Set the JWT token pub fn set_jwt>(&self, jwt: S) -> Result { let jwt = jwt.into(); - let value: reqwest::header::HeaderValue = jwt.parse()?; + let value: reqwest::header::HeaderValue = jwt.parse() + .map_err(|e| {{ spec.title | caseUcfirst }}Error::new(400, format!("Invalid header value for x-appwrite-jwt: {}", e), None, String::new()))?; self.state.rcu(|state| { let mut next = (**state).clone(); next.config.headers.insert("x-appwrite-jwt", value.clone()); @@ -204,7 +207,8 @@ impl Client { /// Set the locale pub fn set_locale>(&self, locale: S) -> Result { let locale = locale.into(); - let value: reqwest::header::HeaderValue = locale.parse()?; + let value: reqwest::header::HeaderValue = locale.parse() + .map_err(|e| {{ spec.title | caseUcfirst }}Error::new(400, format!("Invalid header value for x-appwrite-locale: {}", e), None, String::new()))?; self.state.rcu(|state| { let mut next = (**state).clone(); next.config.headers.insert("x-appwrite-locale", value.clone()); @@ -216,7 +220,8 @@ impl Client { /// Set the session pub fn set_session>(&self, session: S) -> Result { let session = session.into(); - let value: reqwest::header::HeaderValue = session.parse()?; + let value: reqwest::header::HeaderValue = session.parse() + .map_err(|e| {{ spec.title | caseUcfirst }}Error::new(400, format!("Invalid header value for x-appwrite-session: {}", e), None, String::new()))?; self.state.rcu(|state| { let mut next = (**state).clone(); next.config.headers.insert("x-appwrite-session", value.clone()); diff --git a/templates/rust/tests/tests.rs b/templates/rust/tests/tests.rs index 0e18779442..11317ebb1c 100644 --- a/templates/rust/tests/tests.rs +++ b/templates/rust/tests/tests.rs @@ -135,7 +135,13 @@ async fn test_general_service(client: &Client, string_in_array: &[String]) -> Re } match Client::new().set_endpoint("htp://cloud.appwrite.io/v1") { - Ok(_) => println!("ERROR: Expected validation failure for invalid endpoint but got Ok"), + Ok(_) => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Expected validation failure for invalid endpoint but got Ok", + ) + .into()) + }, Err(e) => println!("{}", e.message), }