Loading pageā¦
Rust walkthroughs
Loading pageā¦
reqwest::Response::error_for_status and error_for_status_ref for HTTP error handling?reqwest::Response::error_for_status and error_for_status_ref both convert HTTP error status codes (4xx and 5xx) into Rust Error values, but they differ in ownership semantics: error_for_status consumes the Response and returns Result<Response, Error>, while error_for_status_ref borrows the response and returns Result<&Response, Error>. The consuming variant allows access to the response body on success, while the borrowing variant preserves the response for further inspection even after the status check, which is useful when you need to read the error body from the server regardless of status code.
use reqwest::StatusCode;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::get("https://httpbin.org/status/404").await?;
// error_for_status consumes the Response
// Returns Result<Response, Error>
match response.error_for_status() {
Ok(response) => {
println!("Success with status: {}", response.status());
// Can use response body here
}
Err(e) => {
// Error contains status information
println!("Request failed: {}", e);
}
}
// Response is consumed - can't use it after error_for_status
// println!("{:?}", response); // Error: value borrowed after move
Ok(())
}error_for_status takes ownership of the response and returns it on success.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::get("https://httpbin.org/status/404").await?;
// error_for_status_ref borrows the Response
// Returns Result<&Response, Error>
match response.error_for_status_ref() {
Ok(_) => {
println!("Success with status: {}", response.status());
}
Err(e) => {
println!("Request failed: {}", e);
// Response is still available for inspection
println!("Status: {}", response.status());
// Can still read the body
}
}
// Response is still available after error_for_status_ref
println!("Response status: {}", response.status());
Ok(())
}error_for_status_ref borrows the response, allowing continued use after the check.
use reqwest::Response;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::get("https://httpbin.org/get").await?;
// error_for_status: consumes Response, returns Result<Response, Error>
// fn error_for_status(self) -> Result<Response, Error>
let result: Result<Response, reqwest::Error> = response.error_for_status();
// error_for_status_ref: borrows Response, returns Result<&Response, Error>
// fn error_for_status_ref(&self) -> Result<&Response, Error>
let response2 = reqwest::get("https://httpbin.org/get").await?;
let result: Result<&Response, reqwest::Error> = response2.error_for_status_ref();
// Key difference:
// - error_for_status: self -> Result<Self, Error>
// - error_for_status_ref: &self -> Result<&Self, Error>
Ok(())
}The method signatures differ in ownership: self versus &self.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// error_for_status_ref allows reading body on error
let response = reqwest::get("https://httpbin.org/status/500").await?;
// Using error_for_status_ref to preserve response
if let Err(e) = response.error_for_status_ref() {
println!("Error: {}", e);
// Can still read the response body
let body = response.text().await?;
println!("Error body: {}", body);
// This is useful when servers return error details in the body
// e.g., {"error": "database connection failed", "code": "E123"}
}
// Contrast with error_for_status
let response = reqwest::get("https://httpbin.org/status/500").await?;
match response.error_for_status() {
Ok(_) => println!("Success"),
Err(e) => {
println!("Error: {}", e);
// Cannot read body - response was consumed
// let body = response.text().await?; // Error: value borrowed after move
}
}
Ok(())
}error_for_status_ref preserves the response for body reading on error.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// error_for_status enables clean chaining
let body = reqwest::get("https://httpbin.org/get")
.await?
.error_for_status()? // Consumes response, returns on error
.text() // Can only chain on success
.await?;
println!("Body: {}", body);
// error_for_status_ref requires different pattern for body access
let response = reqwest::get("https://httpbin.org/get").await?;
response.error_for_status_ref()?; // Only borrows
// Now need to use response separately
let body = response.text().await?;
println!("Body: {}", body);
Ok(())
}error_for_status enables method chaining; error_for_status_ref requires separate steps.
use reqwest::StatusCode;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::get("https://httpbin.org/status/403").await?;
// Use error_for_status_ref to inspect error response
if let Err(e) = response.error_for_status_ref() {
let status = response.status();
println!("Error: {}", e);
println!("Status: {} {}", status.as_u16(), status.canonical_reason().unwrap_or(""));
// Read the body to get more details
let body = response.text().await?;
println!("Response body: {}", body);
// Handle different error types
match status {
StatusCode::UNAUTHORIZED => {
println!("Authentication required");
}
StatusCode::FORBIDDEN => {
println!("Access denied");
}
StatusCode::NOT_FOUND => {
println!("Resource not found");
}
StatusCode::INTERNAL_SERVER_ERROR => {
println!("Server error");
}
_ => {
println!("Other error: {}", status);
}
}
}
Ok(())
}error_for_status_ref allows detailed error response inspection.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::get("https://httpbin.org/status/404").await?;
// Check status first with error_for_status_ref
let is_success = response.error_for_status_ref().is_ok();
if is_success {
// Success path: consume response and read body
// Can't do this directly - response is borrowed
// Need to consume after the check
let body = response.text().await?;
println!("Success body: {}", body);
} else {
// Error path: read error body
let status = response.status();
let body = response.text().await?;
println!("Error {} body: {}", status, body);
}
Ok(())
}Use error_for_status_ref when you need different handling for success and error bodies.
use reqwest::Response;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn log_error_response(response: &Response) -> Result<String, reqwest::Error> {
// Check status without consuming
if let Err(e) = response.error_for_status_ref() {
let status = response.status();
let body = response.text().await?;
// Log the error with full details
eprintln!("HTTP Error: {} {}", status.as_u16(), status.canonical_reason().unwrap_or(""));
eprintln!("Error body: {}", body);
eprintln!("Error: {}", e);
return Err(e);
}
// Success: read body
response.text().await
}
let response = reqwest::get("https://httpbin.org/status/500").await?;
let result = log_error_response(&response).await;
if result.is_err() {
println!("Request failed with logged details");
}
Ok(())
}error_for_status_ref enables error logging with response body access.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::get("https://httpbin.org/status/404").await?;
// Both methods return reqwest::Error on failure
// The error contains:
// - Status code (for HTTP errors)
// - URL that was requested
// - Whether it's a status error or other error
match response.error_for_status_ref() {
Ok(_) => println!("Success"),
Err(e) => {
// Error information
println!("Error: {}", e);
// Check if it's an HTTP status error
if let Some(status) = e.status() {
println!("HTTP status: {}", status);
}
// Get the URL
if let Some(url) = e.url() {
println!("URL: {}", url);
}
// Check error type
if e.is_status() {
println!("Status error");
}
if e.is_connect() {
println!("Connection error");
}
if e.is_timeout() {
println!("Timeout error");
}
}
}
Ok(())
}Both methods return the same reqwest::Error type with full error context.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// error_for_status can be used with ? for concise error propagation
let body = reqwest::get("https://httpbin.org/get")
.await?
.error_for_status()? // Propagates error, consumes response on success
.text()
.await?;
println!("Body: {}", body);
// error_for_status_ref requires response to remain borrowed
// Can't use ? with subsequent operations that need ownership
let response = reqwest::get("https://httpbin.org/get").await?;
let _ = response.error_for_status_ref()?; // Returns &Response
// Response is still borrowed, but we need to read body
// This works because text() takes ownership of Response
// But we can't use it after error_for_status_ref with ?
Ok(())
}error_for_status works cleanly with the ? operator for method chaining.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
// error_for_status works with custom clients
let response = client
.get("https://httpbin.org/status/200")
.send()
.await?;
let body = response.error_for_status()?.text().await?;
println!("Body: {}", body);
// error_for_status_ref for error handling
let response = client
.get("https://httpbin.org/status/404")
.send()
.await?;
if let Err(e) = response.error_for_status_ref() {
println!("Error: {}", e);
let error_body = response.text().await?;
println!("Error body: {}", error_body);
}
Ok(())
}Both methods work identically with custom reqwest::Client instances.
use reqwest::Response;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
async fn process_response(response: &Response) -> Result<String, reqwest::Error> {
// error_for_status_ref allows multiple operations on response
response.error_for_status_ref()?;
// Get headers before consuming body
let content_type = response.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
println!("Content-Type: {}", content_type);
println!("Status: {}", response.status());
// Now read body
// Note: text() takes ownership, so we need response to be owned
// This pattern works when response is passed by reference for checking
Ok(format!("Processed response with status {}", response.status()))
}
let response = reqwest::get("https://httpbin.org/get").await?;
let result = process_response(&response).await?;
println!("{}", result);
// Response still available for body reading after process_response
let body = response.text().await?;
println!("Body length: {}", body.len());
Ok(())
}error_for_status_ref enables multiple operations on the same response reference.
use reqwest::StatusCode;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// error_for_status and error_for_status_ref both handle 4xx and 5xx
// as errors; 1xx, 2xx, and 3xx are considered success
let statuses = [
("https://httpbin.org/status/200", "OK"),
("https://httpbin.org/status/201", "Created"),
("https://httpbin.org/status/301", "Redirect"),
("https://httpbin.org/status/400", "Bad Request"),
("https://httpbin.org/status/404", "Not Found"),
("https://httpbin.org/status/500", "Internal Server Error"),
];
for (url, desc) in statuses {
let response = reqwest::get(url).await?;
let status = response.status();
let result = response.error_for_status_ref();
match result {
Ok(_) => println!("{} ({}): Success", desc, status),
Err(e) => println!("{} ({}): Error - {}", desc, status, e),
}
}
Ok(())
}Both methods treat HTTP 4xx and 5xx status codes as errors.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Pattern 1: error_for_status with early return
let body = async {
let response = reqwest::get("https://httpbin.org/get").await?;
let response = response.error_for_status()?; // Consumes on success
let body = response.text().await?;
Ok::<_, reqwest::Error>(body)
}.await?;
// Pattern 2: error_for_status_ref with conditional
let response = reqwest::get("https://httpbin.org/get").await?;
if response.error_for_status_ref().is_ok() {
let body = response.text().await?;
println!("Body: {}", body);
} else {
let error_body = response.text().await?;
println!("Error body: {}", error_body);
}
// Pattern 3: error_for_status_ref with ? for references
fn check_status(response: &reqwest::Response) -> Result<(), reqwest::Error> {
response.error_for_status_ref()?;
Ok(())
}
let response = reqwest::get("https://httpbin.org/get").await?;
check_status(&response)?;
// Response still available
Ok(())
}Choose based on whether you need the response after the status check.
Method comparison:
| Aspect | error_for_status | error_for_status_ref |
|--------|-------------------|----------------------|
| Ownership | Consumes Response | Borrows &Response |
| Return type | Result<Response, Error> | Result<&Response, Error> |
| After error | Response consumed | Response still available |
| Method chaining | Clean (?.text()) | Requires separation |
| Body access on error | Not possible | Possible |
Use cases:
| Scenario | Recommended Method |
|----------|-------------------|
| Simple success path | error_for_status |
| Method chaining with ? | error_for_status |
| Reading error body | error_for_status_ref |
| Logging error details | error_for_status_ref |
| Inspecting headers on error | error_for_status_ref |
| Function taking &Response | error_for_status_ref |
Status code handling:
| Status Code | Result |
|-------------|--------|
| 1xx (Informational) | Ok |
| 2xx (Success) | Ok |
| 3xx (Redirect) | Ok |
| 4xx (Client Error) | Err |
| 5xx (Server Error) | Err |
Key insight: The difference between error_for_status and error_for_status_ref is purely about ownership semantics. error_for_status takes ownership of the Response, returning it on success and enabling clean method chaining (response.error_for_status()?.text().await?). error_for_status_ref borrows the response, preserving it for subsequent operationsāessential when you need to read the response body on error, log error details, or inspect headers regardless of the status code. The underlying error detection logic is identical: both methods check if the status code indicates an HTTP error (4xx or 5xx) and return the same reqwest::Error type. Use error_for_status when you only care about success and want concise chaining; use error_for_status_ref when error responses contain valuable information that needs to be logged or processed.