Refactor and add documentation

This commit is contained in:
Matthias Endler 2020-11-10 00:03:50 +01:00
parent 1474ae802b
commit d0b7a64d0a
4 changed files with 115 additions and 99 deletions

View file

@ -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<String> for RequestMethod {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
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<HashSet<http::StatusCode>>) -> 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<reqwest::Error> 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<RegexSet>,
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> {

View file

@ -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: AsRef<Path>>(p: P) -> FileType {
let path = p.as_ref();
match path.extension() {
@ -18,6 +19,8 @@ fn resolve_file_type_by_path<P: AsRef<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<String>,
base_url: Option<String>,

View file

@ -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 {

107
src/types.rs Normal file
View file

@ -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<String> for RequestMethod {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
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<HashSet<http::StatusCode>>) -> 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<reqwest::Error> 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<RegexSet>,
/// 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,
}
}
}