Skip to content

fix(rust): replace unwrap/panic with Result-based error handling#1398

Open
yashksaini-coder wants to merge 3 commits intoappwrite:masterfrom
yashksaini-coder:fix/rust-result-error-handling
Open

fix(rust): replace unwrap/panic with Result-based error handling#1398
yashksaini-coder wants to merge 3 commits intoappwrite:masterfrom
yashksaini-coder:fix/rust-result-error-handling

Conversation

@yashksaini-coder
Copy link
Copy Markdown

@yashksaini-coder yashksaini-coder commented Mar 27, 2026

Summary

  • Replace .parse().unwrap() and panic! with Result<Self> returns in all user-input client setters (set_endpoint, set_project, set_key, set_jwt, set_locale, set_session)
  • Add From<reqwest::header::InvalidHeaderValue> impl to the error type
  • Add // SAFETY: comment for compile-time constant header values in new()
  • Update tests to actually validate invalid endpoint instead of faking output with println!

Test plan

  • Regenerated Rust SDK — examples/rust/ output verified
  • Twig linter passes (0 errors)
  • PHPUnit spec assertions pass (542/542)
  • Inline unit tests added for invalid endpoint and invalid header value

Closes #1397

Summary by CodeRabbit

  • New Features
    • Rust SDK client configuration now validates endpoints and headers, reporting errors instead of panicking during setup. Configuration methods enable graceful error handling through result types.

Copilot AI review requested due to automatic review settings March 27, 2026 10:04
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

This pull request converts six Rust client builder setter methods (set_endpoint, set_project, set_key, set_jwt, set_locale, set_session) from returning Self to returning Result<Self>. A new error trait implementation handles invalid header value conversion, and examples and tests are updated to use the ? operator for error propagation.

Changes

Cohort / File(s) Summary
Client Builder Methods
templates/rust/src/client.rs.twig
Changed six setter methods to return Result<Self> instead of Self. Added validation for endpoint URLs and header values; set_endpoint now returns 400 error for invalid prefixes, and header setters parse values without unwrap() to catch invalid characters (e.g., newlines). Updated existing test with .unwrap() calls and added tests for invalid endpoint and invalid header values.
Error Handling
templates/rust/src/error.rs.twig
Added From<reqwest::header::InvalidHeaderValue> trait implementation to convert invalid header values into errors with status code 400 and formatted message.
Examples and Tests
templates/rust/examples/basic_usage.rs.twig, templates/rust/src/lib.rs.twig, templates/rust/tests/tests.rs
Updated client initialization calls to propagate errors using the ? operator. Test code now unwraps results and validates error responses for invalid endpoint URLs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 With whiskers held high and eyes bright with glee,
No more panics and unwraps! Now errors run free!
Results flow through like carrots in spring,
Proper error handling—a cottontailed thing! 🌱✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: converting from unwrap/panic calls to Result-based error handling for client setters.
Linked Issues check ✅ Passed All objectives from issue #1397 are met: setter methods return Result instead of panicking, InvalidHeaderValue conversion is implemented, and tests validate error behavior.
Out of Scope Changes check ✅ Passed All changes are directly related to replacing unwrap/panic with Result-based error handling as specified in the linked issue.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Rust SDK template to avoid panics in user-input client setters by switching them to Result-returning APIs, improving error handling consistency and test validation for invalid inputs.

Changes:

  • Convert Client setters for endpoint/project/key/jwt/locale/session from infallible chaining to Result<Self> returns.
  • Add From<reqwest::header::InvalidHeaderValue> conversion to the SDK error type.
  • Update docs/examples/tests to handle fallible setters and add unit tests for invalid endpoint/header values.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
templates/rust/src/client.rs.twig Make setters fallible (Result<Self>) and add tests; add SAFETY note for header unwraps.
templates/rust/src/error.rs.twig Add error conversion from InvalidHeaderValue.
templates/rust/src/lib.rs.twig Update crate-level usage docs to use ? with fallible setters.
templates/rust/examples/basic_usage.rs.twig Update example code to use ? with fallible setters.
templates/rust/tests/tests.rs Update template test harness to compile with fallible setters and print invalid-endpoint behavior.
Comments suppressed due to low confidence (1)

templates/rust/src/client.rs.twig:95

  • The // SAFETY: comment claims the following header values are compile-time constants, but the user-agent header right after the loop includes runtime OS/ARCH values. Either scope the comment to only the truly constant spec headers (the loop), or adjust the comment/implementation so it accurately justifies every unwrap() that it is meant to cover.
        // 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 %}
        headers.insert("user-agent", format!("{{ spec.title }}RustSDK/{{ sdk.version }} ({}; {})", std::env::consts::OS, std::env::consts::ARCH).parse().unwrap());

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 10 to 14
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");
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client::new() builder chain uses .unwrap() for the fallible setters even though main() already returns Result. This reintroduces panics for what is now modeled as recoverable error handling; prefer propagating with ? (or explicitly handling the error) so the test harness matches the SDK’s intended usage.

