use {crate::stream_name::StreamName, std::str::FromStr};
const DIRECTIVE_DELIMITER: &str = ",";
const PREFIX_RULE_ASSIGNMENT: &str = "=";
const ON: &str = "on";
const OFF: &str = "off";
#[derive(Debug, PartialEq, Eq, Default)]
pub struct StreamPolicy {
pub(crate) default_rule: StreamRule,
pub(crate) prefix_rules_sorted_by_length: Box<[PrefixStreamRule]>,
}
impl StreamPolicy {
#[cfg_attr(
not(target_os = "linux"),
expect(dead_code, reason = "only used by the linux backend")
)]
pub(crate) fn stream_rule(&self, stream_name: &StreamName) -> StreamRule {
let best_match = self
.prefix_rules_sorted_by_length
.iter()
.rev()
.find(|PrefixStreamRule { prefix, .. }| stream_name.as_str().starts_with(prefix))
.map(|PrefixStreamRule { stream_state, .. }| stream_state)
.copied();
best_match.unwrap_or(self.default_rule)
}
}
impl FromStr for StreamPolicy {
type Err = ParseStreamFilterError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
if input.trim().is_empty() {
return Ok(Self::default());
}
let mut prefix_rules: Vec<PrefixStreamRule> = vec![];
let mut default_rule = StreamRule::default();
for directive in input
.split(DIRECTIVE_DELIMITER)
.map(str::trim)
{
if directive.is_empty() {
continue;
}
if let Ok(parsed_default_rule) = directive.parse::<StreamRule>() {
default_rule = parsed_default_rule;
continue;
}
let prefix_rule = directive.parse::<PrefixStreamRule>()?;
let prefix_was_empty = prefix_rule.prefix.is_empty();
if prefix_was_empty {
default_rule = prefix_rule.stream_state;
continue;
}
if let Some(previous_duplicate_rule) = prefix_rules
.iter_mut()
.find(|rule| rule.prefix == prefix_rule.prefix)
{
*previous_duplicate_rule = prefix_rule;
} else {
prefix_rules.push(prefix_rule);
}
}
prefix_rules.sort_by(|left, right| {
left.prefix
.len()
.cmp(&right.prefix.len())
.then_with(|| left.prefix.cmp(&right.prefix))
});
Ok(Self {
default_rule,
prefix_rules_sorted_by_length: prefix_rules.into_boxed_slice(),
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct PrefixStreamRule {
prefix: String,
stream_state: StreamRule,
}
impl FromStr for PrefixStreamRule {
type Err = ParseStreamFilterError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let (prefix, rule_value) = input
.split_once(PREFIX_RULE_ASSIGNMENT)
.unwrap_or((input, ""));
let prefix = prefix.trim();
let rule_value = rule_value.trim();
let stream_state = if rule_value.is_empty() {
StreamRule::On
} else {
rule_value.parse()?
};
Ok(Self {
prefix: prefix.to_string(),
stream_state,
})
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) enum StreamRule {
On,
#[default]
Off,
}
impl FromStr for StreamRule {
type Err = ParseStreamFilterError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if value.eq_ignore_ascii_case(ON) {
Ok(Self::On)
} else if value.eq_ignore_ascii_case(OFF) {
Ok(Self::Off)
} else {
Err(ParseStreamFilterError::InvalidStreamRuleValue(
value.to_string(),
))
}
}
}
impl std::fmt::Display for StreamRule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StreamRule::On => f.write_str(ON),
StreamRule::Off => f.write_str(OFF),
}
}
}
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum ParseStreamFilterError {
#[error("invalid stream rule value. Must be either {ON} or {OFF}, got {0}")]
InvalidStreamRuleValue(String),
}