initial commit

This commit is contained in:
Jan Christian Grünhage 2020-04-19 15:40:43 +02:00
commit 6757ad28f8
Signed by: jcgruenhage
GPG Key ID: 6594C449C633D10C
8 changed files with 225 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

32
Cargo.toml Normal file
View File

@ -0,0 +1,32 @@
[package]
name = "yapilt"
version = "0.1.0"
authors = ["Jan Christian Grünhage <jan.christian@gruenhage.xyz>"]
edition = "2018"
description = "Yet another public IP lookup tool - Get your public IP"
[[bin]]
name = "yapilt4"
path = "src/bin/yapilt4.rs"
required-features = ["reqwest-client", "tokio-threaded"]
[[bin]]
name = "yapilt6"
path = "src/bin/yapilt6.rs"
required-features = ["reqwest-client", "tokio-threaded"]
[features]
reqwest-client = ["reqwest"]
tokio-threaded = ["tokio"]
default = ["reqwest-client"]
[dependencies]
http = "0.2.1"
log = "0.4.8"
reqwest = { version = "0.10.4", optional = true }
tokio = { version = "0.2.18", features = ["rt-threaded", "macros"], optional = true }
[dev-dependencies]
reqwest = "0.10.4"

4
src/bin/yapilt4.rs Normal file
View File

@ -0,0 +1,4 @@
#[tokio::main]
async fn main() {
println!("{}", yapilt::reqwest_client::ipv4(&mut reqwest::Client::new()).await);
}

4
src/bin/yapilt6.rs Normal file
View File

@ -0,0 +1,4 @@
#[tokio::main]
async fn main() {
println!("{}", yapilt::reqwest_client::ipv6(&mut reqwest::Client::new()).await);
}

30
src/error.rs Normal file
View File

@ -0,0 +1,30 @@
use crate::{IpType, Provider};
use std::convert::From;
#[derive(Debug)]
pub enum Error {
Unsupported(IpType, Provider),
Http(http::Error),
#[cfg(feature = "reqwest-client")]
Reqwest(reqwest::Error),
}
impl From<(IpType, &Provider)> for Error {
fn from(tuple: (IpType, &Provider)) -> Self {
Self::Unsupported(tuple.0, tuple.1.clone())
}
}
impl From<http::Error> for Error {
fn from(error: http::Error) -> Self {
Self::Http(error)
}
}
#[cfg(feature = "reqwest-client")]
impl From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Self {
Self::Reqwest(error)
}
}

12
src/ip_type.rs Normal file
View File

@ -0,0 +1,12 @@
#[derive(Debug, Copy, Clone)]
pub enum IpType { V4, V6 }
impl ToString for IpType {
fn to_string(&self) -> String {
match self {
Self::V4 => String::from("IPv4"),
Self::V6 => String::from("IPv6"),
}
}
}

83
src/lib.rs Normal file
View File

@ -0,0 +1,83 @@
mod error;
mod provider;
mod ip_type;
pub use error::Error;
pub use provider::Provider;
pub use ip_type::IpType;
pub mod http {
use crate::{Error, IpType, Provider};
use std::default::Default;
pub fn ipv4() -> http::Request<&'static [u8]> {
ip(IpType::V4, Provider::default()).unwrap()
}
pub fn ipv6() -> http::Request<&'static [u8]> {
ip(IpType::V6, Provider::default()).unwrap()
}
pub fn ip<T: Into<Provider>>(ip_type: IpType, provider: T) -> Result<http::Request<&'static [u8]>, Error> {
let provider : Provider = provider.into();
provider.ip(ip_type)
}
}
#[cfg(feature = "reqwest-client")]
pub mod reqwest_client {
use reqwest::Client;
use crate::{Error, IpType, Provider, http::ip as http_ip};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::default::Default;
use std::convert::TryInto;
pub async fn ipv4(client: &mut Client) -> Ipv4Addr {
match ip(client, IpType::V4, Provider::default()).await.unwrap() {
IpAddr::V4(ip) => ip,
_ => {
log::error!("This should be unreachable, if the provider is working and configured correctly");
panic!();
}
}
}
pub async fn ipv6(client: &mut Client) -> Ipv6Addr {
match ip(client, IpType::V6, Provider::default()).await.unwrap() {
IpAddr::V6(ip) => ip,
_ => {
log::error!("This should be unreachable, if the provider is working and configured correctly");
panic!();
}
}
}
pub async fn ip<T: Into<Provider>>(client: &mut Client, ip_type: IpType, provider: T) -> Result<IpAddr, Error> {
let request = http_ip(ip_type, provider)?;
let response = client
.execute(request.try_into().unwrap())
.await?
.text()
.await?;
Ok(match ip_type {
IpType::V4 => {
let addr : Ipv4Addr = response.parse().unwrap();
IpAddr::from(addr)
},
IpType::V6 => {
let addr : Ipv6Addr = response.parse().unwrap();
IpAddr::from(addr)
},
})
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

58
src/provider.rs Normal file
View File

@ -0,0 +1,58 @@
use crate::{Error, IpType};
#[derive(Debug, PartialEq, Clone)]
pub struct Provider {
pub v4: Option<&'static str>,
pub v6: Option<&'static str>,
}
impl Provider {
pub fn ipify_org() -> Self {
Self { v4: Some("https://api.ipify.org"), v6: Some("https://api6.ipify.org") }
}
pub fn ident_me() -> Self {
Self { v4: Some("https://v4.ident.me"), v6: Some("https://v6.ident.me") }
}
pub fn ip(&self, ip_type: IpType) -> Result<http::Request<&'static [u8]>, Error> {
let uri = match ip_type {
IpType::V4 => self.v4.ok_or((ip_type, self))?,
IpType::V6 => self.v6.ok_or((ip_type, self))?,
};
Ok(http::Request::get(uri).body(&[][..] as &'static [u8])?)
}
}
impl Default for Provider {
fn default() -> Self {
Self::ident_me()
}
}
impl std::fmt::Display for Provider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let provider = match self {
provider if provider == &Self::ipify_org() => String::from("ipify.org"),
provider if provider == &Self::ident_me() => String::from("ident.me"),
_ => String::from("unknown"),
};
write!(f, "{}", provider)
}
}
impl Into<Provider> for String {
fn into(self) -> Provider {
match self.as_ref() {
"ipify.org" => Provider::ipify_org(),
"ident.me" => Provider::ident_me(),
provider => {
// TODO: Handle this better
// I tried using TryInto here, but I couldn't figure out how to
// implement From<<T as TryInto<Provider>>::Error>.
log::error!("Couldn't find provider {}, falling back to the default", provider);
Provider::default()
}
}
}
}