wave-api 0.1.0

Typed Rust client for the Wave Accounting GraphQL API
Documentation
use serde::Deserialize;

/// Errors returned by the Wave API client.
#[derive(Debug, thiserror::Error)]
pub enum WaveError {
    /// HTTP transport error from reqwest.
    #[error("HTTP error: {0}")]
    Http(#[from] reqwest::Error),

    /// JSON serialization/deserialization error.
    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),

    /// GraphQL-level errors returned in the `errors` array.
    #[error("GraphQL errors: {}", format_graphql_errors(.0))]
    GraphQL(Vec<GraphqlError>),

    /// Mutation returned `didSucceed: false` with input validation errors.
    #[error("Mutation failed: {}", format_input_errors(.0))]
    MutationFailed(Vec<InputError>),

    /// Authentication error (missing or invalid token).
    #[error("Authentication error: {0}")]
    Auth(String),

    /// Token refresh failed.
    #[error("Token refresh failed: {0}")]
    TokenRefresh(String),
}

/// A GraphQL error from the `errors` array in the response.
#[derive(Debug, Clone, Deserialize)]
pub struct GraphqlError {
    pub message: String,
    #[serde(default)]
    pub path: Vec<String>,
    pub extensions: Option<GraphqlErrorExtensions>,
}

/// Extension data on a GraphQL error.
#[derive(Debug, Clone, Deserialize)]
pub struct GraphqlErrorExtensions {
    pub id: Option<String>,
    pub code: Option<String>,
}

/// A mutation validation error from the `inputErrors` array.
#[derive(Debug, Clone, Deserialize)]
pub struct InputError {
    pub path: Option<Vec<String>>,
    pub message: Option<String>,
    pub code: Option<String>,
}

impl std::fmt::Display for GraphqlError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl std::fmt::Display for InputError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if let Some(msg) = &self.message {
            write!(f, "{msg}")?;
        }
        if let Some(path) = &self.path {
            write!(f, " (at {})", path.join("."))?;
        }
        Ok(())
    }
}

fn format_graphql_errors(errors: &[GraphqlError]) -> String {
    errors
        .iter()
        .map(|e| e.message.as_str())
        .collect::<Vec<_>>()
        .join("; ")
}

fn format_input_errors(errors: &[InputError]) -> String {
    errors
        .iter()
        .map(|e| format!("{e}"))
        .collect::<Vec<_>>()
        .join("; ")
}