emixnet 0.6.0

Higher-level HTTP, mail, and VPN helpers layered on EssentialMix core utilities.
Documentation
#[cfg(feature = "mail")]
pub mod mail;
pub mod reqwestx;

use reqwest::{Client, blocking::Client as BlockingClient};
use url::{ParseError, Url};
use urlencoding::{decode, encode};

use crate::{Error, Result};

pub const REMOTE_IP_URL: &'static str = "https://api.ipify.org";

pub fn url_encode<T: AsRef<str>>(value: T) -> String {
    encode(value.as_ref()).to_string()
}

pub fn url_decode<T: AsRef<str>>(value: T) -> Result<String> {
    Ok(decode(value.as_ref())
        .map_err(|e| Error::from_std_error(e))?
        .to_string())
}

pub fn to_url<T: AsRef<str>>(value: T) -> Result<Url> {
    const LOCALHOST: &str = "https://localhost";

    let value = value.as_ref();

    if value.is_empty() {
        return Ok(Url::parse(LOCALHOST).map_err(Error::from_std_error)?);
    }

    match Url::parse(value) {
        Ok(it) => Ok(it.into()),
        Err(ParseError::RelativeUrlWithoutBase) => Ok(Url::parse(LOCALHOST)
            .map_err(Error::from_std_error)?
            .join(value)
            .map_err(Error::from_std_error)?),
        Err(_) => Ok(Url::parse(&url_encode(value)).map_err(Error::from_std_error)?),
    }
}

fn append_if_not_empty<T: AsRef<str>>(base: &Url, component: T) -> Result<Url> {
    let component = component.as_ref();

    if component.is_empty() {
        return Ok(base.clone());
    }
    Ok(base.join(component).map_err(Error::from_std_error)?)
}

pub trait AsUrl<T> {
    fn as_url(&self) -> Result<Url>;
}

impl<T: AsRef<str>> AsUrl<T> for T {
    fn as_url(&self) -> Result<Url> {
        to_url(self)
    }
}

impl<T: AsRef<str>> AsUrl<T> for (T, T) {
    fn as_url(&self) -> Result<Url> {
        let base = to_url(&self.0)?;
        append_if_not_empty(&base, &self.1)
    }
}

impl<T: AsRef<str>> AsUrl<T> for (T, T, T) {
    fn as_url(&self) -> Result<Url> {
        let url = to_url(&self.0)?;
        let url = append_if_not_empty(&url, &self.1)?;
        let url = append_if_not_empty(&url, &self.2)?;
        Ok(url)
    }
}

impl<T: AsRef<str>> AsUrl<T> for (T, T, T, T) {
    fn as_url(&self) -> Result<Url> {
        let url = to_url(&self.0)?;
        let url = append_if_not_empty(&url, &self.1)?;
        let url = append_if_not_empty(&url, &self.2)?;
        let url = append_if_not_empty(&url, &self.3)?;
        Ok(url)
    }
}

impl<T: AsRef<str>> AsUrl<T> for (T, T, T, T, T) {
    fn as_url(&self) -> Result<Url> {
        let url = to_url(&self.0)?;
        let url = append_if_not_empty(&url, &self.1)?;
        let url = append_if_not_empty(&url, &self.2)?;
        let url = append_if_not_empty(&url, &self.3)?;
        let url = append_if_not_empty(&url, &self.4)?;
        Ok(url)
    }
}

impl<T: AsRef<str>, const N: usize> AsUrl<T> for [T; N] {
    fn as_url(&self) -> Result<Url> {
        self.iter().try_fold(to_url(&self[0])?, |url, component| {
            append_if_not_empty(&url, component)
        })
    }
}

impl<T: AsRef<str>> AsUrl<T> for Vec<T> {
    fn as_url(&self) -> Result<Url> {
        self.iter().try_fold(to_url(&self[0])?, |url, component| {
            append_if_not_empty(&url, component)
        })
    }
}

pub fn remove<T: AsRef<str>>(url: &mut Url, value: T) {
    let value = value.as_ref();

    if value.is_empty() {
        return;
    }
    url.set_path(&url.path().replace(value, ""));
}

pub fn get_public_ip(client: &BlockingClient) -> Result<String> {
    let response = client
        .get(REMOTE_IP_URL)
        .send()
        .map_err(Error::from_std_error)?;

    if !response.status().is_success() {
        return Err(Error::from_std_error(
            response.error_for_status().unwrap_err(),
        ));
    }

    let text = response.text().map_err(Error::from_std_error)?;
    Ok(text)
}

pub async fn get_public_ip_async(client: &Client) -> Result<String> {
    let response = client
        .get(REMOTE_IP_URL)
        .send()
        .await
        .map_err(Error::from_std_error)?;

    if !response.status().is_success() {
        return Err(Error::from_std_error(
            response.error_for_status().unwrap_err(),
        ));
    }

    let text = response.text().await.map_err(Error::from_std_error)?;
    Ok(text)
}