carddav: Fix filter test="allof"|"anyof"

This commit is contained in:
Lennart
2025-12-31 14:46:49 +01:00
parent e5d6541ffb
commit 845b3e61e3
4 changed files with 35 additions and 31 deletions

View File

@@ -1,9 +1,8 @@
use super::FilterElement;
use rstest::rstest; use rstest::rstest;
use rustical_ical::CalendarObject; use rustical_ical::CalendarObject;
use rustical_xml::XmlDocument; use rustical_xml::XmlDocument;
use crate::calendar::methods::report::calendar_query::FilterElement;
const ICS_1: &str = r"BEGIN:VCALENDAR const ICS_1: &str = r"BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN PRODID:-//Example Corp.//CalDAV Client//EN

View File

@@ -2,10 +2,11 @@ use crate::{
address_object::AddressObjectPropWrapperName, address_object::AddressObjectPropWrapperName,
addressbook::methods::report::addressbook_query::PropFilterElement, addressbook::methods::report::addressbook_query::PropFilterElement,
}; };
use derive_more::{From, Into};
use ical::property::Property; use ical::property::Property;
use rustical_dav::xml::{PropfindType, TextMatchElement}; use rustical_dav::xml::{PropfindType, TextMatchElement};
use rustical_ical::{AddressObject, UtcDateTime}; use rustical_ical::{AddressObject, UtcDateTime};
use rustical_xml::XmlDeserialize; use rustical_xml::{ValueDeserialize, XmlDeserialize, XmlRootTag};
#[derive(XmlDeserialize, Clone, Debug, PartialEq, Eq)] #[derive(XmlDeserialize, Clone, Debug, PartialEq, Eq)]
#[allow(dead_code)] #[allow(dead_code)]
@@ -46,18 +47,34 @@ impl ParamFilterElement {
} }
} }
#[derive(XmlDeserialize, Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq, Default, From, Into)]
#[allow(dead_code)] pub struct Allof(pub bool);
impl ValueDeserialize for Allof {
fn deserialize(val: &str) -> Result<Self, rustical_xml::XmlError> {
Ok(Self(match val {
"allof" => true,
"anyof" => false,
_ => {
return Err(rustical_xml::XmlError::InvalidVariant(format!(
"Invalid test parameter: {val}"
)));
}
}))
}
}
// <!ELEMENT filter (prop-filter*)> // <!ELEMENT filter (prop-filter*)>
// <!ATTLIST filter test (anyof | allof) "anyof"> // <!ATTLIST filter test (anyof | allof) "anyof">
// <!-- test value: // <!-- test value:
// anyof logical OR for prop-filter matches // anyof logical OR for prop-filter matches
// allof logical AND for prop-filter matches --> // allof logical AND for prop-filter matches -->
#[derive(XmlDeserialize, XmlRootTag, Clone, Debug, PartialEq, Eq)]
#[xml(root = "filter", ns = "rustical_dav::namespace::NS_CARDDAV")]
#[allow(dead_code)]
pub struct FilterElement { pub struct FilterElement {
#[xml(ty = "attr")] #[xml(ty = "attr", default = "Default::default")]
pub anyof: Option<String>, pub test: Allof,
#[xml(ty = "attr")]
pub allof: Option<String>,
#[xml(ns = "rustical_dav::namespace::NS_CARDDAV", flatten)] #[xml(ns = "rustical_dav::namespace::NS_CARDDAV", flatten)]
pub(crate) prop_filter: Vec<PropFilterElement>, pub(crate) prop_filter: Vec<PropFilterElement>,
} }
@@ -65,11 +82,7 @@ pub struct FilterElement {
impl FilterElement { impl FilterElement {
#[must_use] #[must_use]
pub fn matches(&self, addr_object: &AddressObject) -> bool { pub fn matches(&self, addr_object: &AddressObject) -> bool {
let allof = match (self.allof.is_some(), self.anyof.is_some()) { let Allof(allof) = self.test;
(true, false) => true,
(false, _) => false,
(true, true) => panic!("wat"),
};
let mut results = self let mut results = self
.prop_filter .prop_filter
.iter() .iter()

View File

@@ -1,4 +1,4 @@
use super::ParamFilterElement; use super::{Allof, ParamFilterElement};
use ical::{parser::Component, property::Property}; use ical::{parser::Component, property::Property};
use rustical_dav::xml::TextMatchElement; use rustical_dav::xml::TextMatchElement;
use rustical_ical::AddressObject; use rustical_ical::AddressObject;
@@ -22,25 +22,17 @@ pub struct PropFilterElement {
pub(crate) text_match: Vec<TextMatchElement>, pub(crate) text_match: Vec<TextMatchElement>,
#[xml(ns = "rustical_dav::namespace::NS_CARDDAV", flatten)] #[xml(ns = "rustical_dav::namespace::NS_CARDDAV", flatten)]
pub(crate) param_filter: Vec<ParamFilterElement>, pub(crate) param_filter: Vec<ParamFilterElement>,
#[xml(ty = "attr", default = "Default::default")]
pub test: Allof,
#[xml(ty = "attr")] #[xml(ty = "attr")]
pub(crate) name: String, pub(crate) name: String,
#[xml(ty = "attr")]
pub anyof: Option<String>,
#[xml(ty = "attr")]
pub allof: Option<String>,
} }
impl PropFilterElement { impl PropFilterElement {
#[must_use] #[must_use]
pub fn match_property(&self, property: &Property) -> bool { pub fn match_property(&self, property: &Property) -> bool {
let allof = match (self.allof.is_some(), self.anyof.is_some()) { let Allof(allof) = self.test;
(true, false) => true,
(false, _) => false,
(true, true) => panic!("wat"),
};
let text_matches = self let text_matches = self
.text_match .text_match
.iter() .iter()

View File

@@ -158,7 +158,9 @@ mod tests {
use super::*; use super::*;
use crate::{ use crate::{
address_object::AddressObjectPropName, address_object::AddressObjectPropName,
addressbook::methods::report::addressbook_query::{FilterElement, PropFilterElement}, addressbook::methods::report::addressbook_query::{
Allof, FilterElement, PropFilterElement,
},
}; };
use rustical_dav::xml::{PropElement, sync_collection::SyncLevel}; use rustical_dav::xml::{PropElement, sync_collection::SyncLevel};
@@ -250,15 +252,13 @@ mod tests {
vec![] vec![]
)), )),
filter: FilterElement { filter: FilterElement {
anyof: None, test: Allof::default(),
allof: None,
prop_filter: vec![PropFilterElement { prop_filter: vec![PropFilterElement {
name: "FN".to_owned(), name: "FN".to_owned(),
is_not_defined: None, is_not_defined: None,
text_match: vec![], text_match: vec![],
param_filter: vec![], param_filter: vec![],
allof: None, test: Allof::default()
anyof: None
}] }]
} }
}) })