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}