Skip to main content

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),
        }
    }
}

Build docs developers (and LLMs) love