webmention 0.6.0

Library and a CLI tool for sending and validation webmentions
Documentation
use crate::{wm_url::Url, WebmentionError};
use select::{
    document::Document,
    node::Node,
    predicate::{Attr, Class, Name, Not, Predicate},
};

#[derive(Debug)]
pub struct HTML {
    _source: Url,
    raw_html: String,
    doc: Option<Document>,
}

impl HTML {
    pub fn new(url: Url, raw_html: String) -> HTML {
        let mut html = HTML {
            _source: url,
            raw_html,
            doc: None,
        };
        html.doc = Some(Document::from(html.raw_html.as_str()));
        html
    }

    pub fn contains(&self, target: &Url) -> Result<(), WebmentionError> {
        let doc = self
            .doc
            .as_ref()
            .ok_or(WebmentionError::UnparseableDocument)?;
        let links = Name("a").and(Attr("href", target.as_str()));

        if doc.find(links).count() > 0 {
            Ok(())
        } else {
            Err(WebmentionError::NoDocumentLinks)
        }
    }

    pub fn doc(&self) -> Result<&Document, WebmentionError> {
        self.doc
            .as_ref()
            .ok_or(WebmentionError::UnparseableDocument)
    }

    pub async fn find_links(self: &HTML) -> Result<Vec<Url>, WebmentionError> {
        let mut links = Vec::new();

        let content_link = Name("a").and(Not(Class("u-url")));

        let doc = self
            .doc
            .as_ref()
            .ok_or(WebmentionError::UnparseableDocument)?;

        let entries = doc.find(Class("h-entry"));

        let nodes: Vec<Node> = if entries.count() > 0 {
            doc.find(Class("h-entry").descendant(content_link))
                .collect()
        } else {
            doc.find(content_link).collect()
        };

        for node in nodes.iter() {
            if let Some(href) = node.attr("href") {
                links.push(href.to_string());
            }
        }
        Ok(links
            .iter()
            .map(|l| Url::parse(l))
            .filter_map(|u| u.ok())
            .collect())
    }
}

#[cfg(test)]
mod test {
    use crate::http_client::get;
    use crate::wm_url::Url;
    use tokio_test::block_on;

    #[ignore]
    #[test]
    fn find_links_test() {
        let url = Url::parse("https://marinintim.com/notes/2021/hwc-rsvp/").unwrap();
        let response = block_on(get(&url)).unwrap();
        let links = block_on(response.html.find_links()).unwrap();
        let links_str: Vec<&str> = links.iter().map(|s| s.as_str()).collect();
        assert_eq!(
            links_str,
            vec![
                "https://evgenykuznetsov.org/events/2021/hwc-online/",
                "https://events.indieweb.org/2021/03/-hwc-09ReXTMBeU3M"
            ]
        );
    }
}