sentinel_modsec/
error.rs

1//! Error types for sentinel-modsec.
2
3use std::path::PathBuf;
4use thiserror::Error;
5
6/// Result type alias using the crate's Error type.
7pub type Result<T> = std::result::Result<T, Error>;
8
9/// Main error type for sentinel-modsec operations.
10#[derive(Debug, Error)]
11pub enum Error {
12    /// Error parsing a SecRule directive.
13    #[error("parse error at {location}: {message}")]
14    Parse {
15        /// Human-readable error message.
16        message: String,
17        /// Location in the source (file:line:col or line:col).
18        location: String,
19        /// The source text that caused the error (if available).
20        source_text: Option<String>,
21    },
22
23    /// Error loading a rule file.
24    #[error("failed to load rule file {path}: {source}")]
25    RuleFileLoad {
26        /// Path to the file that failed to load.
27        path: PathBuf,
28        /// Underlying I/O error.
29        #[source]
30        source: std::io::Error,
31    },
32
33    /// Error compiling a regex pattern.
34    #[error("invalid regex pattern '{pattern}': {source}")]
35    RegexCompile {
36        /// The pattern that failed to compile.
37        pattern: String,
38        /// Underlying regex error.
39        #[source]
40        source: regex::Error,
41    },
42
43    /// Error compiling an Aho-Corasick pattern set.
44    #[error("invalid pattern set: {message}")]
45    PatternSet {
46        /// Error message.
47        message: String,
48    },
49
50    /// Error parsing an IP address or network.
51    #[error("invalid IP address or network '{value}': {message}")]
52    InvalidIp {
53        /// The value that failed to parse.
54        value: String,
55        /// Error message.
56        message: String,
57    },
58
59    /// Unknown variable name.
60    #[error("unknown variable: {name}")]
61    UnknownVariable {
62        /// The unknown variable name.
63        name: String,
64    },
65
66    /// Unknown operator name.
67    #[error("unknown operator: @{name}")]
68    UnknownOperator {
69        /// The unknown operator name.
70        name: String,
71    },
72
73    /// Unknown transformation name.
74    #[error("unknown transformation: t:{name}")]
75    UnknownTransformation {
76        /// The unknown transformation name.
77        name: String,
78    },
79
80    /// Unknown action name.
81    #[error("unknown action: {name}")]
82    UnknownAction {
83        /// The unknown action name.
84        name: String,
85    },
86
87    /// Invalid action argument.
88    #[error("invalid argument for action '{action}': {message}")]
89    InvalidActionArgument {
90        /// The action name.
91        action: String,
92        /// Error message.
93        message: String,
94    },
95
96    /// Rule is missing required 'id' action.
97    #[error("rule is missing required 'id' action")]
98    MissingRuleId,
99
100    /// Duplicate rule ID.
101    #[error("duplicate rule id: {id}")]
102    DuplicateRuleId {
103        /// The duplicate ID.
104        id: u64,
105    },
106
107    /// Rule chain is incomplete.
108    #[error("incomplete rule chain: chain action without following rule")]
109    IncompleteChain,
110
111    /// Error processing URI.
112    #[error("failed to process URI: {message}")]
113    ProcessUri {
114        /// Error message.
115        message: String,
116    },
117
118    /// Error processing request headers.
119    #[error("failed to process request headers: {message}")]
120    ProcessRequestHeaders {
121        /// Error message.
122        message: String,
123    },
124
125    /// Error processing request body.
126    #[error("failed to process request body: {message}")]
127    ProcessRequestBody {
128        /// Error message.
129        message: String,
130    },
131
132    /// Error processing response headers.
133    #[error("failed to process response headers: {message}")]
134    ProcessResponseHeaders {
135        /// Error message.
136        message: String,
137    },
138
139    /// Error processing response body.
140    #[error("failed to process response body: {message}")]
141    ProcessResponseBody {
142        /// Error message.
143        message: String,
144    },
145
146    /// Configuration error.
147    #[error("configuration error: {message}")]
148    Config {
149        /// Error message.
150        message: String,
151    },
152
153    /// Internal error (should not happen in normal operation).
154    #[error("internal error: {message}")]
155    Internal {
156        /// Error message.
157        message: String,
158    },
159}
160
161impl Error {
162    /// Create a parse error with location information.
163    pub fn parse(message: impl Into<String>, location: impl Into<String>) -> Self {
164        Self::Parse {
165            message: message.into(),
166            location: location.into(),
167            source_text: None,
168        }
169    }
170
171    /// Create a parse error with location and source text.
172    pub fn parse_with_source(
173        message: impl Into<String>,
174        location: impl Into<String>,
175        source_text: impl Into<String>,
176    ) -> Self {
177        Self::Parse {
178            message: message.into(),
179            location: location.into(),
180            source_text: Some(source_text.into()),
181        }
182    }
183}
184
185/// Source location for error reporting.
186#[derive(Debug, Clone, Default)]
187pub struct SourceLocation {
188    /// File path (if known).
189    pub file: Option<PathBuf>,
190    /// Line number (1-indexed).
191    pub line: usize,
192    /// Column number (1-indexed).
193    pub column: usize,
194}
195
196impl std::fmt::Display for SourceLocation {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        if let Some(ref file) = self.file {
199            write!(f, "{}:{}:{}", file.display(), self.line, self.column)
200        } else {
201            write!(f, "{}:{}", self.line, self.column)
202        }
203    }
204}