Skip to main content
The GrowattError enum represents all possible errors that can occur when using the Growatt API client. It uses the thiserror crate for ergonomic error handling.

Error Types

#[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,
}
This enum is defined in src/lib.rs:14-30 and implements the Error and Debug traits via the thiserror crate.

Result Type

The library provides a convenient Result type alias:
pub type Result<T> = std::result::Result<T, GrowattError>;
All API methods return Result<T> where T is the expected success type.

Error Variants

RequestError
reqwest::Error
Wraps HTTP request errors from the underlying reqwest library. This includes network errors, timeouts, connection failures, and HTTP status errors.Common causes:
  • Network connectivity issues
  • DNS resolution failures
  • Request timeouts
  • Server unavailable (5xx errors)
  • Client errors (4xx errors)
JsonError
serde_json::Error
Wraps JSON parsing/serialization errors from serde_json. Occurs when the API returns unexpected or malformed JSON.Common causes:
  • API response format changed
  • Malformed JSON from server
  • Missing required fields in response
  • Type mismatches in deserialization
AuthError
String
Authentication failed. Contains a message describing the authentication failure.Common causes:
  • Invalid username or password
  • Account locked or disabled
  • Session expired on server side
  • Authentication token rejected
InvalidResponse
String
The API returned a valid HTTP response but the content was invalid or unexpected. Contains a message describing what was invalid.Common causes:
  • Empty response when data was expected
  • Missing expected fields in JSON structure
  • Null values where data was expected
  • Response indicates failure (e.g., result != 1)
NotLoggedIn
()
Attempted to call an API method without being authenticated. No credentials are stored to attempt automatic login.Common causes:
  • Calling API methods before login()
  • Session expired and no credentials available for auto-renewal
  • Explicit logout followed by API call

Error Handling Examples

Basic Match Pattern

use growatt::{Growatt, GrowattError};

#[tokio::main]
async fn main() {
    let mut client = Growatt::new();
    
    match client.login("username", "password").await {
        Ok(success) => {
            if success {
                println!("Login successful");
            } else {
                println!("Login failed");
            }
        },
        Err(err) => match err {
            GrowattError::AuthError(msg) => {
                eprintln!("Authentication error: {}", msg);
            },
            GrowattError::RequestError(err) => {
                eprintln!("Network error: {}", err);
            },
            GrowattError::JsonError(err) => {
                eprintln!("JSON parsing error: {}", err);
            },
            GrowattError::InvalidResponse(msg) => {
                eprintln!("Invalid API response: {}", msg);
            },
            GrowattError::NotLoggedIn => {
                eprintln!("Not logged in");
            },
        }
    }
}

Using the ? Operator

The ? operator can be used to propagate errors up the call stack:
async fn get_plant_info(plant_id: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut client = Growatt::new();
    
    // ? automatically converts GrowattError to Box<dyn Error>
    client.login("username", "password").await?;
    let plant_data = client.get_plant(plant_id).await?;
    
    println!("Plant: {:?}", plant_data);
    Ok(())
}

Handling Specific Errors

match client.get_plants().await {
    Ok(plants) => {
        println!("Found {} plants", plants.0.len());
    },
    Err(GrowattError::NotLoggedIn) => {
        // Automatically login and retry
        client.login("username", "password").await?;
        let plants = client.get_plants().await?;
        println!("Found {} plants", plants.0.len());
    },
    Err(GrowattError::RequestError(_)) => {
        eprintln!("Network error - check your connection");
    },
    Err(e) => {
        eprintln!("Unexpected error: {}", e);
    }
}

Logging Errors

use log::error;

if let Err(e) = client.login("username", "password").await {
    error!("Login failed: {}", e);
    
    // Log additional context based on error type
    match e {
        GrowattError::RequestError(ref req_err) => {
            error!("Request error details: {:?}", req_err);
        },
        GrowattError::AuthError(ref msg) => {
            error!("Auth failure reason: {}", msg);
        },
        _ => {}
    }
}

Retry Logic

use tokio::time::{sleep, Duration};

async fn login_with_retry(
    client: &mut Growatt, 
    username: &str, 
    password: &str,
    max_retries: u32
) -> Result<bool, GrowattError> {
    let mut attempts = 0;
    
    loop {
        match client.login(username, password).await {
            Ok(success) => return Ok(success),
            Err(GrowattError::RequestError(_)) if attempts < max_retries => {
                attempts += 1;
                eprintln!("Login attempt {} failed, retrying...", attempts);
                sleep(Duration::from_secs(2_u64.pow(attempts))).await;
            },
            Err(e) => return Err(e),
        }
    }
}

Error Conversion

The #[from] attribute on RequestError and JsonError enables automatic conversion:
// reqwest::Error automatically converts to GrowattError::RequestError
// serde_json::Error automatically converts to GrowattError::JsonError

// This means you can use ? with these types directly
async fn make_request() -> Result<serde_json::Value> {
    let response = reqwest::get("https://example.com").await?; // Auto-converts
    let json = response.json::<serde_json::Value>().await?;    // Auto-converts
    Ok(json)
}

Best Practices

Use the Result type alias for cleaner function signatures:
async fn my_function() -> growatt::Result<PlantData> { /* ... */ }
Instead of:
async fn my_function() -> Result<PlantData, GrowattError> { /* ... */ }
The NotLoggedIn error only occurs if you try to call API methods without credentials. If you use from_env() or store credentials in the client, automatic session renewal will prevent this error.
All errors implement the standard Error trait, so they can be used with error handling libraries like anyhow or eyre:
use anyhow::Result;

async fn my_function() -> Result<()> {
    let mut client = Growatt::new();
    client.login("user", "pass").await?; // Works with anyhow
    Ok(())
}

Build docs developers (and LLMs) love