kql_panopticon/pack/
processing.rs1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
14pub struct Processing {
15 #[serde(default)]
17 pub steps: Vec<ProcessingStep>,
18}
19
20impl Processing {
21 pub fn new() -> Self {
23 Self::default()
24 }
25
26 pub fn is_empty(&self) -> bool {
28 self.steps.is_empty()
29 }
30
31 pub fn get_step(&self, name: &str) -> Option<&ProcessingStep> {
33 self.steps.iter().find(|s| s.name == name)
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct ProcessingStep {
40 pub name: String,
42
43 #[serde(flatten)]
45 pub config: ProcessingStepConfig,
46
47 #[serde(default)]
49 pub when: Option<String>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(tag = "type", rename_all = "lowercase")]
55pub enum ProcessingStepConfig {
56 Scoring(ScoringConfig),
58 }
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct ScoringConfig {
66 #[serde(default)]
68 pub indicators: Vec<ScoringIndicator>,
69
70 #[serde(default)]
72 pub thresholds: Vec<ScoringThreshold>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct ScoringIndicator {
80 pub name: String,
82
83 pub condition: String,
85
86 pub weight: i32,
88
89 #[serde(default)]
91 pub description: Option<String>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct ScoringThreshold {
99 pub level: String,
101
102 pub min_score: i32,
104
105 #[serde(default)]
107 pub summary: Option<String>,
108
109 #[serde(default)]
111 pub recommendation: Option<String>,
112}
113
114#[derive(Debug, Clone, Default, Serialize, Deserialize)]
116pub struct ScoringResult {
117 pub score: i32,
119
120 pub level: String,
122
123 pub matched_indicators: Vec<MatchedIndicator>,
125
126 pub summary: Option<String>,
128
129 pub recommendation: Option<String>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct MatchedIndicator {
136 pub name: String,
138
139 pub weight: i32,
141
142 pub description: Option<String>,
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_empty_processing() {
152 let proc = Processing::new();
153 assert!(proc.is_empty());
154 }
155
156 #[test]
157 fn test_scoring_config_deserialize() {
158 let yaml = r#"
159name: risk_score
160type: scoring
161indicators:
162 - name: high_failures
163 condition: "signins.any(FailedCount > 10)"
164 weight: 25
165thresholds:
166 - level: HIGH
167 min_score: 50
168"#;
169 let step: ProcessingStep = serde_yaml::from_str(yaml).unwrap();
170 assert_eq!(step.name, "risk_score");
171
172 match step.config {
173 ProcessingStepConfig::Scoring(cfg) => {
174 assert_eq!(cfg.indicators.len(), 1);
175 assert_eq!(cfg.thresholds.len(), 1);
176 }
177 }
178 }
179}