Skip to main content

steer_grpc/grpc/
error.rs

1use std::fmt;
2use thiserror::Error;
3
4#[derive(Debug)]
5pub struct GrpcStatus(Box<tonic::Status>);
6
7impl GrpcStatus {
8    pub fn code(&self) -> tonic::Code {
9        self.0.code()
10    }
11
12    pub fn message(&self) -> &str {
13        self.0.message()
14    }
15}
16
17impl fmt::Display for GrpcStatus {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        write!(
20            f,
21            "status: {:?}, message: \"{}\"",
22            self.code(),
23            self.message()
24        )
25    }
26}
27
28impl From<Box<tonic::Status>> for GrpcStatus {
29    fn from(status: Box<tonic::Status>) -> Self {
30        Self(status)
31    }
32}
33
34impl From<tonic::Status> for GrpcStatus {
35    fn from(status: tonic::Status) -> Self {
36        Self(Box::new(status))
37    }
38}
39
40impl From<GrpcStatus> for tonic::Status {
41    fn from(status: GrpcStatus) -> Self {
42        *status.0
43    }
44}
45
46#[derive(Error, Debug)]
47pub enum GrpcError {
48    #[error("Failed to connect to gRPC server: {0}")]
49    ConnectionFailed(#[from] tonic::transport::Error),
50
51    #[error("gRPC call failed: {0}")]
52    CallFailed(GrpcStatus),
53
54    #[error("Failed to convert message at index {index}: {reason}")]
55    MessageConversionFailed { index: usize, reason: String },
56
57    #[error("Session not found: {session_id}")]
58    SessionNotFound { session_id: String },
59
60    #[error("Invalid session state: {reason}")]
61    InvalidSessionState { reason: String },
62
63    #[error("Stream error: {0}")]
64    StreamError(String),
65
66    #[error("Conversion error: {0}")]
67    ConversionError(#[from] ConversionError),
68
69    #[error("Core error: {0}")]
70    CoreError(#[from] steer_core::error::Error),
71
72    #[error("Channel receive error: {0}")]
73    ChannelError(String),
74}
75
76#[derive(Error, Debug)]
77pub enum ConversionError {
78    #[error("Missing required field: {field}")]
79    MissingField { field: String },
80
81    #[error("Invalid enum value: {value} for {enum_name}")]
82    InvalidEnumValue { value: i32, enum_name: String },
83
84    #[error("JSON serialization error: {0}")]
85    JsonError(#[from] serde_json::Error),
86
87    #[error("Invalid variant: expected {expected}, got {actual}")]
88    InvalidVariant { expected: String, actual: String },
89
90    #[error("Missing oneof variant in {message}")]
91    MissingOneofVariant { message: String },
92
93    #[error("Invalid value '{value}' for field '{field}'")]
94    InvalidValue { field: String, value: String },
95
96    #[error("Invalid JSON for field '{field}': {error}")]
97    InvalidJson { field: String, error: String },
98
99    #[error("Invalid data: {message}")]
100    InvalidData { message: String },
101
102    #[error("Tool result conversion error: {0}")]
103    ToolResultConversion(String),
104}
105
106impl From<GrpcError> for tonic::Status {
107    fn from(err: GrpcError) -> Self {
108        match err {
109            GrpcError::ConnectionFailed(e) => {
110                tonic::Status::unavailable(format!("Connection failed: {e}"))
111            }
112            GrpcError::CallFailed(status) => status.into(),
113            GrpcError::MessageConversionFailed { index, reason } => {
114                tonic::Status::invalid_argument(format!(
115                    "Failed to convert message at index {index}: {reason}"
116                ))
117            }
118            GrpcError::SessionNotFound { session_id } => {
119                tonic::Status::not_found(format!("Session not found: {session_id}"))
120            }
121            GrpcError::InvalidSessionState { reason } => {
122                tonic::Status::failed_precondition(format!("Invalid session state: {reason}"))
123            }
124            GrpcError::StreamError(msg) => tonic::Status::internal(format!("Stream error: {msg}")),
125            GrpcError::ConversionError(e) => {
126                tonic::Status::invalid_argument(format!("Conversion error: {e}"))
127            }
128            GrpcError::CoreError(e) => tonic::Status::internal(format!("Core error: {e}")),
129            GrpcError::ChannelError(msg) => {
130                tonic::Status::internal(format!("Channel error: {msg}"))
131            }
132        }
133    }
134}
135
136impl From<Box<tonic::Status>> for GrpcError {
137    fn from(status: Box<tonic::Status>) -> Self {
138        GrpcError::CallFailed(GrpcStatus::from(status))
139    }
140}
141
142impl From<tonic::Status> for GrpcError {
143    fn from(status: tonic::Status) -> Self {
144        GrpcError::CallFailed(GrpcStatus::from(status))
145    }
146}