initial commit
This commit is contained in:
commit
6757ad28f8
8 changed files with 225 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
32
Cargo.toml
Normal file
32
Cargo.toml
Normal 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
4
src/bin/yapilt4.rs
Normal 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
4
src/bin/yapilt6.rs
Normal 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
30
src/error.rs
Normal 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
12
src/ip_type.rs
Normal 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
83
src/lib.rs
Normal 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
58
src/provider.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue