apollo-errors 0.6.0

Structured error handling with automatic format conversion
Documentation
//! Tests for transparent variant support

mod common;

use apollo_errors::Error as ErrorTrait;
use common::{
    ConfigStructError, InnerError, SimpleStructError, TransparentToStructError,
    TransparentWrapperError,
};
use http::StatusCode;
use insta::assert_json_snapshot;

// ============================================================================
// Display trait tests
// ============================================================================

#[test]
fn test_transparent_display_forwards_to_inner() {
    let error = TransparentWrapperError::Inner(InnerError::DatabaseError);
    assert_eq!(error.to_string(), "Database connection failed");
}

#[test]
fn test_transparent_display_with_fields() {
    let error = TransparentWrapperError::Inner(InnerError::NetworkTimeout { timeout_ms: 5000 });
    assert_eq!(error.to_string(), "Network timeout after 5000ms");
}

// ============================================================================
// Error::source tests
// ============================================================================

#[test]
fn test_transparent_source_returns_inner() {
    let error = TransparentWrapperError::Inner(InnerError::DatabaseError);
    let source = std::error::Error::source(&error);
    assert!(source.is_some());
    assert_eq!(source.unwrap().to_string(), "Database connection failed");
}

// ============================================================================
// JSON format tests
// ============================================================================

