Skip to main content

graphrag_core/core/
error.rs

1//! Unified error handling for the GraphRAG system
2//!
3//! This module provides a centralized error type that encompasses all possible
4//! errors that can occur throughout the GraphRAG pipeline.
5
6use std::fmt;
7
8/// Main error type for the GraphRAG system
9#[derive(Debug)]
10pub enum GraphRAGError {
11    /// Configuration-related errors
12    Config {
13        /// Error message
14        message: String,
15    },
16
17    /// System not initialized error with helpful guidance
18    NotInitialized,
19
20    /// No documents added error with helpful guidance
21    NoDocuments,
22
23    /// I/O errors from file operations
24    Io(std::io::Error),
25
26    /// HTTP request errors
27    #[cfg(feature = "ureq")]
28    Http(Box<ureq::Error>),
29
30    /// HTTP request errors (WASM-compatible)
31    #[cfg(not(feature = "ureq"))]
32    Http(String),
33
34    /// JSON parsing/serialization errors
35    Json(json::Error),
36
37    /// Serde JSON errors
38    SerdeJson(serde_json::Error),
39
40    /// Text processing errors
41    TextProcessing {
42        /// Error message
43        message: String,
44    },
45
46    /// Graph construction and manipulation errors
47    GraphConstruction {
48        /// Error message
49        message: String,
50    },
51
52    /// Vector search and embedding errors
53    VectorSearch {
54        /// Error message
55        message: String,
56    },
57
58    /// Entity extraction errors
59    EntityExtraction {
60        /// Error message
61        message: String,
62    },
63
64    /// Retrieval system errors
65    Retrieval {
66        /// Error message
67        message: String,
68    },
69
70    /// Answer generation errors
71    Generation {
72        /// Error message
73        message: String,
74    },
75
76    /// Function calling errors
77    FunctionCall {
78        /// Error message
79        message: String,
80    },
81
82    /// Storage backend errors
83    Storage {
84        /// Error message
85        message: String,
86    },
87
88    /// Embedding model errors
89    Embedding {
90        /// Error message
91        message: String,
92    },
93
94    /// Language model errors
95    LanguageModel {
96        /// Error message
97        message: String,
98    },
99
100    /// Parallel processing errors
101    Parallel {
102        /// Error message
103        message: String,
104    },
105
106    /// Serialization errors
107    Serialization {
108        /// Error message
109        message: String,
110    },
111
112    /// Validation errors
113    Validation {
114        /// Error message
115        message: String,
116    },
117
118    /// Network connectivity errors
119    Network {
120        /// Error message
121        message: String,
122    },
123
124    /// Authentication/authorization errors
125    Auth {
126        /// Error message
127        message: String,
128    },
129
130    /// Resource not found errors
131    NotFound {
132        /// Resource type
133        resource: String,
134        /// Resource identifier
135        id: String,
136    },
137
138    /// Already exists errors
139    AlreadyExists {
140        /// Resource type
141        resource: String,
142        /// Resource identifier
143        id: String,
144    },
145
146    /// Operation timeout errors
147    Timeout {
148        /// Operation name
149        operation: String,
150        /// Timeout duration
151        duration: std::time::Duration,
152    },
153
154    /// Capacity/resource limit errors
155    ResourceLimit {
156        /// Resource name
157        resource: String,
158        /// Limit value
159        limit: usize,
160    },
161
162    /// Data corruption or integrity errors
163    DataCorruption {
164        /// Error message
165        message: String,
166    },
167
168    /// Unsupported operation errors
169    Unsupported {
170        /// Operation name
171        operation: String,
172        /// Reason for not supporting
173        reason: String,
174    },
175
176    /// Rate limiting errors
177    RateLimit {
178        /// Error message
179        message: String,
180    },
181
182    /// Conflict resolution errors
183    ConflictResolution {
184        /// Error message
185        message: String,
186    },
187
188    /// Incremental update errors
189    IncrementalUpdate {
190        /// Error message
191        message: String,
192    },
193}
194
195impl fmt::Display for GraphRAGError {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        match self {
198            GraphRAGError::Config { message } => {
199                write!(f, "Configuration error: {message}. \
200                          Solution: Check your config file or use default settings with GraphRAG::builder()")
201            },
202            GraphRAGError::NotInitialized => {
203                write!(
204                    f,
205                    "GraphRAG not initialized. \
206                          Solution: Call .initialize() or use .ask() which auto-initializes"
207                )
208            },
209            GraphRAGError::NoDocuments => {
210                write!(f, "No documents added. \
211                          Solution: Use .add_document(), .add_document_from_text(), or .from_file() to add content")
212            },
213            GraphRAGError::Io(err) => {
214                write!(
215                    f,
216                    "I/O error: {err}. \
217                          Solution: Check file permissions and that paths exist"
218                )
219            },
220            #[cfg(feature = "ureq")]
221            GraphRAGError::Http(err) => {
222                write!(
223                    f,
224                    "HTTP request error: {err}. \
225                          Solution: Check network connectivity and service availability"
226                )
227            },
228            #[cfg(not(feature = "ureq"))]
229            GraphRAGError::Http(msg) => {
230                write!(
231                    f,
232                    "HTTP request error: {msg}. \
233                          Solution: Check network connectivity and service availability"
234                )
235            },
236            GraphRAGError::Json(err) => {
237                write!(
238                    f,
239                    "JSON parsing error: {err}. \
240                          Solution: Verify JSON format or use default configuration"
241                )
242            },
243            GraphRAGError::SerdeJson(err) => {
244                write!(
245                    f,
246                    "JSON serialization error: {err}. \
247                          Solution: Verify data structure compatibility"
248                )
249            },
250            GraphRAGError::TextProcessing { message } => {
251                write!(
252                    f,
253                    "Text processing error: {message}. \
254                          Solution: Check text content and chunk size configuration"
255                )
256            },
257            GraphRAGError::GraphConstruction { message } => {
258                write!(
259                    f,
260                    "Graph construction error: {message}. \
261                          Solution: Initialize GraphRAG system and add documents first"
262                )
263            },
264            GraphRAGError::VectorSearch { message } => {
265                write!(f, "Vector search error: {message}. \
266                          Solution: Ensure embeddings are initialized with .initialize_embeddings()")
267            },
268            GraphRAGError::EntityExtraction { message } => {
269                write!(f, "Entity extraction error: {message}. \
270                          Solution: Check entity extraction configuration or use lower confidence threshold")
271            },
272            GraphRAGError::Retrieval { message } => {
273                write!(
274                    f,
275                    "Retrieval error: {message}. \
276                          Solution: Ensure documents are added and graph is built"
277                )
278            },
279            GraphRAGError::Generation { message } => {
280                write!(f, "Answer generation error: {message}. \
281                          Solution: Check LLM provider configuration or use GraphRAG::builder().auto_detect_llm()")
282            },
283            GraphRAGError::FunctionCall { message } => {
284                write!(f, "Function call error: {message}")
285            },
286            GraphRAGError::Storage { message } => {
287                write!(f, "Storage error: {message}")
288            },
289            GraphRAGError::Embedding { message } => {
290                write!(f, "Embedding error: {message}")
291            },
292            GraphRAGError::LanguageModel { message } => {
293                write!(f, "Language model error: {message}")
294            },
295            GraphRAGError::Parallel { message } => {
296                write!(f, "Parallel processing error: {message}")
297            },
298            GraphRAGError::Serialization { message } => {
299                write!(f, "Serialization error: {message}")
300            },
301            GraphRAGError::Validation { message } => {
302                write!(f, "Validation error: {message}")
303            },
304            GraphRAGError::Network { message } => {
305                write!(f, "Network error: {message}")
306            },
307            GraphRAGError::Auth { message } => {
308                write!(f, "Authentication error: {message}")
309            },
310            GraphRAGError::NotFound { resource, id } => {
311                write!(f, "{resource} not found: {id}")
312            },
313            GraphRAGError::AlreadyExists { resource, id } => {
314                write!(f, "{resource} already exists: {id}")
315            },
316            GraphRAGError::Timeout {
317                operation,
318                duration,
319            } => {
320                write!(f, "Operation '{operation}' timed out after {duration:?}")
321            },
322            GraphRAGError::ResourceLimit { resource, limit } => {
323                write!(f, "Resource limit exceeded for {resource}: {limit}")
324            },
325            GraphRAGError::DataCorruption { message } => {
326                write!(f, "Data corruption detected: {message}")
327            },
328            GraphRAGError::Unsupported { operation, reason } => {
329                write!(f, "Unsupported operation '{operation}': {reason}")
330            },
331            GraphRAGError::RateLimit { message } => {
332                write!(f, "Rate limit error: {message}")
333            },
334            GraphRAGError::ConflictResolution { message } => {
335                write!(f, "Conflict resolution error: {message}")
336            },
337            GraphRAGError::IncrementalUpdate { message } => {
338                write!(f, "Incremental update error: {message}")
339            },
340        }
341    }
342}
343
344impl std::error::Error for GraphRAGError {
345    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
346        match self {
347            GraphRAGError::Io(err) => Some(err),
348            #[cfg(feature = "ureq")]
349            GraphRAGError::Http(err) => Some(err.as_ref()),
350            #[cfg(not(feature = "ureq"))]
351            GraphRAGError::Http(_) => None,
352            GraphRAGError::Json(err) => Some(err),
353            GraphRAGError::SerdeJson(err) => Some(err),
354            _ => None,
355        }
356    }
357}
358
359// Automatic conversions from common error types
360impl From<std::io::Error> for GraphRAGError {
361    fn from(err: std::io::Error) -> Self {
362        GraphRAGError::Io(err)
363    }
364}
365
366#[cfg(feature = "ureq")]
367impl From<ureq::Error> for GraphRAGError {
368    fn from(err: ureq::Error) -> Self {
369        GraphRAGError::Http(Box::new(err))
370    }
371}
372
373impl From<json::Error> for GraphRAGError {
374    fn from(err: json::Error) -> Self {
375        GraphRAGError::Json(err)
376    }
377}
378
379impl From<serde_json::Error> for GraphRAGError {
380    fn from(err: serde_json::Error) -> Self {
381        GraphRAGError::SerdeJson(err)
382    }
383}
384
385// ROGRAG error conversions
386#[cfg(feature = "rograg")]
387impl From<crate::rograg::logic_form::LogicFormError> for GraphRAGError {
388    fn from(err: crate::rograg::logic_form::LogicFormError) -> Self {
389        GraphRAGError::Retrieval {
390            message: format!("Logic form error: {err}"),
391        }
392    }
393}
394
395#[cfg(feature = "rograg")]
396impl From<crate::rograg::processor::ProcessingError> for GraphRAGError {
397    fn from(err: crate::rograg::processor::ProcessingError) -> Self {
398        GraphRAGError::Generation {
399            message: format!("Processing error: {err}"),
400        }
401    }
402}
403
404#[cfg(feature = "rograg")]
405impl From<crate::rograg::quality_metrics::MetricsError> for GraphRAGError {
406    fn from(err: crate::rograg::quality_metrics::MetricsError) -> Self {
407        GraphRAGError::Validation {
408            message: format!("Metrics error: {err}"),
409        }
410    }
411}
412
413#[cfg(feature = "rograg")]
414impl From<crate::rograg::streaming::StreamingError> for GraphRAGError {
415    fn from(err: crate::rograg::streaming::StreamingError) -> Self {
416        GraphRAGError::Generation {
417            message: format!("Streaming error: {err}"),
418        }
419    }
420}
421
422#[cfg(feature = "rograg")]
423impl From<crate::rograg::fuzzy_matcher::FuzzyMatchError> for GraphRAGError {
424    fn from(err: crate::rograg::fuzzy_matcher::FuzzyMatchError) -> Self {
425        GraphRAGError::Retrieval {
426            message: format!("Fuzzy match error: {err}"),
427        }
428    }
429}
430
431/// Convenient Result type alias
432pub type Result<T> = std::result::Result<T, GraphRAGError>;
433
434/// Trait for adding context to errors
435pub trait ErrorContext<T> {
436    /// Add context to an error
437    fn with_context(self, context: &str) -> Result<T>;
438
439    /// Add context using a closure
440    fn with_context_lazy<F>(self, f: F) -> Result<T>
441    where
442        F: FnOnce() -> String;
443}
444
445impl<T, E> ErrorContext<T> for std::result::Result<T, E>
446where
447    E: Into<GraphRAGError>,
448{
449    fn with_context(self, context: &str) -> Result<T> {
450        self.map_err(|e| {
451            let base_error = e.into();
452            match base_error {
453                GraphRAGError::Config { message } => GraphRAGError::Config {
454                    message: format!("{context}: {message}"),
455                },
456                GraphRAGError::TextProcessing { message } => GraphRAGError::TextProcessing {
457                    message: format!("{context}: {message}"),
458                },
459                GraphRAGError::GraphConstruction { message } => GraphRAGError::GraphConstruction {
460                    message: format!("{context}: {message}"),
461                },
462                GraphRAGError::VectorSearch { message } => GraphRAGError::VectorSearch {
463                    message: format!("{context}: {message}"),
464                },
465                GraphRAGError::EntityExtraction { message } => GraphRAGError::EntityExtraction {
466                    message: format!("{context}: {message}"),
467                },
468                GraphRAGError::Retrieval { message } => GraphRAGError::Retrieval {
469                    message: format!("{context}: {message}"),
470                },
471                GraphRAGError::Generation { message } => GraphRAGError::Generation {
472                    message: format!("{context}: {message}"),
473                },
474                GraphRAGError::FunctionCall { message } => GraphRAGError::FunctionCall {
475                    message: format!("{context}: {message}"),
476                },
477                GraphRAGError::Storage { message } => GraphRAGError::Storage {
478                    message: format!("{context}: {message}"),
479                },
480                GraphRAGError::Embedding { message } => GraphRAGError::Embedding {
481                    message: format!("{context}: {message}"),
482                },
483                GraphRAGError::LanguageModel { message } => GraphRAGError::LanguageModel {
484                    message: format!("{context}: {message}"),
485                },
486                GraphRAGError::Parallel { message } => GraphRAGError::Parallel {
487                    message: format!("{context}: {message}"),
488                },
489                GraphRAGError::Serialization { message } => GraphRAGError::Serialization {
490                    message: format!("{context}: {message}"),
491                },
492                GraphRAGError::Validation { message } => GraphRAGError::Validation {
493                    message: format!("{context}: {message}"),
494                },
495                GraphRAGError::Network { message } => GraphRAGError::Network {
496                    message: format!("{context}: {message}"),
497                },
498                GraphRAGError::Auth { message } => GraphRAGError::Auth {
499                    message: format!("{context}: {message}"),
500                },
501                GraphRAGError::DataCorruption { message } => GraphRAGError::DataCorruption {
502                    message: format!("{context}: {message}"),
503                },
504                GraphRAGError::RateLimit { message } => GraphRAGError::RateLimit {
505                    message: format!("{context}: {message}"),
506                },
507                GraphRAGError::ConflictResolution { message } => {
508                    GraphRAGError::ConflictResolution {
509                        message: format!("{context}: {message}"),
510                    }
511                },
512                GraphRAGError::IncrementalUpdate { message } => GraphRAGError::IncrementalUpdate {
513                    message: format!("{context}: {message}"),
514                },
515                other => other, // For errors that don't have a message field
516            }
517        })
518    }
519
520    fn with_context_lazy<F>(self, f: F) -> Result<T>
521    where
522        F: FnOnce() -> String,
523    {
524        match self {
525            Ok(value) => Ok(value),
526            Err(e) => {
527                let context = f();
528                Err(e).with_context(&context)
529            },
530        }
531    }
532}
533
534/// Helper macros for creating specific error types
535///
536/// Creates a configuration error with a message
537#[macro_export]
538macro_rules! config_error {
539    ($msg:expr) => {
540        $crate::GraphRAGError::Config {
541            message: $msg.to_string(),
542        }
543    };
544    ($fmt:expr, $($arg:tt)*) => {
545        $crate::GraphRAGError::Config {
546            message: format!($fmt, $($arg)*),
547        }
548    };
549}
550
551/// Creates a storage error with a message
552#[macro_export]
553macro_rules! storage_error {
554    ($msg:expr) => {
555        $crate::GraphRAGError::Storage {
556            message: $msg.to_string(),
557        }
558    };
559    ($fmt:expr, $($arg:tt)*) => {
560        $crate::GraphRAGError::Storage {
561            message: format!($fmt, $($arg)*),
562        }
563    };
564}
565
566/// Creates a retrieval error with a message
567#[macro_export]
568macro_rules! retrieval_error {
569    ($msg:expr) => {
570        $crate::GraphRAGError::Retrieval {
571            message: $msg.to_string(),
572        }
573    };
574    ($fmt:expr, $($arg:tt)*) => {
575        $crate::GraphRAGError::Retrieval {
576            message: format!($fmt, $($arg)*),
577        }
578    };
579}
580
581/// Creates a generation error with a message
582#[macro_export]
583macro_rules! generation_error {
584    ($msg:expr) => {
585        $crate::GraphRAGError::Generation {
586            message: $msg.to_string(),
587        }
588    };
589    ($fmt:expr, $($arg:tt)*) => {
590        $crate::GraphRAGError::Generation {
591            message: format!($fmt, $($arg)*),
592        }
593    };
594}
595
596/// Error severity levels for logging and monitoring
597#[derive(Debug, Clone, Copy, PartialEq, Eq)]
598pub enum ErrorSeverity {
599    /// Informational - not actually an error
600    Info,
601    /// Warning - something unexpected but recoverable
602    Warning,
603    /// Error - operation failed but system can continue
604    Error,
605    /// Critical - system integrity compromised
606    Critical,
607}
608
609/// Structured suggestion for error recovery
610#[derive(Debug, Clone)]
611pub struct ErrorSuggestion {
612    /// Short description of what to do
613    pub action: String,
614    /// Example code snippet (if applicable)
615    pub code_example: Option<String>,
616    /// Link to documentation (if applicable)
617    pub doc_link: Option<String>,
618}
619
620impl GraphRAGError {
621    /// Get the severity level of this error
622    pub fn severity(&self) -> ErrorSeverity {
623        match self {
624            GraphRAGError::Config { .. } => ErrorSeverity::Critical,
625            GraphRAGError::NotInitialized => ErrorSeverity::Warning,
626            GraphRAGError::NoDocuments => ErrorSeverity::Warning,
627            GraphRAGError::Io(_) => ErrorSeverity::Error,
628            GraphRAGError::Http(_) => ErrorSeverity::Warning,
629            GraphRAGError::Json(_) | GraphRAGError::SerdeJson(_) => ErrorSeverity::Error,
630            GraphRAGError::TextProcessing { .. } => ErrorSeverity::Warning,
631            GraphRAGError::GraphConstruction { .. } => ErrorSeverity::Error,
632            GraphRAGError::VectorSearch { .. } => ErrorSeverity::Warning,
633            GraphRAGError::EntityExtraction { .. } => ErrorSeverity::Warning,
634            GraphRAGError::Retrieval { .. } => ErrorSeverity::Warning,
635            GraphRAGError::Generation { .. } => ErrorSeverity::Warning,
636            GraphRAGError::FunctionCall { .. } => ErrorSeverity::Warning,
637            GraphRAGError::Storage { .. } => ErrorSeverity::Error,
638            GraphRAGError::Embedding { .. } => ErrorSeverity::Warning,
639            GraphRAGError::LanguageModel { .. } => ErrorSeverity::Warning,
640            GraphRAGError::Parallel { .. } => ErrorSeverity::Error,
641            GraphRAGError::Serialization { .. } => ErrorSeverity::Error,
642            GraphRAGError::Validation { .. } => ErrorSeverity::Error,
643            GraphRAGError::Network { .. } => ErrorSeverity::Warning,
644            GraphRAGError::Auth { .. } => ErrorSeverity::Error,
645            GraphRAGError::NotFound { .. } => ErrorSeverity::Warning,
646            GraphRAGError::AlreadyExists { .. } => ErrorSeverity::Warning,
647            GraphRAGError::Timeout { .. } => ErrorSeverity::Warning,
648            GraphRAGError::ResourceLimit { .. } => ErrorSeverity::Error,
649            GraphRAGError::DataCorruption { .. } => ErrorSeverity::Critical,
650            GraphRAGError::Unsupported { .. } => ErrorSeverity::Error,
651            GraphRAGError::RateLimit { .. } => ErrorSeverity::Warning,
652            GraphRAGError::ConflictResolution { .. } => ErrorSeverity::Error,
653            GraphRAGError::IncrementalUpdate { .. } => ErrorSeverity::Error,
654        }
655    }
656
657    /// Check if this error is recoverable
658    pub fn is_recoverable(&self) -> bool {
659        match self.severity() {
660            ErrorSeverity::Info | ErrorSeverity::Warning => true,
661            ErrorSeverity::Error => false,
662            ErrorSeverity::Critical => false,
663        }
664    }
665
666    /// Get error category for metrics/monitoring
667    pub fn category(&self) -> &'static str {
668        match self {
669            GraphRAGError::Config { .. } => "config",
670            GraphRAGError::NotInitialized => "initialization",
671            GraphRAGError::NoDocuments => "usage",
672            GraphRAGError::Io(_) => "io",
673            GraphRAGError::Http(_) => "http",
674            GraphRAGError::Json(_) | GraphRAGError::SerdeJson(_) => "serialization",
675            GraphRAGError::TextProcessing { .. } => "text_processing",
676            GraphRAGError::GraphConstruction { .. } => "graph",
677            GraphRAGError::VectorSearch { .. } => "vector_search",
678            GraphRAGError::EntityExtraction { .. } => "entity_extraction",
679            GraphRAGError::Retrieval { .. } => "retrieval",
680            GraphRAGError::Generation { .. } => "generation",
681            GraphRAGError::FunctionCall { .. } => "function_calling",
682            GraphRAGError::Storage { .. } => "storage",
683            GraphRAGError::Embedding { .. } => "embedding",
684            GraphRAGError::LanguageModel { .. } => "language_model",
685            GraphRAGError::Parallel { .. } => "parallel",
686            GraphRAGError::Serialization { .. } => "serialization",
687            GraphRAGError::Validation { .. } => "validation",
688            GraphRAGError::Network { .. } => "network",
689            GraphRAGError::Auth { .. } => "auth",
690            GraphRAGError::NotFound { .. } => "not_found",
691            GraphRAGError::AlreadyExists { .. } => "already_exists",
692            GraphRAGError::Timeout { .. } => "timeout",
693            GraphRAGError::ResourceLimit { .. } => "resource_limit",
694            GraphRAGError::DataCorruption { .. } => "data_corruption",
695            GraphRAGError::Unsupported { .. } => "unsupported",
696            GraphRAGError::RateLimit { .. } => "rate_limit",
697            GraphRAGError::ConflictResolution { .. } => "conflict_resolution",
698            GraphRAGError::IncrementalUpdate { .. } => "incremental_update",
699        }
700    }
701
702    /// Get structured suggestions for recovering from this error
703    ///
704    /// Returns actionable suggestions with optional code examples.
705    ///
706    /// # Example
707    /// ```rust
708    /// use graphrag_core::GraphRAGError;
709    ///
710    /// let error = GraphRAGError::NotInitialized;
711    /// let suggestion = error.suggestion();
712    /// println!("Action: {}", suggestion.action);
713    /// if let Some(code) = suggestion.code_example {
714    ///     println!("Example:\n{}", code);
715    /// }
716    /// ```
717    pub fn suggestion(&self) -> ErrorSuggestion {
718        match self {
719            GraphRAGError::Config { message } => {
720                let (action, code) = if message.contains("not found") || message.contains("load") {
721                    (
722                        "Create a config file or use defaults with Config::default()".to_string(),
723                        Some(r#"// Option 1: Use defaults
724let config = Config::default();
725
726// Option 2: Load with hierarchy (user -> project -> env)
727let config = Config::load()?;
728
729// Option 3: Use TypedBuilder for compile-time safety
730let graphrag = TypedBuilder::new()
731    .with_output_dir("./output")
732    .with_ollama()
733    .build()?;"#.to_string())
734                    )
735                } else {
736                    (
737                        "Check configuration values and TOML syntax".to_string(),
738                        Some(r#"// Validate config before use
739let config = Config::from_toml_file("config.toml")?;
740
741// Or use hierarchical loading with env var overrides
742// Set GRAPHRAG_OLLAMA_HOST=localhost to override
743let config = Config::load()?;"#.to_string())
744                    )
745                };
746                ErrorSuggestion { action, code_example: code, doc_link: None }
747            }
748            GraphRAGError::NotInitialized => ErrorSuggestion {
749                action: "Initialize the GraphRAG system before querying".to_string(),
750                code_example: Some(r#"// Option 1: Manual initialization
751let mut graphrag = GraphRAG::new(config)?;
752graphrag.initialize()?;
753
754// Option 2: Use quick_start (recommended)
755let mut graphrag = GraphRAG::quick_start("Your document text").await?;
756
757// Option 3: Use builder with auto-init
758let graphrag = TypedBuilder::new()
759    .with_output_dir("./output")
760    .with_ollama()
761    .build_and_init()?;"#.to_string()),
762                doc_link: None,
763            },
764            GraphRAGError::NoDocuments => ErrorSuggestion {
765                action: "Add documents before building the graph or querying".to_string(),
766                code_example: Some(r#"// Add text directly
767graphrag.add_document_from_text("Your document content here")?;
768
769// Add from file
770graphrag.add_document_from_file("document.txt")?;
771
772// Add multiple documents
773for file in glob("docs/*.txt")? {
774    graphrag.add_document_from_file(file)?;
775}
776
777// Then build the graph
778graphrag.build_graph().await?;"#.to_string()),
779                doc_link: None,
780            },
781            GraphRAGError::Http(_) => ErrorSuggestion {
782                action: "Check network connectivity and service availability".to_string(),
783                code_example: Some(r#"// Check Ollama is running
784// Terminal: curl http://localhost:11434/api/tags
785
786// Verify Ollama config
787let config = Config::default();
788assert!(config.ollama.enabled);
789assert_eq!(config.ollama.host, "localhost");
790assert_eq!(config.ollama.port, 11434);
791
792// Or use hash embeddings for offline mode
793let graphrag = TypedBuilder::new()
794    .with_output_dir("./output")
795    .with_hash_embeddings()  // No network needed
796    .build()?;"#.to_string()),
797                doc_link: None,
798            },
799            GraphRAGError::LanguageModel { message } => {
800                let action = if message.contains("not found") || message.contains("model") {
801                    "Ensure the LLM model is pulled and available".to_string()
802                } else {
803                    "Check LLM provider configuration".to_string()
804                };
805                ErrorSuggestion {
806                    action,
807                    code_example: Some(r#"// Pull the model first (in terminal)
808// ollama pull llama3.2:latest
809// ollama pull nomic-embed-text:latest
810
811// Or specify a different model
812let graphrag = GraphRAGBuilder::new()
813    .with_ollama_enabled(true)
814    .with_chat_model("mistral:latest")
815    .with_ollama_embedding_model("nomic-embed-text:latest")
816    .build()?;"#.to_string()),
817                    doc_link: None,
818                }
819            }
820            GraphRAGError::Embedding { message } => {
821                let action = if message.contains("dimension") {
822                    "Check embedding dimension matches your model".to_string()
823                } else {
824                    "Verify embedding provider configuration".to_string()
825                };
826                ErrorSuggestion {
827                    action,
828                    code_example: Some(r#"// Use matching dimension for your model
829// nomic-embed-text: 768, all-MiniLM-L6-v2: 384
830let graphrag = GraphRAGBuilder::new()
831    .with_embedding_dimension(768)
832    .with_embedding_backend("ollama")
833    .build()?;
834
835// Or use hash embeddings (dimension-agnostic)
836let graphrag = TypedBuilder::new()
837    .with_output_dir("./output")
838    .with_hash_embeddings()
839    .build()?;"#.to_string()),
840                    doc_link: None,
841                }
842            }
843            GraphRAGError::Retrieval { .. } => ErrorSuggestion {
844                action: "Ensure documents are added and graph is built before querying".to_string(),
845                code_example: Some(r#"// Full workflow
846let mut graphrag = GraphRAG::new(config)?;
847graphrag.initialize()?;
848graphrag.add_document_from_text("Your content")?;
849graphrag.build_graph().await?;
850
851// Now you can query
852let answer = graphrag.ask("Your question").await?;"#.to_string()),
853                doc_link: None,
854            },
855            GraphRAGError::VectorSearch { .. } => ErrorSuggestion {
856                action: "Initialize embeddings and ensure vectors are indexed".to_string(),
857                code_example: Some(r#"// Make sure embeddings are configured
858let graphrag = GraphRAGBuilder::new()
859    .with_embedding_backend("ollama")
860    .with_embedding_model("nomic-embed-text:latest")
861    .build()?;
862
863// Or check if documents need reindexing
864graphrag.build_graph().await?;"#.to_string()),
865                doc_link: None,
866            },
867            GraphRAGError::Timeout { operation, duration } => ErrorSuggestion {
868                action: format!("Operation '{}' took too long ({:?}). Consider increasing timeout or optimizing.", operation, duration),
869                code_example: Some(r#"// Increase timeout in config
870let mut config = Config::default();
871config.ollama.timeout_seconds = 120;  // 2 minutes
872
873// Or process smaller chunks
874config.chunk_size = 500;  // Smaller chunks
875config.parallel.enabled = true;  // Parallel processing"#.to_string()),
876                doc_link: None,
877            },
878            GraphRAGError::RateLimit { .. } => ErrorSuggestion {
879                action: "You've hit rate limits. Wait and retry, or use local models.".to_string(),
880                code_example: Some(r#"// Switch to local Ollama (no rate limits)
881let graphrag = TypedBuilder::new()
882    .with_output_dir("./output")
883    .with_ollama()  // Local, no API limits
884    .build()?;
885
886// Or enable caching to reduce API calls
887let mut config = Config::default();
888config.caching.enabled = true;"#.to_string()),
889                doc_link: None,
890            },
891            GraphRAGError::Storage { .. } => ErrorSuggestion {
892                action: "Check file permissions and disk space".to_string(),
893                code_example: Some(r#"// Verify output directory exists and is writable
894std::fs::create_dir_all("./output")?;
895
896// Use a different output directory
897let graphrag = GraphRAGBuilder::new()
898    .with_output_dir("/tmp/graphrag_output")
899    .build()?;"#.to_string()),
900                doc_link: None,
901            },
902            GraphRAGError::NotFound { resource, id } => ErrorSuggestion {
903                action: format!("{} '{}' not found. Verify it exists.", resource, id),
904                code_example: None,
905                doc_link: None,
906            },
907            GraphRAGError::AlreadyExists { resource, id } => ErrorSuggestion {
908                action: format!("{} '{}' already exists. Use a different ID or update existing.", resource, id),
909                code_example: None,
910                doc_link: None,
911            },
912            // Default suggestions for other errors
913            _ => ErrorSuggestion {
914                action: "Check the error message for details".to_string(),
915                code_example: None,
916                doc_link: None,
917            },
918        }
919    }
920
921    /// Get a formatted error message with suggestion
922    ///
923    /// Returns a user-friendly error message including the suggestion.
924    pub fn display_with_suggestion(&self) -> String {
925        let suggestion = self.suggestion();
926        let mut output = format!("{}\n\n💡 Suggestion: {}", self, suggestion.action);
927        if let Some(code) = &suggestion.code_example {
928            output.push_str(&format!("\n\nExample:\n```rust\n{}\n```", code));
929        }
930        output
931    }
932}
933
934impl From<regex::Error> for GraphRAGError {
935    fn from(err: regex::Error) -> Self {
936        GraphRAGError::Validation {
937            message: format!("Regex error: {err}"),
938        }
939    }
940}
941
942#[cfg(test)]
943mod tests {
944    use super::*;
945
946    #[test]
947    fn test_error_display() {
948        let error = GraphRAGError::Config {
949            message: "Invalid configuration".to_string(),
950        };
951        assert_eq!(
952            format!("{error}"),
953            "Configuration error: Invalid configuration. Solution: Check your config file or use default settings with GraphRAG::builder()"
954        );
955    }
956
957    #[test]
958    fn test_error_context() {
959        let result: std::result::Result<(), std::io::Error> = Err(std::io::Error::new(
960            std::io::ErrorKind::NotFound,
961            "file not found",
962        ));
963
964        let error = result.with_context("loading configuration").unwrap_err();
965        assert!(matches!(error, GraphRAGError::Io(_)));
966    }
967
968    #[test]
969    fn test_error_macros() {
970        let error = config_error!("test message");
971        assert!(matches!(error, GraphRAGError::Config { .. }));
972
973        let error = storage_error!("test {} {}", "formatted", "message");
974        assert!(matches!(error, GraphRAGError::Storage { .. }));
975    }
976
977    #[test]
978    fn test_error_severity() {
979        let config_error = GraphRAGError::Config {
980            message: "test".to_string(),
981        };
982        assert_eq!(config_error.severity(), ErrorSeverity::Critical);
983        assert!(!config_error.is_recoverable());
984
985        let warning_error = GraphRAGError::Retrieval {
986            message: "test".to_string(),
987        };
988        assert_eq!(warning_error.severity(), ErrorSeverity::Warning);
989        assert!(warning_error.is_recoverable());
990    }
991
992    #[test]
993    fn test_error_suggestion_not_initialized() {
994        let error = GraphRAGError::NotInitialized;
995        let suggestion = error.suggestion();
996
997        assert!(suggestion.action.contains("Initialize"));
998        assert!(suggestion.code_example.is_some());
999        let code = suggestion.code_example.unwrap();
1000        assert!(code.contains("initialize()") || code.contains("quick_start"));
1001    }
1002
1003    #[test]
1004    fn test_error_suggestion_no_documents() {
1005        let error = GraphRAGError::NoDocuments;
1006        let suggestion = error.suggestion();
1007
1008        assert!(suggestion.action.contains("Add documents"));
1009        assert!(suggestion.code_example.is_some());
1010        let code = suggestion.code_example.unwrap();
1011        assert!(code.contains("add_document"));
1012    }
1013
1014    #[test]
1015    fn test_error_suggestion_config() {
1016        let error = GraphRAGError::Config {
1017            message: "File not found".to_string(),
1018        };
1019        let suggestion = error.suggestion();
1020
1021        assert!(suggestion.code_example.is_some());
1022        let code = suggestion.code_example.unwrap();
1023        assert!(code.contains("Config::default()") || code.contains("Config::load"));
1024    }
1025
1026    #[test]
1027    fn test_error_suggestion_not_found() {
1028        let error = GraphRAGError::NotFound {
1029            resource: "Document".to_string(),
1030            id: "test-123".to_string(),
1031        };
1032        let suggestion = error.suggestion();
1033
1034        assert!(suggestion.action.contains("Document"));
1035        assert!(suggestion.action.contains("test-123"));
1036    }
1037
1038    #[test]
1039    fn test_display_with_suggestion() {
1040        let error = GraphRAGError::NotInitialized;
1041        let display = error.display_with_suggestion();
1042
1043        assert!(display.contains("not initialized"));
1044        assert!(display.contains("Suggestion:"));
1045        assert!(display.contains("```rust"));
1046    }
1047}