Copilot uses AI. Check for mistakes.

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"),
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The match message for the success branch is inverted: Ok(_) indicates the invalid endpoint was accepted, but it prints "Expected error for invalid endpoint". This makes failures misleading; the Ok(_) arm should indicate unexpected success (or assert/return an error) and the Err(_) arm should be treated as the expected path.

Suggested change
Ok(_) => println!("Expected error for invalid endpoint"),
Ok(_) => println!("Unexpected success for invalid endpoint"),

Copilot uses AI. Check for mistakes.
Comment on lines +142 to 151
pub fn set_endpoint<S: Into<String>>(&self, endpoint: S) -> Result<Self> {
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(),
));
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_endpoint returns an error with message "Invalid endpoint URL" but the validation only checks the scheme prefix. This is misleading and allows other malformed URLs through until request time. Consider parsing/validating the endpoint with url::Url here (and still enforcing http/https), and include a clear message about the requirement (e.g., must start with http:// or https://).

Copilot uses AI. Check for mistakes.
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 27, 2026

Greptile Summary

This PR replaces panic! / .unwrap() with Result-based error handling across all six user-facing client setters (set_endpoint, set_project, set_key, set_jwt, set_locale, set_session) in the Rust SDK template, propagating errors via the SDK's own error type. The approach is sound and meaningfully improves ergonomics and safety for SDK consumers.

Key observations:

  • Core change is correct: All setters now return Result<Self>, callers use ? or .unwrap() explicitly, and two new unit tests exercise the error paths.
  • // SAFETY: comment is a Rust convention misuse: // SAFETY: is reserved for justifying unsafe blocks. These parse().unwrap() calls are entirely safe Rust, and the comment's claim of "compile-time constants" is inaccurate for the user-agent value (which is a runtime-formatted string).
  • From<reqwest::header::InvalidHeaderValue> impl goes unused: The From impl was added to error.rs.twig with the intent of providing richer error messages, but all setter methods use .map_err(|_| …) instead of ?, discarding the original error and bypassing the From conversion entirely.
  • Misleading test output message: The Ok(_) branch in the invalid-endpoint match block prints "Expected error for invalid endpoint", which sounds like a confirmation rather than an indication that validation unexpectedly passed.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style and quality suggestions that do not affect correctness or runtime behavior.

The core change — replacing panics with Result-based error handling — is correct and well-structured. The three issues found are all P2: a misused comment convention, an unused From impl that is a missed opportunity for better error messages rather than a bug, and a slightly misleading test print statement. None block a merge.

templates/rust/src/error.rs.twig and templates/rust/src/client.rs.twig — the unused From impl and the // SAFETY: comment are worth cleaning up before the next release.

Important Files Changed

Filename Overview
templates/rust/src/client.rs.twig Core change: all six user-facing setters now return Result<Self> instead of panicking; tests updated accordingly. Minor issues: // SAFETY: comment is Rust-convention-misuse and some unwrap() calls in new() are not covered by the comment.
templates/rust/src/error.rs.twig Adds From<reqwest::header::InvalidHeaderValue> impl, but none of the setter methods actually use it — they all use `map_err(
templates/rust/tests/tests.rs Updated to use unwrap() after each setter call and added a proper match block for the invalid endpoint case; however the Ok branch message is misleading.
templates/rust/src/lib.rs.twig Doc-comment example updated to propagate errors with ? — straightforward and correct.
templates/rust/examples/basic_usage.rs.twig Example updated to use ? on all setter calls — consistent with the new API and correct within the async fn main() -> Result<…> context.

Comments Outside Diff (1)

  1. templates/rust/src/client.rs.twig, line 91-99 (link)

    P2 // SAFETY: comment is misused and inaccurate

    In Rust, // SAFETY: is a community convention reserved exclusively for explaining why unsafe { ... } blocks are sound. Using it above ordinary safe Rust code (.parse().unwrap() is safe, not unsafe) is misleading and will confuse contributors who follow Clippy/rustc conventions.

    Additionally, the comment states these are "compile-time constants," but the user-agent value is a runtime formatted string (it embeds std::env::consts::OS and std::env::consts::ARCH). A plain comment would be more appropriate.

Reviews (1): Last reviewed commit: "fix(rust): update tests and examples for..." | Re-trigger Greptile

Comment on lines +72 to +76
impl From<reqwest::header::InvalidHeaderValue> for {{ spec.title | caseUcfirst }}Error {
fn from(err: reqwest::header::InvalidHeaderValue) -> Self {
Self::new(400, format!("Invalid header value: {}", err), None, String::new())
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 From<InvalidHeaderValue> impl is never exercised by the setters

None of the setter methods (set_project, set_key, set_jwt, set_locale, set_session) actually use this From impl. They all use .map_err(|_| AppwriteError::new(...)), which silently discards the original InvalidHeaderValue and its diagnostic message. Switching them to ? would leverage the From impl and produce richer error messages:

// Instead of:
let value = key.parse().map_err(|_| AppwriteError::new(400, "Invalid header value for x-appwrite-key", None, String::new()))?;

// Use the From impl:
let value: HeaderValue = key.parse()?;  // converts via From<InvalidHeaderValue>

Comment on lines +129 to +132
match Client::new().set_endpoint("htp://cloud.appwrite.io/v1") {
Ok(_) => println!("Expected error for invalid endpoint"),
Err(e) => println!("{}", e.message),
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Misleading Ok branch message in invalid-endpoint test

If set_endpoint unexpectedly returns Ok, the message "Expected error for invalid endpoint" reads as a confirmation rather than an indication that validation surprisingly passed.

Suggested change
match Client::new().set_endpoint("htp://cloud.appwrite.io/v1") {
Ok(_) => println!("Expected error for invalid endpoint"),
Err(e) => println!("{}", e.message),
}
match Client::new().set_endpoint("htp://cloud.appwrite.io/v1") {
Ok(_) => println!("ERROR: Expected validation failure for invalid endpoint but got Ok"),
Err(e) => println!("{}", e.message),
}

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
templates/rust/src/client.rs.twig (1)

269-286: Consider making add_header consistent with other setters.

The add_header method silently ignores invalid header names/values, while the new set_* methods return errors. This inconsistency could surprise users who expect uniform error handling.

Consider either returning Result<Self> or documenting the silent-failure behavior.

💡 Optional: Return Result for consistency
     /// Add a custom header
-    pub fn add_header<K: AsRef<str>, V: AsRef<str>>(&self, key: K, value: V) -> Self {
+    pub fn add_header<K: AsRef<str>, V: AsRef<str>>(&self, key: K, value: V) -> Result<Self> {
         use reqwest::header::{HeaderName, HeaderValue};
 
         let key = key.as_ref().to_string();
         let value = value.as_ref().to_string();
 
+        let header_name = key.parse::<HeaderName>().map_err(|_| {{ spec.title | caseUcfirst }}Error::new(
+            400, format!("Invalid header name: {}", key), None, String::new(),
+        ))?;
+        let header_value = value.parse::<HeaderValue>().map_err(|_| {{ spec.title | caseUcfirst }}Error::new(
+            400, format!("Invalid header value for {}", key), None, String::new(),
+        ))?;
+
         self.state.rcu(|state| {
             let mut next = (**state).clone();
-            if let (Ok(header_name), Ok(header_value)) = (
-                key.parse::<HeaderName>(),
-                value.parse::<HeaderValue>(),
-            ) {
-                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())
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@templates/rust/src/client.rs.twig` around lines 269 - 286, The add_header
method currently swallows parse failures; make it consistent with the set_*
setters by returning a Result<Self, E> instead of silently ignoring invalid
headers: change add_header to return Result<Self, Error> (use the
crate/common/error type or anyhow), perform key.parse::<HeaderName>() and
value.parse::<HeaderValue>() and if either fails return Err with that parse
error, otherwise insert into next.config.headers inside the rcu closure and
return Ok(self.clone()); update any callers/tests and the method doc comment to
reflect the new Result return signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@templates/rust/src/client.rs.twig`:
- Around line 269-286: The add_header method currently swallows parse failures;
make it consistent with the set_* setters by returning a Result<Self, E> instead
of silently ignoring invalid headers: change add_header to return Result<Self,
Error> (use the crate/common/error type or anyhow), perform
key.parse::<HeaderName>() and value.parse::<HeaderValue>() and if either fails
return Err with that parse error, otherwise insert into next.config.headers
inside the rcu closure and return Ok(self.clone()); update any callers/tests and
the method doc comment to reflect the new Result return signature.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 96b082c4-1325-4dcb-9a99-da2d59e49eb6

📥 Commits

Reviewing files that changed from the base of the PR and between 5899a28 and 7635e2c.

📒 Files selected for processing (5)
  • templates/rust/examples/basic_usage.rs.twig
  • templates/rust/src/client.rs.twig
  • templates/rust/src/error.rs.twig
  • templates/rust/src/lib.rs.twig
  • templates/rust/tests/tests.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Bug Report: Replace unwrap() calls with proper error handling in Client

2 participants