423 lines
12 KiB
Rust
423 lines
12 KiB
Rust
use std::str::SplitWhitespace;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
commands::RequestParserError,
|
|
common::{Priority, Tag},
|
|
};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum CaseSensitivity {
|
|
CaseSensitive,
|
|
CaseInsensitive,
|
|
CommandDependent,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum ComparisonOperator {
|
|
Equal,
|
|
NotEqual,
|
|
GreaterThan,
|
|
GreaterThanOrEqual,
|
|
LessThan,
|
|
LessThanOrEqual,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum Filter {
|
|
Not(Box<Filter>),
|
|
And(Box<Filter>, Box<Filter>),
|
|
|
|
// The bool indicates whether the comparison is negated (true for !=, false for ==)
|
|
EqTag(Tag, CaseSensitivity, bool),
|
|
Contains(Tag, CaseSensitivity, bool),
|
|
StartsWith(Tag, CaseSensitivity, bool),
|
|
PerlRegex(Tag),
|
|
NegPerlRegex(Tag),
|
|
EqUri(String),
|
|
Base(String),
|
|
ModifiedSince(u64),
|
|
AudioFormatEq {
|
|
sample_rate: u32,
|
|
bits: u8,
|
|
channels: u8,
|
|
},
|
|
AudioFormatEqMask {
|
|
sample_rate: Option<u32>,
|
|
bits: Option<u8>,
|
|
channels: Option<u8>,
|
|
},
|
|
PrioCmp(ComparisonOperator, Priority),
|
|
}
|
|
|
|
pub fn parse_filter(parts: &mut SplitWhitespace<'_>) -> Result<Filter, RequestParserError> {
|
|
// TODO: count parentheses and extract the full filter string
|
|
unimplemented!()
|
|
}
|
|
|
|
fn parse_filter_str(string: &str) -> Result<Filter, RequestParserError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_parse_filter_eq_tag() {
|
|
let mut parts = "(artist == 'The Beatles')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::EqTag(
|
|
Tag::Artist("The Beatles".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
false,
|
|
)
|
|
);
|
|
|
|
let mut parts = "(artist != 'The Beatles')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::EqTag(
|
|
Tag::Artist("The Beatles".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
true,
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_contains() {
|
|
let mut parts = "(album contains 'Greatest Hits')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::Contains(
|
|
Tag::Album("Greatest Hits".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
false,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(album !contains 'Greatest Hits')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::Contains(
|
|
Tag::Album("Greatest Hits".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
true,
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_starts_with() {
|
|
let mut parts = "(title starts_with 'Symphony No. ')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::StartsWith(
|
|
Tag::Title("Symphony No. ".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
false,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(title !starts_with 'Symphony No. ')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::StartsWith(
|
|
Tag::Title("Symphony No. ".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
true,
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_perl_regex_positive() {
|
|
let mut parts = "(composer =~ 'Beethoven.*')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::PerlRegex(Tag::Composer("Beethoven.*".to_string())),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_perl_regex_negative() {
|
|
let mut parts = "(genre !~ 'Pop.*')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::NegPerlRegex(Tag::Genre("Pop.*".to_string())),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_base() {
|
|
let mut parts = "(base 'Rock/Classics')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(filter, Filter::Base("Rock/Classics".to_string()),);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_modified_since() {
|
|
let mut parts = "(modified-since '1622505600')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(filter, Filter::ModifiedSince(1622505600),);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_audio_added_since() {
|
|
let mut parts = "(added-since '1622505600')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(filter, Filter::ModifiedSince(1622505600),);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_audio_format_eq() {
|
|
let mut parts = "(AudioFormat == '44100:16:2')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::AudioFormatEq {
|
|
sample_rate: 44100,
|
|
bits: 16,
|
|
channels: 2,
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_audio_format_eq_mask() {
|
|
let mut parts = "(AudioFormat =~ '44100:*:2')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::AudioFormatEqMask {
|
|
sample_rate: Some(44100),
|
|
bits: None,
|
|
channels: Some(2),
|
|
},
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_prio_cmp() {
|
|
let mut parts = "(prio >= 42)".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::PrioCmp(ComparisonOperator::GreaterThanOrEqual, 42),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_not() {
|
|
let mut parts = "(!(artist == 'The Beatles'))".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::Not(Box::new(Filter::EqTag(
|
|
Tag::Artist("The Beatles".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
false,
|
|
))),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_and() {
|
|
let mut parts =
|
|
"((artist == 'The Beatles') AND (album == 'Abbey Road'))".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::And(
|
|
Box::new(Filter::EqTag(
|
|
Tag::Artist("The Beatles".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
false,
|
|
)),
|
|
Box::new(Filter::EqTag(
|
|
Tag::Album("Abbey Road".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
false,
|
|
)),
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_explicitly_case_sensitive() {
|
|
let mut parts = "(artist eq_cs 'The Beatles')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::EqTag(
|
|
Tag::Artist("The Beatles".to_string()),
|
|
CaseSensitivity::CaseSensitive,
|
|
false,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(artist !eq_cs 'The Beatles')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::EqTag(
|
|
Tag::Artist("The Beatles".to_string()),
|
|
CaseSensitivity::CaseSensitive,
|
|
true,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(album contains_cs 'Greatest Hits')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::Contains(
|
|
Tag::Album("Greatest Hits".to_string()),
|
|
CaseSensitivity::CaseSensitive,
|
|
false,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(album !contains_cs 'Greatest Hits')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::Contains(
|
|
Tag::Album("Greatest Hits".to_string()),
|
|
CaseSensitivity::CaseSensitive,
|
|
true,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(title starts_with_cs 'Symphony No. ')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::StartsWith(
|
|
Tag::Title("Symphony No. ".to_string()),
|
|
CaseSensitivity::CaseSensitive,
|
|
false,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(title !starts_with_cs 'Symphony No. ')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::StartsWith(
|
|
Tag::Title("Symphony No. ".to_string()),
|
|
CaseSensitivity::CaseSensitive,
|
|
true,
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_explicitly_case_insensitive() {
|
|
let mut parts = "(artist eq_ci 'The Beatles')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::EqTag(
|
|
Tag::Artist("The Beatles".to_string()),
|
|
CaseSensitivity::CaseInsensitive,
|
|
false,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(artist !eq_ci 'The Beatles')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::EqTag(
|
|
Tag::Artist("The Beatles".to_string()),
|
|
CaseSensitivity::CaseInsensitive,
|
|
true,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(album contains_ci 'Greatest Hits')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::Contains(
|
|
Tag::Album("Greatest Hits".to_string()),
|
|
CaseSensitivity::CaseInsensitive,
|
|
false,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(album !contains_ci 'Greatest Hits')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::Contains(
|
|
Tag::Album("Greatest Hits".to_string()),
|
|
CaseSensitivity::CaseInsensitive,
|
|
true,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(title starts_with_ci 'Symphony No. ')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::StartsWith(
|
|
Tag::Title("Symphony No. ".to_string()),
|
|
CaseSensitivity::CaseInsensitive,
|
|
false,
|
|
),
|
|
);
|
|
|
|
let mut parts = "(title !starts_with_ci 'Symphony No. ')".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::StartsWith(
|
|
Tag::Title("Symphony No. ".to_string()),
|
|
CaseSensitivity::CaseInsensitive,
|
|
true,
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_string_escapes() {
|
|
let mut parts = "(Artist == \"foo\\'bar\\\"\")".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::EqTag(
|
|
Tag::Artist("foo'bar\"".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
false,
|
|
),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_filter_excessive_whitespace() {
|
|
let mut parts = "(\tartist\n == 'The Beatles' ) ".split_whitespace();
|
|
let filter = parse_filter(&mut parts).unwrap();
|
|
assert_eq!(
|
|
filter,
|
|
Filter::EqTag(
|
|
Tag::Artist("The Beatles".to_string()),
|
|
CaseSensitivity::CommandDependent,
|
|
false,
|
|
)
|
|
);
|
|
}
|
|
}
|