mdbook-section-validator 0.1.0

mdBook preprocessor for defining conditionally valid sections.
Documentation
use url::Url;
use regex::Regex;
use serde::Deserialize;
use reqwest::{Result, StatusCode};
use reqwest::blocking::{Client, Response};

#[derive(Debug, Eq, PartialEq)]
pub enum GithubIssueType {
    Issue,
    PullRequest,
}

fn issue_type_from_string(str: &str) -> GithubIssueType {
    if str == "issues" {
        GithubIssueType::Issue
    } else {
        GithubIssueType::PullRequest
    }
}

#[derive(Debug, Eq, PartialEq)]
pub enum Issue<'u> {
    Github(&'u str, &'u str, &'u str, GithubIssueType, &'u Url),
    Link(&'u Url),
}

pub fn issue_from_url(url: &Url) -> Issue {
    let github_regex = Regex::new(r"(?i)github.com/(.+?)/(.+?)/(issues|pull)/(\d+)$").unwrap();
    return if let Some(capture) = github_regex.captures(url.as_str()) {
        let issue_type_string = capture.get(3).unwrap().as_str();
        let issue_type = issue_type_from_string(issue_type_string);

        Issue::Github(
            capture.get(1).unwrap().as_str(),
            capture.get(2).unwrap().as_str(),
            capture.get(4).unwrap().as_str(),
            issue_type,
            url,
        )
    } else {
        Issue::Link(url)
    };
}

#[derive(Debug, Eq, PartialEq)]
pub enum ValidationResult {
    NoLongerValid,
    StillValid,
}

#[derive(Deserialize, Debug, Eq, PartialEq)]
struct IssueResult {
    state: String
}

pub trait IssueValidator {
    fn validate(&self, issue: &Issue) -> ValidationResult;
}

pub struct DefaultIssueValidator;

impl IssueValidator for DefaultIssueValidator {
    fn validate(&self, issue: &Issue) -> ValidationResult {
        match issue {
            Issue::Github(owner, repo, number, issue_type, _url) => self.github_validation_result(owner, repo, number, issue_type),
            Issue::Link(url) => self.arbitrary_url_validation_result(url)
        }
    }
}

impl DefaultIssueValidator {
    fn github_validation_result(&self, owner: &str, repo: &str, number: &str, issue_type: &GithubIssueType) -> ValidationResult {
        let issue_kind = match issue_type {
            GithubIssueType::Issue => "issues",
            GithubIssueType::PullRequest => "pulls"
        };

        let request_url = format!(
            "https://api.github.com/repos/{owner}/{repo}/{issue_kind}/{number}",
            owner = owner,
            repo = repo,
            issue_kind = issue_kind,
            number = number
        );
        let client = Client::new();
        let request = client.get(&request_url)
            .header("User-Agent", "younata/mdbook-section-validator");
        let send_result: Result<Response> = request.send();
        if let Result::Ok(response) = send_result {
            let json_result: Result<IssueResult> = response.json();
            if let Result::Ok(issue) = json_result {
                if issue.state.as_str() == "open" {
                    return ValidationResult::StillValid;
                }
            } else {
                eprintln!("Unable to unwrap json: {}", json_result.unwrap_err());
            }
        } else {
            eprintln!("bad response: {}", send_result.unwrap_err());
        }
        return ValidationResult::NoLongerValid;
    }

    fn arbitrary_url_validation_result(&self, url: &Url) -> ValidationResult {
        let client = Client::new();
        let request = client.head(url.as_str())
            .header("User-Agent", "younata/mdbook-section-validator");
        let result: Result<Response> = request.send();

        if let Result::Ok(response) = result {
            if response.status() == StatusCode::OK {
                return ValidationResult::StillValid;
            }
        }
        return ValidationResult::NoLongerValid;
    }
}

#[cfg(test)]
mod tests {
    use reqwest::Url;
    use crate::issue_validator::{GithubIssueType, Issue, issue_from_url};

    #[test]
    fn issue_from_url_github_pr() {
        let url = Url::parse("https://github.com/rust-lang/mdBook/pull/1539").unwrap();

        assert_eq!(issue_from_url(&url), Issue::Github("rust-lang", "mdBook", "1539", GithubIssueType::PullRequest, &url));
    }

    #[test]
    fn issue_from_url_github_issue() {
        let url = Url::parse("https://github.com/rust-lang/mdBook/issues/1538").unwrap();

        assert_eq!(issue_from_url(&url), Issue::Github("rust-lang", "mdBook", "1538", GithubIssueType::Issue, &url));
    }

    #[test]
    fn issue_from_url_arbitrary_link() {
        let url = Url::parse("https://example.com").unwrap();

        assert_eq!(issue_from_url(&url), Issue::Link(&url));
    }
}