#[test]
fn test_transparent_json_forwards_to_inner() {
    let error = TransparentWrapperError::Inner(InnerError::DatabaseError);
    let json = error.to_json().unwrap();
    assert_json_snapshot!(json, @r#"
    {
      "error": "db::connection_failed",
      "message": "Database connection failed"
    }
    "#);
}

#[test]
fn test_transparent_json_with_extension_fields() {
    let error = TransparentWrapperError::Inner(InnerError::NetworkTimeout { timeout_ms: 5000 });
    let json = error.to_json().unwrap();
    // Note: JSON uses the raw message template, fields are provided separately
    assert_json_snapshot!(json, @r#"
    {
      "error": "network::timeout",
      "message": "Network timeout after 5000ms",
      "timeout_ms": 5000
    }
    "#);
}

// ============================================================================
// GraphQL format tests
// ============================================================================

#[test]
fn test_transparent_graphql_forwards_to_inner() {
    let error = TransparentWrapperError::Inner(InnerError::DatabaseError);
    let graphql = error.to_graphql().unwrap();
    assert_json_snapshot!(graphql, @r#"
    {
      "extensions": {
        "code": "db::connection_failed"
      },
      "message": "Database connection failed"
    }
    "#);
}

#[test]
fn test_transparent_graphql_with_extension_fields() {
    let error = TransparentWrapperError::Inner(InnerError::NetworkTimeout { timeout_ms: 3000 });
    let graphql = error.to_graphql().unwrap();
    // Note: GraphQL uses the raw message template, fields are in extensions
    assert_json_snapshot!(graphql, @r#"
    {
      "extensions": {
        "code": "network::timeout",
        "timeout_ms": 3000
      },
      "message": "Network timeout after 3000ms"
    }
    "#);
}

// ============================================================================
// JSON-RPC format tests
// ============================================================================

#[test]
fn test_transparent_jsonrpc_forwards_to_inner() {
    let error = TransparentWrapperError::Inner(InnerError::DatabaseError);
    let jsonrpc = error.to_jsonrpc().unwrap();
    assert_json_snapshot!(jsonrpc, @r#"
    {
      "code": -32000,
      "data": {
        "diagnostic_code": "db::connection_failed"
      },
      "message": "Database connection failed"
    }
    "#);
}

#[test]
fn test_transparent_jsonrpc_with_extension_fields() {
    let error = TransparentWrapperError::Inner(InnerError::NetworkTimeout { timeout_ms: 5000 });
    let jsonrpc = error.to_jsonrpc().unwrap();
    assert_json_snapshot!(jsonrpc, @r#"
    {
      "code": -32000,
      "data": {
        "diagnostic_code": "network::timeout",
        "timeout_ms": 5000
      },
      "message": "Network timeout after 5000ms"
    }
    "#);
}

// ============================================================================
// HTTP status tests
// ============================================================================

#[test]
fn test_transparent_http_status_forwards_to_inner() {
    let error = TransparentWrapperError::Inner(InnerError::DatabaseError);
    assert_eq!(error.http_status(), StatusCode::SERVICE_UNAVAILABLE);
}

#[test]
fn test_transparent_http_status_with_different_status() {
    let error = TransparentWrapperError::Inner(InnerError::NetworkTimeout { timeout_ms: 1000 });
    assert_eq!(error.http_status(), StatusCode::GATEWAY_TIMEOUT);
}

// ============================================================================
// Text format tests
// ============================================================================

#[test]
fn test_transparent_text_forwards_to_inner() {
    let error = TransparentWrapperError::Inner(InnerError::DatabaseError);
    assert_eq!(
        error.to_text(),
        "[db::connection_failed] Database connection failed"
    );
}

// ============================================================================
// Diagnostic trait tests
// ============================================================================

#[test]
fn test_transparent_diagnostic_code_forwards_to_inner() {
    use miette::Diagnostic;

    let error = TransparentWrapperError::Inner(InnerError::DatabaseError);
    let code = error.code().map(|c| c.to_string());
    assert_eq!(code, Some("db::connection_failed".to_string()));
}

#[test]
fn test_transparent_diagnostic_help_forwards_to_inner() {
    use miette::Diagnostic;

    let error = TransparentWrapperError::Inner(InnerError::DatabaseError);
    let help = error.help().map(|h| h.to_string());
    assert_eq!(help, Some("Check your database credentials".to_string()));
}

// ============================================================================
// Regular variant still works
// ============================================================================

#[test]
fn test_regular_variant_in_mixed_enum_still_works() {
    let error = TransparentWrapperError::ApplicationError;
    let json = error.to_json().unwrap();
    assert_json_snapshot!(json, @r#"
    {
      "error": "app::error",
      "message": "Application error"
    }
    "#);
}

// ============================================================================
// Transparent variant wrapping struct tests
// ============================================================================

#[test]
fn test_transparent_to_struct_display() {
    let error = TransparentToStructError::Simple(SimpleStructError);
    assert_eq!(error.to_string(), "Simple struct error occurred");
}

#[test]
fn test_transparent_to_struct_with_fields_display() {
    let error = TransparentToStructError::Config(ConfigStructError {
        port: 9999,
        config_path: "/app/config.yaml".to_string(),
    });
    assert_eq!(error.to_string(), "Configuration error: invalid port 9999");
}

#[test]
fn test_transparent_to_struct_json() {
    let error = TransparentToStructError::Simple(SimpleStructError);
    let json = error.to_json().unwrap();
    assert_json_snapshot!(json, @r#"
    {
      "error": "structs::simple",
      "message": "Simple struct error occurred"
    }
    "#);
}

#[test]
fn test_transparent_to_struct_with_fields_json() {
    let error = TransparentToStructError::Config(ConfigStructError {
        port: 8080,
        config_path: "/etc/app.toml".to_string(),
    });
    let json = error.to_json().unwrap();
    assert_json_snapshot!(json, @r#"
    {
      "config_path": "/etc/app.toml",
      "error": "structs::config_error",
      "message": "Configuration error: invalid port 8080",
      "port": 8080
    }
    "#);
}

#[test]
fn test_transparent_to_struct_graphql() {
    let error = TransparentToStructError::Config(ConfigStructError {
        port: 443,
        config_path: "/config.json".to_string(),
    });
    let graphql = error.to_graphql().unwrap();
    assert_json_snapshot!(graphql, @r#"
    {
      "extensions": {
        "code": "structs::config_error",
        "config_path": "/config.json",
        "port": 443
      },
      "message": "Configuration error: invalid port 443"
    }
    "#);
}

#[test]
fn test_transparent_to_struct_jsonrpc() {
    let error = TransparentToStructError::Config(ConfigStructError {
        port: 443,
        config_path: "/config.json".to_string(),
    });
    let jsonrpc = error.to_jsonrpc().unwrap();
    assert_json_snapshot!(jsonrpc, @r#"
    {
      "code": -32000,
      "data": {
        "config_path": "/config.json",
        "diagnostic_code": "structs::config_error",
        "port": 443
      },
      "message": "Configuration error: invalid port 443"
    }
    "#);
}

#[test]
fn test_transparent_to_struct_http_status() {
    // SimpleStructError uses default 500
    let error = TransparentToStructError::Simple(SimpleStructError);
    assert_eq!(error.http_status(), StatusCode::INTERNAL_SERVER_ERROR);
}

#[test]
fn test_transparent_to_struct_custom_http_status() {
    // ConfigStructError has custom 400
    let error = TransparentToStructError::Config(ConfigStructError {
        port: 80,
        config_path: "config".to_string(),
    });
    assert_eq!(error.http_status(), StatusCode::BAD_REQUEST);
}

#[test]
fn test_transparent_to_struct_text() {
    let error = TransparentToStructError::Simple(SimpleStructError);
    assert_eq!(
        error.to_text(),
        "[structs::simple] Simple struct error occurred"
    );
}

#[test]
fn test_transparent_to_struct_diagnostic_code() {
    use miette::Diagnostic;

    let error = TransparentToStructError::Config(ConfigStructError {
        port: 80,
        config_path: "config".to_string(),
    });
    let code = error.code().map(|c| c.to_string());
    assert_eq!(code, Some("structs::config_error".to_string()));
}

#[test]
fn test_transparent_to_struct_diagnostic_help() {
    use miette::Diagnostic;

    let error = TransparentToStructError::Config(ConfigStructError {
        port: 80,
        config_path: "config".to_string(),
    });
    let help = error.help().map(|h| h.to_string());
    assert_eq!(help, Some("Check your configuration file".to_string()));
}

#[test]
fn test_transparent_to_struct_source_returns_inner() {
    let error = TransparentToStructError::Config(ConfigStructError {
        port: 80,
        config_path: "config".to_string(),
    });
    let source = std::error::Error::source(&error);
    assert!(source.is_some());
    assert_eq!(
        source.unwrap().to_string(),
        "Configuration error: invalid port 80"
    );
}

#[test]
fn test_direct_variant_in_transparent_to_struct_enum() {
    let error = TransparentToStructError::Direct;
    let json = error.to_json().unwrap();
    assert_json_snapshot!(json, @r#"
    {
      "error": "wrapper::direct",
      "message": "Direct enum error"
    }
    "#);
}