Skip to main content

rsigma_parser/
error.rs

1use std::fmt;
2
3use thiserror::Error;
4
5/// Source location within a Sigma document.
6///
7/// Attached to parse errors when position information is available
8/// (e.g. from pest parse failures). Line and column are 1-indexed.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct SourceLocation {
11    pub line: u32,
12    pub col: u32,
13}
14
15impl fmt::Display for SourceLocation {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        write!(f, "{}:{}", self.line, self.col)
18    }
19}
20
21/// Errors that can occur during Sigma rule parsing.
22#[derive(Debug, Error)]
23#[non_exhaustive]
24pub enum SigmaParserError {
25    #[error("YAML parsing error: {0}")]
26    Yaml(#[from] yaml_serde::Error),
27
28    #[error("{}", format_with_location(.0, .1))]
29    Condition(String, Option<SourceLocation>),
30
31    #[error("Unknown modifier '{0}'")]
32    UnknownModifier(String),
33
34    #[error("Modifier '{0}' is applied more than once")]
35    DuplicateModifier(String),
36
37    /// Reserved when a user writes `field|not: value` or
38    /// `field|contains|not: value` as if `not` were a value modifier.
39    /// Sigma does not support a `|not` modifier; negation is expressed at
40    /// the condition level (`not selection` or `selection and not filter`).
41    #[error(
42        "`not` is not a value modifier in Sigma; express negation in the \
43         condition (e.g. `not selection`) or move the inverted check into a \
44         separate detection used as a filter (e.g. `selection and not other`)"
45    )]
46    NotIsNotAModifier,
47
48    #[error("Invalid field specification: {0}")]
49    InvalidFieldSpec(String),
50
51    #[error("Invalid modifier combination: {0}")]
52    InvalidModifiers(String),
53
54    #[error("Invalid rule: {0}")]
55    InvalidRule(String),
56
57    #[error("Missing required field '{0}'")]
58    MissingField(String),
59
60    #[error("Invalid detection: {0}")]
61    InvalidDetection(String),
62
63    #[error("Invalid correlation rule: {0}")]
64    InvalidCorrelation(String),
65
66    #[error("Invalid timespan '{0}'")]
67    InvalidTimespan(String),
68
69    #[error("Invalid value: {0}")]
70    InvalidValue(String),
71
72    #[error("Invalid collection action '{0}'")]
73    InvalidAction(String),
74
75    #[error("IO error: {0}")]
76    Io(#[from] std::io::Error),
77
78    #[error("YAML merge exceeds maximum depth ({0})")]
79    MergeTooDeep(usize),
80
81    #[error("Condition string too long ({0} bytes, max {1})")]
82    ConditionTooLong(usize, usize),
83}
84
85impl SigmaParserError {
86    /// Returns the source location if this error variant carries one.
87    pub fn location(&self) -> Option<SourceLocation> {
88        match self {
89            SigmaParserError::Condition(_, loc) => *loc,
90            _ => None,
91        }
92    }
93}
94
95fn format_with_location(msg: &str, loc: &Option<SourceLocation>) -> String {
96    match loc {
97        Some(loc) => format!("Condition parse error at {loc}: {msg}"),
98        None => format!("Condition parse error: {msg}"),
99    }
100}
101
102pub type Result<T> = std::result::Result<T, SigmaParserError>;