Skip to main content

nautilus_protocol/
error.rs

1//! Nautilus protocol errors with stable error codes.
2//!
3//! Error code ranges:
4//! - `1000..1999`: Schema / Validation errors
5//! - `2000..2999`: Query planning / rendering errors
6//! - `3000..3999`: Database execution errors
7//! - `9000..9999`: Internal engine errors
8//!
9//! Standard JSON-RPC errors (negative codes) are reserved for protocol-level issues.
10
11use thiserror::Error;
12
13use crate::wire::RpcError;
14
15// Error code constants
16
17// Schema / Validation errors (1000-1999)
18pub const ERR_SCHEMA_VALIDATION: i32 = 1000;
19pub const ERR_INVALID_MODEL: i32 = 1001;
20pub const ERR_INVALID_FIELD: i32 = 1002;
21pub const ERR_TYPE_MISMATCH: i32 = 1003;
22
23// Query planning / rendering errors (2000-2999)
24pub const ERR_QUERY_PLANNING: i32 = 2000;
25pub const ERR_INVALID_FILTER: i32 = 2001;
26pub const ERR_INVALID_ORDERBY: i32 = 2002;
27pub const ERR_UNSUPPORTED_OPERATION: i32 = 2003;
28
29// Database execution errors (3000-3999)
30pub const ERR_DATABASE_EXECUTION: i32 = 3000;
31pub const ERR_CONNECTION_FAILED: i32 = 3001;
32pub const ERR_CONSTRAINT_VIOLATION: i32 = 3002;
33pub const ERR_QUERY_TIMEOUT: i32 = 3003;
34pub const ERR_RECORD_NOT_FOUND: i32 = 3004;
35pub const ERR_UNIQUE_CONSTRAINT: i32 = 3005;
36pub const ERR_FOREIGN_KEY_CONSTRAINT: i32 = 3006;
37pub const ERR_CHECK_CONSTRAINT: i32 = 3007;
38pub const ERR_NULL_CONSTRAINT: i32 = 3008;
39pub const ERR_DEADLOCK: i32 = 3009;
40pub const ERR_SERIALIZATION_FAILURE: i32 = 3010;
41
42// Transaction errors (4000-4999)
43pub const ERR_TRANSACTION_NOT_FOUND: i32 = 4001;
44pub const ERR_TRANSACTION_TIMEOUT: i32 = 4002;
45pub const ERR_TRANSACTION_ALREADY_CLOSED: i32 = 4003;
46pub const ERR_TRANSACTION_FAILED: i32 = 4004;
47
48// Internal engine errors (9000-9999)
49pub const ERR_INTERNAL: i32 = 9000;
50pub const ERR_UNSUPPORTED_PROTOCOL_VERSION: i32 = 9001;
51pub const ERR_INVALID_METHOD: i32 = 9002;
52pub const ERR_INVALID_REQUEST_PARAMS: i32 = 9003;
53
54/// Nautilus protocol error.
55#[derive(Debug, Error)]
56pub enum ProtocolError {
57    #[error("Schema validation error: {0}")]
58    SchemaValidation(String),
59
60    #[error("Invalid model: {0}")]
61    InvalidModel(String),
62
63    #[error("Invalid field: {0}")]
64    InvalidField(String),
65
66    #[error("Type mismatch: {0}")]
67    TypeMismatch(String),
68
69    #[error("Query planning error: {0}")]
70    QueryPlanning(String),
71
72    #[error("Invalid filter: {0}")]
73    InvalidFilter(String),
74
75    #[error("Invalid order by: {0}")]
76    InvalidOrderBy(String),
77
78    #[error("Unsupported operation: {0}")]
79    UnsupportedOperation(String),
80
81    #[error("Database execution error: {0}")]
82    DatabaseExecution(String),
83
84    #[error("Connection failed: {0}")]
85    ConnectionFailed(String),
86
87    #[error("Constraint violation: {0}")]
88    ConstraintViolation(String),
89
90    #[error("Unique constraint violation: {0}")]
91    UniqueConstraintViolation(String),
92
93    #[error("Foreign key constraint violation: {0}")]
94    ForeignKeyConstraintViolation(String),
95
96    #[error("Check constraint violation: {0}")]
97    CheckConstraintViolation(String),
98
99    #[error("NOT NULL constraint violation: {0}")]
100    NullConstraintViolation(String),
101
102    #[error("Deadlock detected: {0}")]
103    Deadlock(String),
104
105    #[error("Serialization failure: {0}")]
106    SerializationFailure(String),
107
108    #[error("Query timeout: {0}")]
109    QueryTimeout(String),
110
111    #[error("Record not found: {0}")]
112    RecordNotFound(String),
113
114    #[error("Transaction not found: {0}")]
115    TransactionNotFound(String),
116
117    #[error("Transaction timed out: {0}")]
118    TransactionTimeout(String),
119
120    #[error("Transaction already closed: {0}")]
121    TransactionAlreadyClosed(String),
122
123    #[error("Transaction failed: {0}")]
124    TransactionFailed(String),
125
126    #[error("Internal engine error: {0}")]
127    Internal(String),
128
129    #[error("Unsupported protocol version: {actual}, expected {expected}")]
130    UnsupportedProtocolVersion { actual: u32, expected: u32 },
131
132    #[error("Invalid method: {0}")]
133    InvalidMethod(String),
134
135    #[error("Invalid request params: {0}")]
136    InvalidParams(String),
137
138    #[error("Serialization error: {0}")]
139    Serialization(String),
140}
141
142impl ProtocolError {
143    /// Get the error code for this error.
144    pub fn code(&self) -> i32 {
145        match self {
146            ProtocolError::SchemaValidation(_) => ERR_SCHEMA_VALIDATION,
147            ProtocolError::InvalidModel(_) => ERR_INVALID_MODEL,
148            ProtocolError::InvalidField(_) => ERR_INVALID_FIELD,
149            ProtocolError::TypeMismatch(_) => ERR_TYPE_MISMATCH,
150            ProtocolError::QueryPlanning(_) => ERR_QUERY_PLANNING,
151            ProtocolError::InvalidFilter(_) => ERR_INVALID_FILTER,
152            ProtocolError::InvalidOrderBy(_) => ERR_INVALID_ORDERBY,
153            ProtocolError::UnsupportedOperation(_) => ERR_UNSUPPORTED_OPERATION,
154            ProtocolError::DatabaseExecution(_) => ERR_DATABASE_EXECUTION,
155            ProtocolError::ConnectionFailed(_) => ERR_CONNECTION_FAILED,
156            ProtocolError::ConstraintViolation(_) => ERR_CONSTRAINT_VIOLATION,
157            ProtocolError::UniqueConstraintViolation(_) => ERR_UNIQUE_CONSTRAINT,
158            ProtocolError::ForeignKeyConstraintViolation(_) => ERR_FOREIGN_KEY_CONSTRAINT,
159            ProtocolError::CheckConstraintViolation(_) => ERR_CHECK_CONSTRAINT,
160            ProtocolError::NullConstraintViolation(_) => ERR_NULL_CONSTRAINT,
161            ProtocolError::Deadlock(_) => ERR_DEADLOCK,
162            ProtocolError::SerializationFailure(_) => ERR_SERIALIZATION_FAILURE,
163            ProtocolError::QueryTimeout(_) => ERR_QUERY_TIMEOUT,
164            ProtocolError::RecordNotFound(_) => ERR_RECORD_NOT_FOUND,
165            ProtocolError::TransactionNotFound(_) => ERR_TRANSACTION_NOT_FOUND,
166            ProtocolError::TransactionTimeout(_) => ERR_TRANSACTION_TIMEOUT,
167            ProtocolError::TransactionAlreadyClosed(_) => ERR_TRANSACTION_ALREADY_CLOSED,
168            ProtocolError::TransactionFailed(_) => ERR_TRANSACTION_FAILED,
169            ProtocolError::Internal(_) => ERR_INTERNAL,
170            ProtocolError::UnsupportedProtocolVersion { .. } => ERR_UNSUPPORTED_PROTOCOL_VERSION,
171            ProtocolError::InvalidMethod(_) => ERR_INVALID_METHOD,
172            ProtocolError::InvalidParams(_) => ERR_INVALID_REQUEST_PARAMS,
173            ProtocolError::Serialization(_) => ERR_INTERNAL,
174        }
175    }
176}
177
178impl From<ProtocolError> for RpcError {
179    fn from(err: ProtocolError) -> Self {
180        RpcError {
181            code: err.code(),
182            message: err.to_string(),
183            data: None,
184        }
185    }
186}
187
188impl From<serde_json::Error> for ProtocolError {
189    fn from(err: serde_json::Error) -> Self {
190        ProtocolError::Serialization(err.to_string())
191    }
192}
193
194/// Result type alias for protocol operations.
195pub type Result<T> = std::result::Result<T, ProtocolError>;
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use serde_json::Value;
201
202    #[test]
203    fn test_error_codes() {
204        assert_eq!(
205            ProtocolError::SchemaValidation("test".into()).code(),
206            ERR_SCHEMA_VALIDATION
207        );
208        assert_eq!(
209            ProtocolError::InvalidModel("User".into()).code(),
210            ERR_INVALID_MODEL
211        );
212        assert_eq!(
213            ProtocolError::QueryPlanning("bad query".into()).code(),
214            ERR_QUERY_PLANNING
215        );
216        assert_eq!(
217            ProtocolError::DatabaseExecution("timeout".into()).code(),
218            ERR_DATABASE_EXECUTION
219        );
220        assert_eq!(
221            ProtocolError::UnsupportedProtocolVersion {
222                actual: 2,
223                expected: 1
224            }
225            .code(),
226            ERR_UNSUPPORTED_PROTOCOL_VERSION
227        );
228    }
229
230    #[test]
231    fn test_error_to_rpc_error() {
232        let err = ProtocolError::InvalidModel("Post".to_string());
233        let rpc_err: RpcError = err.into();
234
235        assert_eq!(rpc_err.code, ERR_INVALID_MODEL);
236        assert_eq!(rpc_err.message, "Invalid model: Post");
237        assert!(rpc_err.data.is_none());
238    }
239
240    #[test]
241    fn test_error_display() {
242        let err = ProtocolError::UnsupportedProtocolVersion {
243            actual: 3,
244            expected: 1,
245        };
246        assert_eq!(
247            err.to_string(),
248            "Unsupported protocol version: 3, expected 1"
249        );
250    }
251
252    #[test]
253    fn test_serde_error_conversion() {
254        let bad_json = "{invalid json}";
255        let err: serde_json::Error = serde_json::from_str::<Value>(bad_json).unwrap_err();
256        let protocol_err: ProtocolError = err.into();
257
258        match protocol_err {
259            ProtocolError::Serialization(msg) => {
260                assert!(!msg.is_empty());
261            }
262            _ => panic!("Expected Serialization error"),
263        }
264    }
265}