Skip to main content

otelite_receiver/
error.rs

1//! Error types for the OTLP receiver
2
3use axum::{
4    http::StatusCode,
5    response::{IntoResponse, Response},
6    Json,
7};
8use serde_json::json;
9
10/// Result type alias for receiver operations
11pub type Result<T> = std::result::Result<T, ReceiverError>;
12
13/// Errors that can occur in the OTLP receiver
14#[derive(Debug, thiserror::Error)]
15pub enum ReceiverError {
16    /// Invalid OTLP protocol version
17    #[error("Invalid OTLP protocol version: {0}")]
18    InvalidProtocolVersion(String),
19
20    /// Failed to parse Protobuf message
21    #[error("Failed to parse Protobuf message: {0}")]
22    ProtobufParseError(#[from] prost::DecodeError),
23
24    /// Failed to parse JSON message
25    #[error("Failed to parse JSON message: {0}")]
26    JsonParseError(#[from] serde_json::Error),
27
28    /// Invalid content type
29    #[error("Invalid content type: {0}")]
30    InvalidContentType(String),
31
32    /// Message too large
33    #[error("Message too large: {size} bytes (max: {max} bytes)")]
34    MessageTooLarge { size: usize, max: usize },
35
36    /// Missing required field
37    #[error("Missing required field: {0}")]
38    MissingField(String),
39
40    /// Invalid signal type
41    #[error("Invalid signal type: {0}")]
42    InvalidSignalType(String),
43
44    /// gRPC server error
45    #[error("gRPC server error: {0}")]
46    GrpcError(#[from] tonic::transport::Error),
47
48    /// HTTP server error
49    #[error("HTTP server error: {0}")]
50    HttpError(String),
51
52    /// Compression error
53    #[error("Compression error: {0}")]
54    CompressionError(String),
55
56    /// Configuration error
57    #[error("Configuration error: {0}")]
58    ConfigError(String),
59
60    /// Internal error
61    #[error("Internal error: {0}")]
62    Internal(String),
63
64    /// Storage error
65    #[error("Storage error: {0}")]
66    StorageError(#[from] otelite_core::storage::StorageError),
67}
68
69impl ReceiverError {
70    /// Create a new HTTP error
71    pub fn http_error(msg: impl Into<String>) -> Self {
72        Self::HttpError(msg.into())
73    }
74
75    /// Create a new compression error
76    pub fn compression_error(msg: impl Into<String>) -> Self {
77        Self::CompressionError(msg.into())
78    }
79
80    /// Create a new configuration error
81    pub fn config_error(msg: impl Into<String>) -> Self {
82        Self::ConfigError(msg.into())
83    }
84
85    /// Create a new internal error
86    pub fn internal(msg: impl Into<String>) -> Self {
87        Self::Internal(msg.into())
88    }
89
90    /// Convert to gRPC status code
91    pub fn to_grpc_status(&self) -> tonic::Status {
92        match self {
93            Self::InvalidProtocolVersion(_) => tonic::Status::invalid_argument(self.to_string()),
94            Self::ProtobufParseError(_) => tonic::Status::invalid_argument(self.to_string()),
95            Self::JsonParseError(_) => tonic::Status::invalid_argument(self.to_string()),
96            Self::InvalidContentType(_) => tonic::Status::invalid_argument(self.to_string()),
97            Self::MessageTooLarge { .. } => tonic::Status::resource_exhausted(self.to_string()),
98            Self::MissingField(_) => tonic::Status::invalid_argument(self.to_string()),
99            Self::InvalidSignalType(_) => tonic::Status::invalid_argument(self.to_string()),
100            Self::GrpcError(_) => tonic::Status::internal(self.to_string()),
101            Self::HttpError(_) => tonic::Status::internal(self.to_string()),
102            Self::CompressionError(_) => tonic::Status::internal(self.to_string()),
103            Self::ConfigError(_) => tonic::Status::failed_precondition(self.to_string()),
104            Self::Internal(_) => tonic::Status::internal(self.to_string()),
105            Self::StorageError(_) => tonic::Status::internal(self.to_string()),
106        }
107    }
108
109    /// Convert to HTTP status code
110    pub fn to_http_status(&self) -> u16 {
111        match self {
112            Self::InvalidProtocolVersion(_) => 400,
113            Self::ProtobufParseError(_) => 400,
114            Self::JsonParseError(_) => 400,
115            Self::InvalidContentType(_) => 415, // Unsupported Media Type
116            Self::MessageTooLarge { .. } => 413, // Payload Too Large
117            Self::MissingField(_) => 400,
118            Self::InvalidSignalType(_) => 400,
119            Self::GrpcError(_) => 500,
120            Self::HttpError(_) => 500,
121            Self::CompressionError(_) => 500,
122            Self::ConfigError(_) => 500,
123            Self::Internal(_) => 500,
124            Self::StorageError(_) => 500,
125        }
126    }
127}
128
129// Implement IntoResponse for axum HTTP handlers
130impl IntoResponse for ReceiverError {
131    fn into_response(self) -> Response {
132        let status = StatusCode::from_u16(self.to_http_status())
133            .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
134
135        let body = Json(json!({
136            "error": self.to_string(),
137            "status": status.as_u16(),
138        }));
139
140        (status, body).into_response()
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_error_display() {
150        let err = ReceiverError::InvalidProtocolVersion("1.0.0".to_string());
151        assert_eq!(err.to_string(), "Invalid OTLP protocol version: 1.0.0");
152    }
153
154    #[test]
155    fn test_grpc_status_conversion() {
156        let err = ReceiverError::InvalidProtocolVersion("1.0.0".to_string());
157        let status = err.to_grpc_status();
158        assert_eq!(status.code(), tonic::Code::InvalidArgument);
159    }
160
161    #[test]
162    fn test_http_status_conversion() {
163        let err = ReceiverError::InvalidContentType("text/plain".to_string());
164        assert_eq!(err.to_http_status(), 415);
165
166        let err = ReceiverError::MessageTooLarge {
167            size: 20_000_000,
168            max: 10_000_000,
169        };
170        assert_eq!(err.to_http_status(), 413);
171    }
172}