use icalendar::Property;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attachment {
pub uri: String,
pub params: Vec<(String, String)>,
}
impl Attachment {
pub fn new(uri: impl Into<String>) -> Self {
Attachment {
uri: uri.into(),
params: Vec::new(),
}
}
pub fn filename(&self) -> Option<&str> {
self.params
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case("FILENAME"))
.map(|(_, v)| v.as_str())
}
pub fn from_property(prop: &Property) -> Option<Self> {
if is_binary(prop) {
return None;
}
let params = prop
.params()
.values()
.map(|p| (p.key().to_string(), p.value().to_string()))
.collect();
Some(Attachment {
uri: prop.value().to_string(),
params,
})
}
}
fn is_binary(prop: &Property) -> bool {
prop.params().values().any(|p| {
(p.key().eq_ignore_ascii_case("VALUE") && p.value().eq_ignore_ascii_case("BINARY"))
|| (p.key().eq_ignore_ascii_case("ENCODING")
&& p.value().eq_ignore_ascii_case("BASE64"))
})
}
impl From<&Attachment> for Property {
fn from(value: &Attachment) -> Self {
let mut prop = Property::new("ATTACH", &value.uri);
for (k, v) in &value.params {
prop.add_parameter(k, v);
}
prop.done()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keeps_uri_value() {
let prop = Property::new("ATTACH", "https://drive.google.com/file/d/abc").done();
let attachment = Attachment::from_property(&prop).unwrap();
assert_eq!(attachment.uri, "https://drive.google.com/file/d/abc");
assert!(attachment.params.is_empty());
}
#[test]
fn captures_params() {
let mut prop = Property::new("ATTACH", "https://p01.icloud.com/att/abc");
prop.add_parameter("MANAGED-ID", "1");
prop.add_parameter("FMTTYPE", "application/pdf");
prop.add_parameter("FILENAME", "agenda.pdf");
let attachment = Attachment::from_property(&prop.done()).unwrap();
assert!(
attachment
.params
.contains(&("MANAGED-ID".to_string(), "1".to_string()))
);
assert!(
attachment
.params
.contains(&("FILENAME".to_string(), "agenda.pdf".to_string()))
);
}
#[test]
fn filename_reads_param_case_insensitively() {
let mut prop = Property::new("ATTACH", "https://p01.icloud.com/att/abc");
prop.add_parameter("filename", "agenda.pdf");
let attachment = Attachment::from_property(&prop.done()).unwrap();
assert_eq!(attachment.filename(), Some("agenda.pdf"));
}
#[test]
fn filename_is_none_without_param() {
let attachment = Attachment::new("https://drive.google.com/file/d/abc");
assert_eq!(attachment.filename(), None);
}
#[test]
fn skips_binary_value() {
let mut prop = Property::new("ATTACH", "VGhlIHF1aWNr");
prop.add_parameter("VALUE", "BINARY");
prop.add_parameter("ENCODING", "BASE64");
assert!(Attachment::from_property(&prop.done()).is_none());
}
#[test]
fn skips_base64_encoding_without_value_param() {
let mut prop = Property::new("ATTACH", "VGhlIHF1aWNr");
prop.add_parameter("ENCODING", "BASE64");
assert!(Attachment::from_property(&prop.done()).is_none());
}
#[test]
fn writes_attach_property_with_params() {
let attachment = Attachment {
uri: "https://example.com/report.doc".to_string(),
params: vec![("FMTTYPE".to_string(), "application/msword".to_string())],
};
let prop = Property::from(&attachment);
assert_eq!(prop.key(), "ATTACH");
assert_eq!(prop.value(), "https://example.com/report.doc");
assert_eq!(
prop.params().get("FMTTYPE").map(|p| p.value()),
Some("application/msword")
);
}
}