apollo-errors 0.6.0

Structured error handling with automatic format conversion
Documentation
//! Tests for error catalog functionality

mod common;

use apollo_errors::error_catalog;
use insta::assert_json_snapshot;

/// Helper to find an error type by name in the catalog
fn find_error_by_name<'a>(
    errors: &'a [serde_json::Value],
    name: &str,
) -> Option<&'a serde_json::Value> {
    errors
        .iter()
        .find(|e| e.get("typeName").and_then(|v| v.as_str()) == Some(name))
}

#[test]
fn test_simple_error_catalog() {
    let catalog = error_catalog();
    let catalog_json = serde_json::to_value(&catalog).unwrap();
    let errors = catalog_json.as_array().unwrap();
    let simple_error = find_error_by_name(errors, "SimpleError").unwrap();

    assert_json_snapshot!(simple_error, @r#"
    {
      "typeName": "SimpleError",
      "variants": [
        {
          "code": "errors::simple",
          "fields": [],
          "httpStatus": 500,
          "message": "Something went wrong",
          "name": "Simple"
        },
        {
          "code": "errors::another",
          "fields": [],
          "httpStatus": 500,
          "message": "Another error occurred",
          "name": "Another"
        }
      ]
    }
    "#);
}

#[test]
fn test_error_with_fields_catalog() {
    let catalog = error_catalog();
    let catalog_json = serde_json::to_value(&catalog).unwrap();
    let errors = catalog_json.as_array().unwrap();
    let error_with_fields = find_error_by_name(errors, "ErrorWithFields").unwrap();

    assert_json_snapshot!(error_with_fields, @r###"
    {
      "typeName": "ErrorWithFields",
      "variants": [
        {
          "code": "config::invalid_port",
          "fields": [
            {
              "isExtension": true,
              "outputName": "port",
              "rustName": "port",
              "ty": "u16"
            },
            {
              "isExtension": true,
              "outputName": "config_file",
              "rustName": "config_file",
              "ty": "String"
            }
          ],
          "httpStatus": 500,
          "message": "Invalid port",
          "name": "InvalidPort"
        },
        {
          "code": "config::missing",
          "fields": [
            {
              "isExtension": true,
              "outputName": "expected_path",
              "rustName": "expected_path",
              "ty": "String"
            }
          ],
          "httpStatus": 500,
          "message": "Missing configuration",
          "name": "MissingConfig"
        }
      ]
    }
    "###);
}

#[test]
fn test_error_with_status_catalog() {
    let catalog = error_catalog();
    let catalog_json = serde_json::to_value(&catalog).unwrap();
    let errors = catalog_json.as_array().unwrap();
    let error_with_status = find_error_by_name(errors, "ErrorWithStatus").unwrap();

    assert_json_snapshot!(error_with_status, @r#"
    {
      "typeName": "ErrorWithStatus",
      "variants": [
        {
          "code": "resource::not_found",
          "fields": [],
          "httpStatus": 404,
          "message": "Resource not found",
          "name": "NotFound"
        },
        {
          "code": "request::bad",
          "fields": [],
          "httpStatus": 400,
          "message": "Bad request",
          "name": "BadRequest"
        },
        {
          "code": "service::unavailable",
          "fields": [],
          "httpStatus": 503,
          "message": "Service unavailable",
          "name": "ServiceUnavailable"
        },
        {
          "code": "internal::error",
          "fields": [],
          "httpStatus": 500,
          "message": "Internal error",
          "name": "InternalError"
        }
      ]
    }
    "#);
}

#[test]
fn test_error_with_help_catalog() {
    let catalog = error_catalog();
    let catalog_json = serde_json::to_value(&catalog).unwrap();
    let errors = catalog_json.as_array().unwrap();
    let error_with_help = find_error_by_name(errors, "ErrorWithHelp").unwrap();

    assert_json_snapshot!(error_with_help, @r#"
    {
      "typeName": "ErrorWithHelp",
      "variants": [
        {
          "code": "config::error",
          "fields": [],
          "help": "Check your configuration file",
          "httpStatus": 500,
          "message": "Configuration error",
          "name": "ConfigError",
          "severity": "warning",
          "url": "https://docs.example.com/errors/config"
        }
      ]
    }
    "#);
}

#[test]
fn test_transparent_wrapper_error_catalog() {
    // Use error_catalog() which expands transparent variants
    let catalog = error_catalog();
    let catalog_json = serde_json::to_value(&catalog).unwrap();
    let errors = catalog_json.as_array().unwrap();
    let transparent_wrapper = find_error_by_name(errors, "TransparentWrapperError").unwrap();

    // Transparent variants are expanded to show the inner type's variants
    assert_json_snapshot!(transparent_wrapper, @r###"
    {
      "typeName": "TransparentWrapperError",
      "variants": [
        {
          "code": "app::error",
          "fields": [],
          "httpStatus": 500,
          "message": "Application error",
          "name": "ApplicationError"
        },
        {
          "code": "db::connection_failed",
          "fields": [],
          "help": "Check your database credentials",
          "httpStatus": 503,
          "message": "Database connection failed",
          "name": "DatabaseError"
        },
        {
          "code": "network::timeout",
          "fields": [
            {
              "isExtension": true,
              "outputName": "timeout_ms",
              "rustName": "timeout_ms",
              "ty": "u64"
            }
          ],
          "httpStatus": 504,
          "message": "Network timeout after {timeout_ms}ms",
          "name": "NetworkTimeout"
        }
      ]
    }
    "###);
}

#[test]
fn test_transparent_to_struct_error_catalog() {
    // Use error_catalog() which expands transparent variants pointing to structs
    let catalog = error_catalog();
    let catalog_json = serde_json::to_value(&catalog).unwrap();
    let errors = catalog_json.as_array().unwrap();
    let transparent_to_struct = find_error_by_name(errors, "TransparentToStructError").unwrap();

    // Transparent variants pointing to structs are expanded to show the struct's single variant
    assert_json_snapshot!(transparent_to_struct, @r###"
    {
      "typeName": "TransparentToStructError",
      "variants": [
        {
          "code": "wrapper::direct",
          "fields": [],
          "httpStatus": 500,
          "message": "Direct enum error",
          "name": "Direct"
        },
        {
          "code": "structs::config_error",
          "fields": [
            {
              "isExtension": true,
              "outputName": "port",
              "rustName": "port",
              "ty": "u16"
            },
            {
              "isExtension": true,
              "outputName": "config_path",
              "rustName": "config_path",
              "ty": "String"
            }
          ],
          "help": "Check your configuration file",
          "httpStatus": 400,
          "message": "Configuration error: invalid port {port}",
          "name": "ConfigStructError"
        },
        {
          "code": "structs::simple",
          "fields": [],
          "httpStatus": 500,
          "message": "Simple struct error occurred",
          "name": "SimpleStructError"
        }
      ]
    }
    "###);
}