Error Types
The Growatt API Rust SDK uses a custom error type GrowattError that covers all error scenarios you might encounter:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GrowattError {
#[error("HTTP request failed: {0}")]
RequestError(#[from] reqwest::Error),
#[error("JSON deserialization error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Authentication failed: {0}")]
AuthError(String),
#[error("Invalid response: {0}")]
InvalidResponse(String),
#[error("Not logged in")]
NotLoggedIn,
}
pub type Result<T> = std::result::Result<T, GrowattError>;
Error Variants
RequestError
Occurs when HTTP requests fail due to network issues, timeouts, or connection problems.
match client.get_plants().await {
Err(GrowattError::RequestError(err)) => {
eprintln!("Network error occurred: {}", err);
// Handle network failure (retry, fallback, etc.)
}
Ok(plants) => println!("Retrieved {} plants", plants.0.len()),
Err(e) => eprintln!("Other error: {}", e),
}
JsonError
Occurs when the API response cannot be deserialized into the expected data structure.
match client.get_plant("plant_id").await {
Err(GrowattError::JsonError(err)) => {
eprintln!("Failed to parse API response: {}", err);
// API response format may have changed
}
Ok(plant_data) => println!("Plant data: {:?}", plant_data),
Err(e) => eprintln!("Other error: {}", e),
}
AuthError
Occurs when authentication fails due to invalid credentials or authentication issues.
match client.login(username, password).await {
Err(GrowattError::AuthError(msg)) => {
eprintln!("Authentication failed: {}", msg);
// Prompt user for correct credentials
}
Ok(true) => println!("Login successful!"),
Ok(false) => println!("Login failed"),
Err(e) => eprintln!("Other error: {}", e),
}
InvalidResponse
Occurs when the API returns an unexpected response structure or empty data.
match client.get_plants().await {
Err(GrowattError::InvalidResponse(msg)) => {
eprintln!("Invalid API response: {}", msg);
// Response structure may be unexpected
}
Ok(plants) => println!("Retrieved plants"),
Err(e) => eprintln!("Other error: {}", e),
}
NotLoggedIn
Occurs when attempting to call API methods without being authenticated.
match client.get_plants().await {
Err(GrowattError::NotLoggedIn) => {
eprintln!("Not authenticated. Please login first.");
client.login(username, password).await?;
}
Ok(plants) => println!("Retrieved plants"),
Err(e) => eprintln!("Other error: {}", e),
}
The SDK automatically handles session expiry and re-authentication, so you typically won’t encounter NotLoggedIn errors if you’ve called login() at least once.
Complete Error Handling Example
Here’s a comprehensive example demonstrating proper error handling:
use growatt::{Growatt, GrowattError};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Growatt::new();
let username = "your_username";
let password = "your_password";
// Handle login errors
match client.login(username, password).await {
Ok(true) => {
println!("Login successful!");
},
Ok(false) => {
println!("Login failed! Check your credentials.");
return Ok(());
},
Err(err) => match err {
GrowattError::AuthError(msg) => {
eprintln!("Authentication error: {}", msg);
return Ok(());
},
GrowattError::RequestError(err) => {
eprintln!("Network error: {}", err);
return Ok(());
},
GrowattError::JsonError(err) => {
eprintln!("JSON parsing error: {}", err);
return Ok(());
},
GrowattError::InvalidResponse(msg) => {
eprintln!("Invalid API response: {}", msg);
return Ok(());
},
GrowattError::NotLoggedIn => {
eprintln!("Not logged in");
return Ok(());
}
}
}
// Handle API call errors
match client.get_plants().await {
Ok(plants) => {
println!("Found {} plants:", plants.0.len());
for plant in plants.0 {
println!("- {}: {}", plant.plant_id, plant.plant_name);
}
},
Err(e) => {
eprintln!("Error getting plants: {}", e);
}
}
// Logout (handle errors gracefully)
if let Err(e) = client.logout().await {
eprintln!("Error during logout: {}", e);
} else {
println!("Successfully logged out");
}
Ok(())
}
Best Practices
Use the ? operator for propagation: When you want errors to bubble up to the caller, use the ? operator for cleaner code:async fn get_plant_info(client: &mut Growatt, plant_id: &str) -> Result<(), GrowattError> {
let plant_data = client.get_plant(plant_id).await?;
// Process plant_data
Ok(())
}
Match specific errors when recovery is possible: When you can take corrective action, match specific error variants:match client.get_plants().await {
Ok(plants) => process_plants(plants),
Err(GrowattError::NotLoggedIn) => {
// Retry with authentication
client.login(username, password).await?;
client.get_plants().await?
},
Err(e) => return Err(e),
}
Log errors appropriately: Use different log levels based on error severity:use log::{error, warn, info};
match client.login(username, password).await {
Err(GrowattError::AuthError(msg)) => {
error!("Critical: Authentication failed - {}", msg);
},
Err(GrowattError::RequestError(err)) => {
warn!("Network issue (may be transient): {}", err);
},
Ok(_) => info!("Login successful"),
_ => {},
}
Error Context
For better error reporting in complex applications, consider adding context:
use anyhow::{Context, Result};
async fn fetch_plant_energy(client: &mut Growatt, plant_id: &str) -> Result<f64> {
let plant_data = client
.get_plant(plant_id)
.await
.context(format!("Failed to fetch plant data for {}", plant_id))?;
plant_data.total_energy
.ok_or_else(|| anyhow::anyhow!("No energy data available for plant {}", plant_id))
}
Retry Logic
For network-related errors, implement retry logic:
use tokio::time::{sleep, Duration};
async fn get_plants_with_retry(
client: &mut Growatt,
max_retries: u32,
) -> Result<growatt::PlantList, GrowattError> {
let mut attempts = 0;
loop {
match client.get_plants().await {
Ok(plants) => return Ok(plants),
Err(GrowattError::RequestError(e)) if attempts < max_retries => {
attempts += 1;
eprintln!("Request failed (attempt {}), retrying...", attempts);
sleep(Duration::from_secs(2u64.pow(attempts))).await;
}
Err(e) => return Err(e),
}
}
}