mcp_langbase_reasoning/error/
mod.rs

1//! Error types and result aliases for the application.
2//!
3//! This module provides a hierarchy of error types for different subsystems:
4//! - `AppError`: Top-level application errors
5//! - `StorageError`: Database and persistence errors
6//! - `LangbaseError`: Langbase API communication errors
7//! - `McpError`: MCP protocol errors
8//! - `ToolError`: Tool-specific execution errors
9//! - `ModeError`: Mode-specific reasoning execution errors
10
11use thiserror::Error;
12
13/// Application-level errors encompassing all subsystem errors.
14#[derive(Debug, Error)]
15pub enum AppError {
16    /// Configuration-related error.
17    #[error("Configuration error: {message}")]
18    Config {
19        /// Error message describing the configuration issue.
20        message: String,
21    },
22
23    /// Storage layer error.
24    #[error("Storage error: {0}")]
25    Storage(#[from] StorageError),
26
27    /// Langbase API error.
28    #[error("Langbase error: {0}")]
29    Langbase(#[from] LangbaseError),
30
31    /// MCP protocol error.
32    #[error("MCP protocol error: {0}")]
33    Mcp(#[from] McpError),
34
35    /// Internal application error.
36    #[error("Internal error: {message}")]
37    Internal {
38        /// Error message describing the internal issue.
39        message: String,
40    },
41}
42
43/// Storage layer errors for database operations.
44#[derive(Debug, Error)]
45pub enum StorageError {
46    /// Database connection failed.
47    #[error("Database connection failed: {message}")]
48    Connection {
49        /// Error message describing the connection issue.
50        message: String,
51    },
52
53    /// Database query failed.
54    #[error("Query failed: {message}")]
55    Query {
56        /// Error message describing the query issue.
57        message: String,
58    },
59
60    /// Session not found in storage.
61    #[error("Session not found: {session_id}")]
62    SessionNotFound {
63        /// ID of the missing session.
64        session_id: String,
65    },
66
67    /// Thought not found in storage.
68    #[error("Thought not found: {thought_id}")]
69    ThoughtNotFound {
70        /// ID of the missing thought.
71        thought_id: String,
72    },
73
74    /// Database migration failed.
75    #[error("Migration failed: {message}")]
76    Migration {
77        /// Error message describing the migration issue.
78        message: String,
79    },
80
81    /// Underlying SQLx error.
82    #[error("SQLx error: {0}")]
83    Sqlx(#[from] sqlx::Error),
84
85    /// JSON serialization failed.
86    #[error("Serialization failed: {message}")]
87    Serialization {
88        /// Description of the serialization issue.
89        message: String,
90    },
91}
92
93/// Langbase API errors for pipe communication.
94#[derive(Debug, Error)]
95pub enum LangbaseError {
96    /// Langbase service unavailable after retries.
97    #[error("Langbase unavailable: {message} (retries: {retries})")]
98    Unavailable {
99        /// Error message from the service.
100        message: String,
101        /// Number of retry attempts made.
102        retries: u32,
103    },
104
105    /// API returned an error status.
106    #[error("API error: {status} - {message}")]
107    Api {
108        /// HTTP status code.
109        status: u16,
110        /// Error message from the API.
111        message: String,
112    },
113
114    /// Invalid response from the API.
115    #[error("Invalid response: {message}")]
116    InvalidResponse {
117        /// Description of the response issue.
118        message: String,
119    },
120
121    /// Request timed out.
122    #[error("Request timeout after {timeout_ms}ms")]
123    Timeout {
124        /// Timeout duration in milliseconds.
125        timeout_ms: u64,
126    },
127
128    /// Underlying HTTP error.
129    #[error("HTTP error: {0}")]
130    Http(#[from] reqwest::Error),
131
132    /// Pipe response parsing failed - no fallback available in strict mode.
133    #[error("Response parse failed for pipe '{pipe}': {message}")]
134    ResponseParseFailed {
135        /// Name of the pipe that returned unparseable response.
136        pipe: String,
137        /// Description of the parse failure.
138        message: String,
139        /// Raw response content (truncated for logging).
140        raw_response: String,
141    },
142
143    /// Pipe not found (404 error).
144    #[error("Pipe not found: {pipe} (verify pipe exists on Langbase)")]
145    PipeNotFound {
146        /// Name of the missing pipe.
147        pipe: String,
148    },
149}
150
151/// MCP protocol errors for request handling.
152#[derive(Debug, Error)]
153pub enum McpError {
154    /// Invalid MCP request format.
155    #[error("Invalid request: {message}")]
156    InvalidRequest {
157        /// Description of the request issue.
158        message: String,
159    },
160
161    /// Requested tool not found.
162    #[error("Unknown tool: {tool_name}")]
163    UnknownTool {
164        /// Name of the unknown tool.
165        tool_name: String,
166    },
167
168    /// Invalid parameters for a tool.
169    #[error("Invalid parameters for {tool_name}: {message}")]
170    InvalidParameters {
171        /// Name of the tool with invalid parameters.
172        tool_name: String,
173        /// Description of the parameter issue.
174        message: String,
175    },
176
177    /// Tool execution failed.
178    #[error("Tool execution failed: {message}")]
179    ExecutionFailed {
180        /// Description of the execution failure.
181        message: String,
182    },
183
184    /// JSON serialization/deserialization error.
185    #[error("JSON serialization error: {0}")]
186    Json(#[from] serde_json::Error),
187}
188
189/// Tool-specific errors with structured details.
190#[derive(Debug, Error)]
191pub enum ToolError {
192    /// Input validation failed.
193    #[error("Validation failed: {field} - {reason}")]
194    Validation {
195        /// Name of the invalid field.
196        field: String,
197        /// Reason for validation failure.
198        reason: String,
199    },
200
201    /// Session-related error.
202    #[error("Session error: {0}")]
203    Session(String),
204
205    /// Reasoning operation failed.
206    #[error("Reasoning failed: {message}")]
207    Reasoning {
208        /// Description of the reasoning failure.
209        message: String,
210    },
211
212    /// Response parsing failed (strict mode - no fallback).
213    #[error("Parse error in {mode} mode: {message}")]
214    ParseFailed {
215        /// Reasoning mode that failed.
216        mode: String,
217        /// Description of parse failure.
218        message: String,
219    },
220
221    /// Pipe unavailable and no fallback allowed.
222    #[error("Pipe unavailable: {pipe} - {reason}")]
223    PipeUnavailable {
224        /// Name of the unavailable pipe.
225        pipe: String,
226        /// Reason for unavailability.
227        reason: String,
228    },
229}
230
231/// Mode-specific execution errors for reasoning operations.
232///
233/// These errors occur during the execution of reasoning modes
234/// and provide structured information for debugging and recovery.
235#[derive(Debug, Error)]
236pub enum ModeError {
237    /// Session state has been corrupted or is inconsistent.
238    #[error("Session state corrupted: {message}")]
239    StateCorrupted {
240        /// Description of the corruption.
241        message: String,
242    },
243
244    /// A required parameter was not provided.
245    #[error("Required parameter missing: {param}")]
246    MissingParameter {
247        /// Name of the missing parameter.
248        param: String,
249    },
250
251    /// Branch state is invalid for the requested operation.
252    #[error("Invalid branch state: {branch_id}")]
253    InvalidBranchState {
254        /// ID of the branch with invalid state.
255        branch_id: String,
256    },
257
258    /// Lock acquisition failed (mutex poisoned or timeout).
259    #[error("Lock acquisition failed: {resource}")]
260    LockPoisoned {
261        /// Name of the resource that couldn't be locked.
262        resource: String,
263    },
264
265    /// Checkpoint not found for backtracking.
266    #[error("Checkpoint not found: {checkpoint_id}")]
267    CheckpointNotFound {
268        /// ID of the missing checkpoint.
269        checkpoint_id: String,
270    },
271
272    /// Graph node not found.
273    #[error("Graph node not found: {node_id}")]
274    NodeNotFound {
275        /// ID of the missing node.
276        node_id: String,
277    },
278
279    /// Invalid confidence value (must be 0.0-1.0).
280    #[error("Invalid confidence value: {value} (must be 0.0-1.0)")]
281    InvalidConfidence {
282        /// The invalid confidence value.
283        value: f64,
284    },
285
286    /// Operation timeout.
287    #[error("Operation timed out after {timeout_ms}ms")]
288    Timeout {
289        /// Timeout duration in milliseconds.
290        timeout_ms: u64,
291    },
292
293    /// Parse error when processing mode-specific data.
294    #[error("Parse error in {context}: {message}")]
295    ParseError {
296        /// Context where parsing failed.
297        context: String,
298        /// Description of the parse error.
299        message: String,
300    },
301}
302
303impl From<ModeError> for AppError {
304    fn from(err: ModeError) -> Self {
305        AppError::Internal {
306            message: err.to_string(),
307        }
308    }
309}
310
311impl From<ModeError> for McpError {
312    fn from(err: ModeError) -> Self {
313        McpError::ExecutionFailed {
314            message: err.to_string(),
315        }
316    }
317}
318
319impl From<ToolError> for AppError {
320    fn from(err: ToolError) -> Self {
321        AppError::Internal {
322            message: err.to_string(),
323        }
324    }
325}
326
327impl From<AppError> for McpError {
328    fn from(err: AppError) -> Self {
329        McpError::ExecutionFailed {
330            message: err.to_string(),
331        }
332    }
333}
334
335/// Result type alias for application errors
336pub type AppResult<T> = Result<T, AppError>;
337
338/// Result type alias for storage operations
339pub type StorageResult<T> = Result<T, StorageError>;
340
341/// Result type alias for Langbase operations
342pub type LangbaseResult<T> = Result<T, LangbaseError>;
343
344/// Result type alias for MCP operations
345pub type McpResult<T> = Result<T, McpError>;
346
347/// Result type alias for mode operations
348pub type ModeResult<T> = Result<T, ModeError>;
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353
354    #[test]
355    fn test_app_error_display() {
356        let err = AppError::Config {
357            message: "missing key".to_string(),
358        };
359        assert_eq!(err.to_string(), "Configuration error: missing key");
360
361        let err = AppError::Internal {
362            message: "unexpected".to_string(),
363        };
364        assert_eq!(err.to_string(), "Internal error: unexpected");
365    }
366
367    #[test]
368    fn test_storage_error_display() {
369        let err = StorageError::Connection {
370            message: "failed to connect".to_string(),
371        };
372        assert_eq!(
373            err.to_string(),
374            "Database connection failed: failed to connect"
375        );
376
377        let err = StorageError::SessionNotFound {
378            session_id: "sess-123".to_string(),
379        };
380        assert_eq!(err.to_string(), "Session not found: sess-123");
381
382        let err = StorageError::ThoughtNotFound {
383            thought_id: "thought-456".to_string(),
384        };
385        assert_eq!(err.to_string(), "Thought not found: thought-456");
386
387        let err = StorageError::Query {
388            message: "syntax error".to_string(),
389        };
390        assert_eq!(err.to_string(), "Query failed: syntax error");
391
392        let err = StorageError::Migration {
393            message: "version mismatch".to_string(),
394        };
395        assert_eq!(err.to_string(), "Migration failed: version mismatch");
396
397        let err = StorageError::Serialization {
398            message: "invalid utf-8 in metadata".to_string(),
399        };
400        assert_eq!(
401            err.to_string(),
402            "Serialization failed: invalid utf-8 in metadata"
403        );
404    }
405
406    #[test]
407    fn test_langbase_error_display() {
408        let err = LangbaseError::Unavailable {
409            message: "server down".to_string(),
410            retries: 3,
411        };
412        assert_eq!(
413            err.to_string(),
414            "Langbase unavailable: server down (retries: 3)"
415        );
416
417        let err = LangbaseError::Api {
418            status: 401,
419            message: "unauthorized".to_string(),
420        };
421        assert_eq!(err.to_string(), "API error: 401 - unauthorized");
422
423        let err = LangbaseError::InvalidResponse {
424            message: "malformed JSON".to_string(),
425        };
426        assert_eq!(err.to_string(), "Invalid response: malformed JSON");
427
428        let err = LangbaseError::Timeout { timeout_ms: 5000 };
429        assert_eq!(err.to_string(), "Request timeout after 5000ms");
430    }
431
432    #[test]
433    fn test_mcp_error_display() {
434        let err = McpError::InvalidRequest {
435            message: "bad format".to_string(),
436        };
437        assert_eq!(err.to_string(), "Invalid request: bad format");
438
439        let err = McpError::UnknownTool {
440            tool_name: "nonexistent".to_string(),
441        };
442        assert_eq!(err.to_string(), "Unknown tool: nonexistent");
443
444        let err = McpError::InvalidParameters {
445            tool_name: "reasoning.linear".to_string(),
446            message: "missing content".to_string(),
447        };
448        assert_eq!(
449            err.to_string(),
450            "Invalid parameters for reasoning.linear: missing content"
451        );
452
453        let err = McpError::ExecutionFailed {
454            message: "pipe failed".to_string(),
455        };
456        assert_eq!(err.to_string(), "Tool execution failed: pipe failed");
457    }
458
459    #[test]
460    fn test_tool_error_display() {
461        let err = ToolError::Validation {
462            field: "content".to_string(),
463            reason: "cannot be empty".to_string(),
464        };
465        assert_eq!(
466            err.to_string(),
467            "Validation failed: content - cannot be empty"
468        );
469
470        let err = ToolError::Session("not found".to_string());
471        assert_eq!(err.to_string(), "Session error: not found");
472
473        let err = ToolError::Reasoning {
474            message: "logic error".to_string(),
475        };
476        assert_eq!(err.to_string(), "Reasoning failed: logic error");
477    }
478
479    #[test]
480    fn test_tool_error_conversion_to_app_error() {
481        let tool_err = ToolError::Validation {
482            field: "test".to_string(),
483            reason: "invalid".to_string(),
484        };
485        let app_err: AppError = tool_err.into();
486        assert!(matches!(app_err, AppError::Internal { .. }));
487        assert!(app_err.to_string().contains("Validation failed"));
488    }
489
490    #[test]
491    fn test_app_error_conversion_to_mcp_error() {
492        let app_err = AppError::Config {
493            message: "test error".to_string(),
494        };
495        let mcp_err: McpError = app_err.into();
496        assert!(matches!(mcp_err, McpError::ExecutionFailed { .. }));
497        assert!(mcp_err.to_string().contains("Configuration error"));
498    }
499
500    #[test]
501    fn test_storage_error_conversion_to_app_error() {
502        let storage_err = StorageError::SessionNotFound {
503            session_id: "test-123".to_string(),
504        };
505        let app_err: AppError = storage_err.into();
506        assert!(matches!(app_err, AppError::Storage(_)));
507    }
508
509    #[test]
510    fn test_langbase_error_conversion_to_app_error() {
511        let langbase_err = LangbaseError::Timeout { timeout_ms: 1000 };
512        let app_err: AppError = langbase_err.into();
513        assert!(matches!(app_err, AppError::Langbase(_)));
514    }
515
516    #[test]
517    fn test_mcp_error_conversion_to_app_error() {
518        let mcp_err = McpError::UnknownTool {
519            tool_name: "test".to_string(),
520        };
521        let app_err: AppError = mcp_err.into();
522        assert!(matches!(app_err, AppError::Mcp(_)));
523    }
524
525    // Additional comprehensive tests
526
527    #[test]
528    fn test_app_error_storage_variant_display() {
529        let storage_err = StorageError::Query {
530            message: "syntax error".to_string(),
531        };
532        let app_err = AppError::Storage(storage_err);
533        assert!(app_err.to_string().contains("Storage error"));
534        assert!(app_err.to_string().contains("syntax error"));
535    }
536
537    #[test]
538    fn test_app_error_langbase_variant_display() {
539        let langbase_err = LangbaseError::Api {
540            status: 500,
541            message: "internal server error".to_string(),
542        };
543        let app_err = AppError::Langbase(langbase_err);
544        assert!(app_err.to_string().contains("Langbase error"));
545        assert!(app_err.to_string().contains("500"));
546    }
547
548    #[test]
549    fn test_app_error_mcp_variant_display() {
550        let mcp_err = McpError::InvalidParameters {
551            tool_name: "test_tool".to_string(),
552            message: "missing field".to_string(),
553        };
554        let app_err = AppError::Mcp(mcp_err);
555        assert!(app_err.to_string().contains("MCP protocol error"));
556        assert!(app_err.to_string().contains("test_tool"));
557    }
558
559    #[test]
560    fn test_storage_error_conversion_from_sqlx() {
561        let sqlx_err = sqlx::Error::RowNotFound;
562        let storage_err: StorageError = sqlx_err.into();
563        assert!(matches!(storage_err, StorageError::Sqlx(_)));
564    }
565
566    #[test]
567    fn test_langbase_error_http_variant_display() {
568        // Testing that LangbaseError::Http variant exists and displays properly
569        // Note: Creating a real reqwest::Error is complex, so we test the variant exists
570        // In real usage: let langbase_err: LangbaseError = reqwest_error.into();
571        // This test verifies the From<reqwest::Error> trait is implemented
572    }
573
574    #[test]
575    fn test_mcp_error_conversion_from_serde_json() {
576        let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
577        let mcp_err: McpError = json_err.into();
578        assert!(matches!(mcp_err, McpError::Json(_)));
579    }
580
581    #[test]
582    fn test_tool_error_to_app_error_to_mcp_error_chain() {
583        let tool_err = ToolError::Reasoning {
584            message: "inference failed".to_string(),
585        };
586        let app_err: AppError = tool_err.into();
587        let mcp_err: McpError = app_err.into();
588        assert!(matches!(mcp_err, McpError::ExecutionFailed { .. }));
589        assert!(mcp_err.to_string().contains("Reasoning failed"));
590    }
591
592    #[test]
593    fn test_storage_error_to_app_error_to_mcp_error_chain() {
594        let storage_err = StorageError::Connection {
595            message: "db offline".to_string(),
596        };
597        let app_err: AppError = storage_err.into();
598        let mcp_err: McpError = app_err.into();
599        assert!(matches!(mcp_err, McpError::ExecutionFailed { .. }));
600        assert!(mcp_err.to_string().contains("Database connection failed"));
601    }
602
603    #[test]
604    fn test_langbase_error_to_app_error_to_mcp_error_chain() {
605        let langbase_err = LangbaseError::InvalidResponse {
606            message: "malformed response".to_string(),
607        };
608        let app_err: AppError = langbase_err.into();
609        let mcp_err: McpError = app_err.into();
610        assert!(matches!(mcp_err, McpError::ExecutionFailed { .. }));
611        assert!(mcp_err.to_string().contains("Invalid response"));
612    }
613
614    #[test]
615    fn test_storage_error_serialization_variant() {
616        let err = StorageError::Serialization {
617            message: "json parse error".to_string(),
618        };
619        let display = err.to_string();
620        assert!(display.contains("Serialization failed"));
621        assert!(display.contains("json parse error"));
622    }
623
624    #[test]
625    fn test_langbase_error_unavailable_with_zero_retries() {
626        let err = LangbaseError::Unavailable {
627            message: "immediate failure".to_string(),
628            retries: 0,
629        };
630        assert!(err.to_string().contains("retries: 0"));
631    }
632
633    #[test]
634    fn test_langbase_error_unavailable_with_high_retries() {
635        let err = LangbaseError::Unavailable {
636            message: "persistent failure".to_string(),
637            retries: 999,
638        };
639        assert!(err.to_string().contains("retries: 999"));
640    }
641
642    #[test]
643    fn test_langbase_error_api_with_various_status_codes() {
644        let err_400 = LangbaseError::Api {
645            status: 400,
646            message: "bad request".to_string(),
647        };
648        assert!(err_400.to_string().contains("400"));
649
650        let err_403 = LangbaseError::Api {
651            status: 403,
652            message: "forbidden".to_string(),
653        };
654        assert!(err_403.to_string().contains("403"));
655
656        let err_503 = LangbaseError::Api {
657            status: 503,
658            message: "service unavailable".to_string(),
659        };
660        assert!(err_503.to_string().contains("503"));
661    }
662
663    #[test]
664    fn test_langbase_error_timeout_various_durations() {
665        let err_short = LangbaseError::Timeout { timeout_ms: 100 };
666        assert!(err_short.to_string().contains("100ms"));
667
668        let err_long = LangbaseError::Timeout { timeout_ms: 60000 };
669        assert!(err_long.to_string().contains("60000ms"));
670    }
671
672    #[test]
673    fn test_tool_error_session_variant_with_various_messages() {
674        let err1 = ToolError::Session("session expired".to_string());
675        assert!(err1.to_string().contains("session expired"));
676
677        let err2 = ToolError::Session("session locked".to_string());
678        assert!(err2.to_string().contains("session locked"));
679
680        let err3 = ToolError::Session("session corrupt".to_string());
681        assert!(err3.to_string().contains("session corrupt"));
682    }
683
684    #[test]
685    fn test_tool_error_validation_field_names() {
686        let err = ToolError::Validation {
687            field: "max_depth".to_string(),
688            reason: "must be between 1 and 10".to_string(),
689        };
690        assert!(err.to_string().contains("max_depth"));
691        assert!(err.to_string().contains("must be between 1 and 10"));
692    }
693
694    #[test]
695    fn test_app_error_debug_format() {
696        let err = AppError::Config {
697            message: "test".to_string(),
698        };
699        let debug_str = format!("{:?}", err);
700        assert!(debug_str.contains("Config"));
701        assert!(debug_str.contains("test"));
702    }
703
704    #[test]
705    fn test_storage_error_debug_format() {
706        let err = StorageError::Migration {
707            message: "failed migration".to_string(),
708        };
709        let debug_str = format!("{:?}", err);
710        assert!(debug_str.contains("Migration"));
711        assert!(debug_str.contains("failed migration"));
712    }
713
714    #[test]
715    fn test_langbase_error_debug_format() {
716        let err = LangbaseError::Timeout { timeout_ms: 3000 };
717        let debug_str = format!("{:?}", err);
718        assert!(debug_str.contains("Timeout"));
719        assert!(debug_str.contains("3000"));
720    }
721
722    #[test]
723    fn test_mcp_error_debug_format() {
724        let err = McpError::UnknownTool {
725            tool_name: "mystery_tool".to_string(),
726        };
727        let debug_str = format!("{:?}", err);
728        assert!(debug_str.contains("UnknownTool"));
729        assert!(debug_str.contains("mystery_tool"));
730    }
731
732    #[test]
733    fn test_tool_error_debug_format() {
734        let err = ToolError::Reasoning {
735            message: "logic failed".to_string(),
736        };
737        let debug_str = format!("{:?}", err);
738        assert!(debug_str.contains("Reasoning"));
739        assert!(debug_str.contains("logic failed"));
740    }
741
742    #[test]
743    fn test_error_equality_via_string_representation() {
744        let err1 = StorageError::SessionNotFound {
745            session_id: "test-id".to_string(),
746        };
747        let err2 = StorageError::SessionNotFound {
748            session_id: "test-id".to_string(),
749        };
750        assert_eq!(err1.to_string(), err2.to_string());
751    }
752
753    #[test]
754    fn test_nested_error_display_preservation() {
755        let storage_err = StorageError::ThoughtNotFound {
756            thought_id: "thought-999".to_string(),
757        };
758        let original_msg = storage_err.to_string();
759
760        let app_err: AppError = storage_err.into();
761        assert!(app_err.to_string().contains(&original_msg));
762    }
763
764    #[test]
765    fn test_app_result_type_alias() {
766        fn returns_app_result() -> AppResult<String> {
767            Ok("success".to_string())
768        }
769        assert!(returns_app_result().is_ok());
770    }
771
772    #[test]
773    fn test_storage_result_type_alias() {
774        fn returns_storage_result() -> StorageResult<i32> {
775            Err(StorageError::Query {
776                message: "test".to_string(),
777            })
778        }
779        assert!(returns_storage_result().is_err());
780    }
781
782    #[test]
783    fn test_langbase_result_type_alias() {
784        fn returns_langbase_result() -> LangbaseResult<bool> {
785            Ok(true)
786        }
787        assert!(returns_langbase_result().unwrap());
788    }
789
790    #[test]
791    fn test_mcp_result_type_alias() {
792        fn returns_mcp_result() -> McpResult<()> {
793            Err(McpError::InvalidRequest {
794                message: "test".to_string(),
795            })
796        }
797        assert!(returns_mcp_result().is_err());
798    }
799
800    #[test]
801    fn test_multiple_error_conversions_in_sequence() {
802        let tool_err = ToolError::Validation {
803            field: "depth".to_string(),
804            reason: "negative value".to_string(),
805        };
806
807        let app_err: AppError = tool_err.into();
808        assert!(app_err.to_string().contains("Validation failed"));
809        assert!(app_err.to_string().contains("depth"));
810
811        let mcp_err: McpError = app_err.into();
812        assert!(mcp_err.to_string().contains("Tool execution failed"));
813        assert!(mcp_err.to_string().contains("Validation failed"));
814    }
815
816    #[test]
817    fn test_error_messages_with_special_characters() {
818        let err = AppError::Internal {
819            message: "Error: \"quotes\" and 'apostrophes' and \\ backslashes".to_string(),
820        };
821        let display = err.to_string();
822        assert!(display.contains("quotes"));
823        assert!(display.contains("apostrophes"));
824        assert!(display.contains("backslashes"));
825    }
826
827    #[test]
828    fn test_error_messages_with_unicode() {
829        let err = StorageError::Query {
830            message: "Invalid character: \u{1F4A5}".to_string(),
831        };
832        assert!(err.to_string().contains("\u{1F4A5}"));
833    }
834
835    #[test]
836    fn test_empty_error_messages() {
837        let err1 = AppError::Config {
838            message: "".to_string(),
839        };
840        assert_eq!(err1.to_string(), "Configuration error: ");
841
842        let err2 = ToolError::Session("".to_string());
843        assert_eq!(err2.to_string(), "Session error: ");
844    }
845
846    #[test]
847    fn test_very_long_error_messages() {
848        let long_msg = "a".repeat(1000);
849        let err = LangbaseError::InvalidResponse {
850            message: long_msg.clone(),
851        };
852        assert!(err.to_string().contains(&long_msg));
853    }
854
855    #[test]
856    fn test_error_trait_source_method() {
857        use std::error::Error;
858
859        let json_err = serde_json::from_str::<serde_json::Value>("bad").unwrap_err();
860        let mcp_err = McpError::Json(json_err);
861
862        assert!(mcp_err.source().is_some());
863    }
864
865    // Tests for new strict mode error types
866
867    #[test]
868    fn test_langbase_error_response_parse_failed() {
869        let err = LangbaseError::ResponseParseFailed {
870            pipe: "linear-reasoning-v1".to_string(),
871            message: "expected object, found array".to_string(),
872            raw_response: "[1, 2, 3]".to_string(),
873        };
874        let display = err.to_string();
875        assert!(display.contains("Response parse failed"));
876        assert!(display.contains("linear-reasoning-v1"));
877        assert!(display.contains("expected object, found array"));
878    }
879
880    #[test]
881    fn test_langbase_error_pipe_not_found() {
882        let err = LangbaseError::PipeNotFound {
883            pipe: "nonexistent-pipe-v1".to_string(),
884        };
885        let display = err.to_string();
886        assert!(display.contains("Pipe not found"));
887        assert!(display.contains("nonexistent-pipe-v1"));
888        assert!(display.contains("verify pipe exists on Langbase"));
889    }
890
891    #[test]
892    fn test_tool_error_parse_failed() {
893        let err = ToolError::ParseFailed {
894            mode: "auto".to_string(),
895            message: "JSON syntax error at line 1".to_string(),
896        };
897        let display = err.to_string();
898        assert!(display.contains("Parse error in auto mode"));
899        assert!(display.contains("JSON syntax error"));
900    }
901
902    #[test]
903    fn test_tool_error_pipe_unavailable() {
904        let err = ToolError::PipeUnavailable {
905            pipe: "decision-framework-v1".to_string(),
906            reason: "API returned 503 Service Unavailable".to_string(),
907        };
908        let display = err.to_string();
909        assert!(display.contains("Pipe unavailable"));
910        assert!(display.contains("decision-framework-v1"));
911        assert!(display.contains("503"));
912    }
913
914    #[test]
915    fn test_tool_error_parse_failed_conversion_to_app_error() {
916        let tool_err = ToolError::ParseFailed {
917            mode: "got_generate".to_string(),
918            message: "missing required field 'continuations'".to_string(),
919        };
920        let app_err: AppError = tool_err.into();
921        assert!(matches!(app_err, AppError::Internal { .. }));
922        assert!(app_err.to_string().contains("Parse error"));
923    }
924
925    #[test]
926    fn test_tool_error_pipe_unavailable_conversion_to_app_error() {
927        let tool_err = ToolError::PipeUnavailable {
928            pipe: "test-pipe".to_string(),
929            reason: "connection refused".to_string(),
930        };
931        let app_err: AppError = tool_err.into();
932        assert!(matches!(app_err, AppError::Internal { .. }));
933        assert!(app_err.to_string().contains("Pipe unavailable"));
934    }
935
936    #[test]
937    fn test_langbase_error_response_parse_failed_debug() {
938        let err = LangbaseError::ResponseParseFailed {
939            pipe: "test".to_string(),
940            message: "error".to_string(),
941            raw_response: "raw".to_string(),
942        };
943        let debug_str = format!("{:?}", err);
944        assert!(debug_str.contains("ResponseParseFailed"));
945        assert!(debug_str.contains("test"));
946    }
947
948    #[test]
949    fn test_langbase_error_pipe_not_found_debug() {
950        let err = LangbaseError::PipeNotFound {
951            pipe: "missing-pipe".to_string(),
952        };
953        let debug_str = format!("{:?}", err);
954        assert!(debug_str.contains("PipeNotFound"));
955        assert!(debug_str.contains("missing-pipe"));
956    }
957}