lxy 0.1.1

A convenient async http and RPC framework in Rust
Documentation
use serde::{Deserialize, Serialize};

/// System-defined error codes that map to both HTTP and gRPC status codes.
///
/// These codes represent the high-level category of errors and are designed to work
/// across both HTTP and gRPC protocols. Unlike HTTP status codes, these error codes
/// are protocol-agnostic and provide semantic meaning that translates cleanly to both
/// HTTP status codes and gRPC status codes.
///
/// # Examples
///
/// ```
/// use lxy::error::ErrorCode;
/// use http::StatusCode;
///
/// let code = ErrorCode::ResourceNotFound;
/// assert_eq!(code.to_http_status(), StatusCode::NOT_FOUND);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ErrorCode {
  /// Invalid input or malformed request.
  ///
  /// Maps to:
  /// - HTTP 400 Bad Request
  /// - gRPC INVALID_ARGUMENT
  InvalidInput,

  /// Authentication required or authentication failed.
  ///
  /// Maps to:
  /// - HTTP 401 Unauthorized
  /// - gRPC UNAUTHENTICATED
  Unauthorized,

  /// Authenticated but lacks required permissions.
  ///
  /// Maps to:
  /// - HTTP 403 Forbidden
  /// - gRPC PERMISSION_DENIED
  Forbidden,

  /// Requested resource was not found.
  ///
  /// Maps to:
  /// - HTTP 404 Not Found
  /// - gRPC NOT_FOUND
  ResourceNotFound,

  /// Resource already exists or operation conflicts with current state.
  ///
  /// Maps to:
  /// - HTTP 409 Conflict
  /// - gRPC ALREADY_EXISTS
  Conflict,

  /// Rate limit exceeded.
  ///
  /// Maps to:
  /// - HTTP 429 Too Many Requests
  /// - gRPC RESOURCE_EXHAUSTED
  RateLimited,

  /// Internal server error.
  ///
  /// Maps to:
  /// - HTTP 500 Internal Server Error
  /// - gRPC INTERNAL
  Internal,

  /// Service is temporarily unavailable.
  ///
  /// Maps to:
  /// - HTTP 503 Service Unavailable
  /// - gRPC UNAVAILABLE
  Unavailable,

  /// Request timeout or deadline exceeded.
  ///
  /// Maps to:
  /// - HTTP 504 Gateway Timeout
  /// - gRPC DEADLINE_EXCEEDED
  Timeout,
}

impl ErrorCode {
  /// Converts the error code to the corresponding HTTP status code.
  ///
  /// # Examples
  ///
  /// ```
  /// use lxy::error::ErrorCode;
  /// use http::StatusCode;
  ///
  /// assert_eq!(
  ///     ErrorCode::InvalidInput.to_http_status(),
  ///     StatusCode::BAD_REQUEST
  /// );
  /// assert_eq!(
  ///     ErrorCode::ResourceNotFound.to_http_status(),
  ///     StatusCode::NOT_FOUND
  /// );
  /// ```
  #[cfg(feature = "http")]
  pub fn to_http_status(&self) -> http::StatusCode {
    use http::StatusCode;
    match self {
      Self::InvalidInput => StatusCode::BAD_REQUEST,
      Self::Unauthorized => StatusCode::UNAUTHORIZED,
      Self::Forbidden => StatusCode::FORBIDDEN,
      Self::ResourceNotFound => StatusCode::NOT_FOUND,
      Self::Conflict => StatusCode::CONFLICT,
      Self::RateLimited => StatusCode::TOO_MANY_REQUESTS,
      Self::Internal => StatusCode::INTERNAL_SERVER_ERROR,
      Self::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
      Self::Timeout => StatusCode::GATEWAY_TIMEOUT,
    }
  }

  /// Converts the error code to the corresponding gRPC status code.
  ///
  /// # Examples
  ///
  /// ```
  /// use lxy::error::ErrorCode;
  /// use tonic::Code;
  ///
  /// assert_eq!(
  ///     ErrorCode::InvalidInput.to_grpc_code(),
  ///     Code::InvalidArgument
  /// );
  /// assert_eq!(
  ///     ErrorCode::ResourceNotFound.to_grpc_code(),
  ///     Code::NotFound
  /// );
  /// ```
  #[cfg(feature = "grpc")]
  pub fn to_grpc_code(&self) -> tonic::Code {
    use tonic::Code;
    match self {
      Self::InvalidInput => Code::InvalidArgument,
      Self::Unauthorized => Code::Unauthenticated,
      Self::Forbidden => Code::PermissionDenied,
      Self::ResourceNotFound => Code::NotFound,
      Self::Conflict => Code::AlreadyExists,
      Self::RateLimited => Code::ResourceExhausted,
      Self::Internal => Code::Internal,
      Self::Unavailable => Code::Unavailable,
      Self::Timeout => Code::DeadlineExceeded,
    }
  }
}

impl std::fmt::Display for ErrorCode {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      Self::InvalidInput => write!(f, "InvalidInput"),
      Self::Unauthorized => write!(f, "Unauthorized"),
      Self::Forbidden => write!(f, "Forbidden"),
      Self::ResourceNotFound => write!(f, "ResourceNotFound"),
      Self::Conflict => write!(f, "Conflict"),
      Self::RateLimited => write!(f, "RateLimited"),
      Self::Internal => write!(f, "Internal"),
      Self::Unavailable => write!(f, "Unavailable"),
      Self::Timeout => write!(f, "Timeout"),
    }
  }
}