use super::types::EventLevel;
use std::borrow::Cow;
#[derive(Debug, Clone)]
pub struct QueryBuilder {
xpath: Option<String>,
level: Option<EventLevel>,
provider: Option<String>,
event_id: Option<u32>,
reverse: bool,
max_results: Option<usize>,
include_event_data: bool,
parse_message: bool,
}
impl QueryBuilder {
pub fn new() -> Self {
QueryBuilder {
xpath: None,
level: None,
provider: None,
event_id: None,
reverse: false,
max_results: None,
include_event_data: false,
parse_message: false,
}
}
pub fn xpath(mut self, xpath: impl Into<Cow<'static, str>>) -> Self {
self.xpath = Some(xpath.into().into_owned());
self
}
pub fn level(mut self, level: EventLevel) -> Self {
self.level = Some(level);
self
}
pub fn provider(mut self, name: impl Into<Cow<'static, str>>) -> Self {
self.provider = Some(name.into().into_owned());
self
}
pub fn event_id(mut self, id: u32) -> Self {
self.event_id = Some(id);
self
}
pub fn reverse(mut self) -> Self {
self.reverse = true;
self
}
pub fn max_results(mut self, count: usize) -> Self {
self.max_results = Some(count);
self
}
pub fn with_event_data(mut self) -> Self {
self.include_event_data = true;
self
}
pub fn with_message(mut self) -> Self {
self.parse_message = true;
self
}
pub fn should_parse_event_data(&self) -> bool {
self.include_event_data
}
pub fn should_parse_message(&self) -> bool {
self.parse_message
}
pub fn build_xpath(&self) -> String {
if let Some(xpath) = &self.xpath {
return xpath.clone();
}
let mut conditions = Vec::new();
if let Some(level) = self.level {
conditions.push(format!("Level <= {}", level as u8));
}
if let Some(ref provider) = self.provider {
conditions.push(format!(
"System/Provider/@Name='{}'",
escape_xpath_string(provider)
));
}
if let Some(id) = self.event_id {
conditions.push(format!("EventID={}", id));
}
if conditions.is_empty() {
"Event".to_string()
} else {
format!("Event/System[{}]", conditions.join(" and "))
}
}
pub fn is_reverse(&self) -> bool {
self.reverse
}
pub fn max_results_limit(&self) -> Option<usize> {
self.max_results
}
}
impl Default for QueryBuilder {
fn default() -> Self {
Self::new()
}
}
fn escape_xpath_string(s: &str) -> String {
if s.contains('\'') && s.contains('"') {
s.replace('"', """)
} else if s.contains('\'') {
s.to_string()
} else {
s.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_query_builder_empty() {
let builder = QueryBuilder::new();
assert_eq!(builder.build_xpath(), "Event");
}
#[test]
fn test_query_builder_with_level() {
let builder = QueryBuilder::new().level(EventLevel::Error);
let xpath = builder.build_xpath();
assert!(xpath.contains("Level <= 3"));
assert!(xpath.contains("Event/System"));
}
#[test]
fn test_query_builder_with_provider() {
let builder = QueryBuilder::new().provider("Security");
let xpath = builder.build_xpath();
assert!(xpath.contains("System/Provider/@Name='Security'"));
}
#[test]
fn test_query_builder_with_event_id() {
let builder = QueryBuilder::new().event_id(4688);
let xpath = builder.build_xpath();
assert!(xpath.contains("EventID=4688"));
}
#[test]
fn test_query_builder_combined() {
let builder = QueryBuilder::new()
.level(EventLevel::Warning)
.provider("Application")
.event_id(1000);
let xpath = builder.build_xpath();
assert!(xpath.contains("Level <= 4"));
assert!(xpath.contains("System/Provider/@Name='Application'"));
assert!(xpath.contains("EventID=1000"));
assert!(xpath.contains(" and "));
}
#[test]
fn test_query_builder_with_raw_xpath() {
let builder = QueryBuilder::new()
.xpath("Event/System[EventID=4728]")
.level(EventLevel::Error); let xpath = builder.build_xpath();
assert_eq!(xpath, "Event/System[EventID=4728]");
}
#[test]
fn test_query_builder_reverse() {
let builder = QueryBuilder::new().reverse();
assert!(builder.is_reverse());
let builder2 = QueryBuilder::new();
assert!(!builder2.is_reverse());
}
#[test]
fn test_query_builder_max_results() {
let builder = QueryBuilder::new().max_results(100);
assert_eq!(builder.max_results_limit(), Some(100));
let builder2 = QueryBuilder::new();
assert_eq!(builder2.max_results_limit(), None);
}
}