From d0b7a64d0af4eceb6142ac9cc477a5acae1fb258 Mon Sep 17 00:00:00 2001 From: Matthias Endler Date: Tue, 10 Nov 2020 00:03:50 +0100 Subject: [PATCH] Refactor and add documentation --- src/checker.rs | 100 +------------------------------------------ src/collector.rs | 3 ++ src/main.rs | 4 +- src/types.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 99 deletions(-) create mode 100644 src/types.rs diff --git a/src/checker.rs b/src/checker.rs index 3f695d0..b4bd212 100644 --- a/src/checker.rs +++ b/src/checker.rs @@ -1,8 +1,7 @@ use crate::{ extract::{self, Uri}, - options::Config, + types::{Excludes, RequestMethod, Status}, }; -use anyhow::anyhow; use anyhow::{Context, Result}; use check_if_email_exists::{check_email, CheckEmailInput}; use headers::{HeaderMap, HeaderValue}; @@ -11,105 +10,10 @@ use indicatif::ProgressBar; use regex::{Regex, RegexSet}; use reqwest::header; use std::net::IpAddr; -use std::{collections::HashSet, convert::TryFrom, time::Duration}; +use std::{collections::HashSet, time::Duration}; use tokio::time::delay_for; use url::Url; -pub(crate) enum RequestMethod { - GET, - HEAD, -} - -impl TryFrom for RequestMethod { - type Error = anyhow::Error; - fn try_from(value: String) -> Result { - match value.to_lowercase().as_ref() { - "get" => Ok(RequestMethod::GET), - "head" => Ok(RequestMethod::HEAD), - _ => Err(anyhow!("Only `get` and `head` allowed, got {}", value)), - } - } -} - -#[derive(Debug)] -pub enum Status { - Ok(http::StatusCode), - Failed(http::StatusCode), - Timeout, - Redirected, - Excluded, - Error(String), -} - -impl Status { - pub fn new(statuscode: http::StatusCode, accepted: Option>) -> Self { - if let Some(accepted) = accepted { - if accepted.contains(&statuscode) { - return Status::Ok(statuscode); - } - } else if statuscode.is_success() { - return Status::Ok(statuscode); - }; - if statuscode.is_redirection() { - Status::Redirected - } else { - Status::Failed(statuscode) - } - } - - pub fn is_success(&self) -> bool { - matches!(self, Status::Ok(_)) - } - - pub fn is_excluded(&self) -> bool { - matches!(self, Status::Excluded) - } -} - -impl From for Status { - fn from(e: reqwest::Error) -> Self { - if e.is_timeout() { - Status::Timeout - } else { - Status::Error(e.to_string()) - } - } -} - -/// Exclude configuration for the link checker. -pub(crate) struct Excludes { - regex: Option, - private_ips: bool, - link_local_ips: bool, - loopback_ips: bool, -} - -impl Excludes { - pub fn from_options(config: &Config) -> Self { - // exclude_all_private option turns on all "private" excludes, - // including private IPs, link-local IPs and loopback IPs - let enable_exclude = |opt| opt || config.exclude_all_private; - - Self { - regex: RegexSet::new(&config.exclude).ok(), - private_ips: enable_exclude(config.exclude_private), - link_local_ips: enable_exclude(config.exclude_link_local), - loopback_ips: enable_exclude(config.exclude_loopback), - } - } -} - -impl Default for Excludes { - fn default() -> Self { - Self { - regex: None, - private_ips: false, - link_local_ips: false, - loopback_ips: false, - } - } -} - /// A link checker using an API token for Github links /// otherwise a normal HTTP client. pub(crate) struct Checker<'a> { diff --git a/src/collector.rs b/src/collector.rs index 2bb6488..f398481 100644 --- a/src/collector.rs +++ b/src/collector.rs @@ -6,6 +6,7 @@ use reqwest::Url; use std::path::Path; use std::{collections::HashSet, fs}; +/// Detect if the given path points to a Markdown, HTML, or plaintext file. fn resolve_file_type_by_path>(p: P) -> FileType { let path = p.as_ref(); match path.extension() { @@ -18,6 +19,8 @@ fn resolve_file_type_by_path>(p: P) -> FileType { } } +/// Fetch all unique links from a vector of inputs +/// All relative URLs get prefixed with `base_url` if given. pub(crate) async fn collect_links( inputs: Vec, base_url: Option, diff --git a/src/main.rs b/src/main.rs index bf44c7f..cc969aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,10 +14,12 @@ mod checker; mod collector; mod extract; mod options; +mod types; -use checker::{Checker, Excludes, Status}; +use checker::Checker; use extract::Uri; use options::{Config, LycheeOptions}; +use types::{Excludes, Status}; /// A C-like enum that can be cast to `i32` and used as process exit code. enum ExitCode { diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..27730e6 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,107 @@ +use crate::options::Config; +use anyhow::anyhow; +use std::{collections::HashSet, convert::TryFrom}; + +use regex::RegexSet; + +/// Specifies how requests to websites will be made +pub(crate) enum RequestMethod { + GET, + HEAD, +} + +impl TryFrom for RequestMethod { + type Error = anyhow::Error; + fn try_from(value: String) -> Result { + match value.to_lowercase().as_ref() { + "get" => Ok(RequestMethod::GET), + "head" => Ok(RequestMethod::HEAD), + _ => Err(anyhow!("Only `get` and `head` allowed, got {}", value)), + } + } +} + +/// Response status of the request +#[derive(Debug)] +pub enum Status { + Ok(http::StatusCode), + Failed(http::StatusCode), + Timeout, + Redirected, + Excluded, + Error(String), +} + +impl Status { + pub fn new(statuscode: http::StatusCode, accepted: Option>) -> Self { + if let Some(accepted) = accepted { + if accepted.contains(&statuscode) { + return Status::Ok(statuscode); + } + } else if statuscode.is_success() { + return Status::Ok(statuscode); + }; + if statuscode.is_redirection() { + Status::Redirected + } else { + Status::Failed(statuscode) + } + } + + pub fn is_success(&self) -> bool { + matches!(self, Status::Ok(_)) + } + + pub fn is_excluded(&self) -> bool { + matches!(self, Status::Excluded) + } +} + +impl From for Status { + fn from(e: reqwest::Error) -> Self { + if e.is_timeout() { + Status::Timeout + } else { + Status::Error(e.to_string()) + } + } +} + +/// Exclude configuration for the link checker. +/// You can ignore links based on +pub(crate) struct Excludes { + pub regex: Option, + /// Example: 192.168.0.1 + pub private_ips: bool, + /// Example: 169.254.0.0 + pub link_local_ips: bool, + /// For IPv4: 127.0. 0.1/8 + /// For IPv6: ::1/128 + pub loopback_ips: bool, +} + +impl Excludes { + pub fn from_options(config: &Config) -> Self { + // exclude_all_private option turns on all "private" excludes, + // including private IPs, link-local IPs and loopback IPs + let enable_exclude = |opt| opt || config.exclude_all_private; + + Self { + regex: RegexSet::new(&config.exclude).ok(), + private_ips: enable_exclude(config.exclude_private), + link_local_ips: enable_exclude(config.exclude_link_local), + loopback_ips: enable_exclude(config.exclude_loopback), + } + } +} + +impl Default for Excludes { + fn default() -> Self { + Self { + regex: None, + private_ips: false, + link_local_ips: false, + loopback_ips: false, + } + } +}