Avoid multiple main versions & concurrently get ipv4 and ipv6 addresses (#87)

* Avoid having multiple versions of main run function

* Concurrently get ipv4 and ipv6 addresses
This commit is contained in:
Kaan Barmore-Genç 2023-02-01 00:21:02 -05:00 committed by GitHub
parent 7e7a9da65e
commit f8060fad42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 31 additions and 24 deletions

View file

@ -2,7 +2,7 @@ use async_trait::async_trait;
use super::ip_source::IPSource; use super::ip_source::IPSource;
pub(crate) struct IPSourceIcanhazip {} pub(crate) struct IPSourceIcanhazip;
async fn get_ip(api_url: &str) -> anyhow::Result<String> { async fn get_ip(api_url: &str) -> anyhow::Result<String> {
let response = reqwest::get(api_url).await?; let response = reqwest::get(api_url).await?;
@ -12,14 +12,14 @@ async fn get_ip(api_url: &str) -> anyhow::Result<String> {
#[async_trait] #[async_trait]
impl IPSource for IPSourceIcanhazip { impl IPSource for IPSourceIcanhazip {
async fn get_ipv4() -> anyhow::Result<String> { async fn get_ipv4(&self) -> anyhow::Result<String> {
Ok(get_ip("https://ipv4.icanhazip.com") Ok(get_ip("https://ipv4.icanhazip.com")
.await? .await?
// icanazip puts a newline at the end // icanazip puts a newline at the end
.trim() .trim()
.to_string()) .to_string())
} }
async fn get_ipv6() -> anyhow::Result<String> { async fn get_ipv6(&self) -> anyhow::Result<String> {
Ok(get_ip("https://ipv6.icanhazip.com") Ok(get_ip("https://ipv6.icanhazip.com")
.await? .await?
// icanazip puts a newline at the end // icanazip puts a newline at the end
@ -32,13 +32,15 @@ impl IPSource for IPSourceIcanhazip {
mod tests { mod tests {
use regex::Regex; use regex::Regex;
use super::IPSource; use crate::ip_source::ip_source::IPSource;
use super::IPSourceIcanhazip; use super::IPSourceIcanhazip;
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
async fn ipv4_test() { async fn ipv4_test() {
let ipv4 = IPSourceIcanhazip::get_ipv4() let ipv4 = IPSourceIcanhazip
.get_ipv4()
.await .await
.expect("Failed to get the IP address"); .expect("Failed to get the IP address");
assert!(Regex::new(r"^\d+[.]\d+[.]\d+[.]\d+$") assert!(Regex::new(r"^\d+[.]\d+[.]\d+[.]\d+$")
@ -49,7 +51,8 @@ mod tests {
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
async fn ipv6_test() { async fn ipv6_test() {
let ipv6 = IPSourceIcanhazip::get_ipv6() let ipv6 = IPSourceIcanhazip
.get_ipv6()
.await .await
.expect("Failed to get the IP address"); .expect("Failed to get the IP address");
assert!(Regex::new(r"^([0-9a-fA-F]*:){7}[0-9a-fA-F]*$") assert!(Regex::new(r"^([0-9a-fA-F]*:){7}[0-9a-fA-F]*$")

View file

@ -2,6 +2,6 @@ use async_trait::async_trait;
#[async_trait] #[async_trait]
pub trait IPSource { pub trait IPSource {
async fn get_ipv4() -> anyhow::Result<String>; async fn get_ipv4(&self) -> anyhow::Result<String>;
async fn get_ipv6() -> anyhow::Result<String>; async fn get_ipv6(&self) -> anyhow::Result<String>;
} }

View file

@ -2,7 +2,7 @@ use async_trait::async_trait;
use super::ip_source::IPSource; use super::ip_source::IPSource;
pub(crate) struct IPSourceIpify {} pub(crate) struct IPSourceIpify;
async fn get_ip(api_url: &str) -> anyhow::Result<String> { async fn get_ip(api_url: &str) -> anyhow::Result<String> {
let response = reqwest::get(api_url).await?; let response = reqwest::get(api_url).await?;
@ -12,10 +12,10 @@ async fn get_ip(api_url: &str) -> anyhow::Result<String> {
#[async_trait] #[async_trait]
impl IPSource for IPSourceIpify { impl IPSource for IPSourceIpify {
async fn get_ipv4() -> anyhow::Result<String> { async fn get_ipv4(&self) -> anyhow::Result<String> {
get_ip("https://api.ipify.org").await get_ip("https://api.ipify.org").await
} }
async fn get_ipv6() -> anyhow::Result<String> { async fn get_ipv6(&self) -> anyhow::Result<String> {
get_ip("https://api6.ipify.org").await get_ip("https://api6.ipify.org").await
} }
} }
@ -30,7 +30,8 @@ mod tests {
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
async fn ipv4_test() { async fn ipv4_test() {
let ipv4 = IPSourceIpify::get_ipv4() let ipv4 = IPSourceIpify
.get_ipv4()
.await .await
.expect("Failed to get the IP address"); .expect("Failed to get the IP address");
assert!(Regex::new(r"^\d+[.]\d+[.]\d+[.]\d+$") assert!(Regex::new(r"^\d+[.]\d+[.]\d+[.]\d+$")
@ -41,7 +42,8 @@ mod tests {
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
async fn ipv6_test() { async fn ipv6_test() {
let ipv6 = IPSourceIpify::get_ipv6() let ipv6 = IPSourceIpify
.get_ipv6()
.await .await
.expect("Failed to get the IP address"); .expect("Failed to get the IP address");
assert!(Regex::new(r"^([0-9a-fA-F]*:){7}[0-9a-fA-F]*$") assert!(Regex::new(r"^([0-9a-fA-F]*:){7}[0-9a-fA-F]*$")

View file

@ -7,6 +7,7 @@ use ip_source::icanhazip::IPSourceIcanhazip;
use reqwest::{header, Client, ClientBuilder, StatusCode}; use reqwest::{header, Client, ClientBuilder, StatusCode};
use serde::Serialize; use serde::Serialize;
use std::{num::NonZeroU32, sync::Arc, time::Duration}; use std::{num::NonZeroU32, sync::Arc, time::Duration};
use tokio::join;
use tokio::{self, task::JoinHandle, time::sleep}; use tokio::{self, task::JoinHandle, time::sleep};
mod config; mod config;
mod gandi; mod gandi;
@ -39,11 +40,10 @@ pub struct APIPayload {
pub rrset_ttl: u32, pub rrset_ttl: u32,
} }
async fn run<IP: IPSource>(base_url: &str, conf: &Config) -> anyhow::Result<()> { async fn run(base_url: &str, ip_source: &Box<dyn IPSource>, conf: &Config) -> anyhow::Result<()> {
config::validate_config(conf).die_with(|error| format!("Invalid config: {}", error)); config::validate_config(conf).die_with(|error| format!("Invalid config: {}", error));
println!("Finding out the IP address..."); println!("Finding out the IP address...");
let ipv4_result = IP::get_ipv4().await; let (ipv4_result, ipv6_result) = join!(ip_source.get_ipv4(), ip_source.get_ipv6());
let ipv6_result = IP::get_ipv6().await;
let ipv4 = ipv4_result.as_ref(); let ipv4 = ipv4_result.as_ref();
let ipv6 = ipv6_result.as_ref(); let ipv6 = ipv6_result.as_ref();
println!("Found these:"); println!("Found these:");
@ -136,10 +136,11 @@ async fn main() -> anyhow::Result<()> {
} }
async fn run_dispatch(conf: &Config) -> anyhow::Result<()> { async fn run_dispatch(conf: &Config) -> anyhow::Result<()> {
match conf.ip_source { let ip_source: Box<dyn IPSource> = match conf.ip_source {
IPSourceName::Ipify => run::<IPSourceIpify>("https://api.gandi.net", conf).await, IPSourceName::Ipify => Box::new(IPSourceIpify),
IPSourceName::Icanhazip => run::<IPSourceIcanhazip>("https://api.gandi.net", conf).await, IPSourceName::Icanhazip => Box::new(IPSourceIcanhazip),
} };
run("https://api.gandi.net", &ip_source, conf).await
} }
#[cfg(test)] #[cfg(test)]
@ -151,14 +152,14 @@ mod tests {
use httpmock::MockServer; use httpmock::MockServer;
use tokio::fs; use tokio::fs;
struct IPSourceMock {} struct IPSourceMock;
#[async_trait] #[async_trait]
impl IPSource for IPSourceMock { impl IPSource for IPSourceMock {
async fn get_ipv4() -> anyhow::Result<String> { async fn get_ipv4(&self) -> anyhow::Result<String> {
Ok("192.168.0.0".to_string()) Ok("192.168.0.0".to_string())
} }
async fn get_ipv6() -> anyhow::Result<String> { async fn get_ipv6(&self) -> anyhow::Result<String> {
Ok("fe80:0000:0208:74ff:feda:625c".to_string()) Ok("fe80:0000:0208:74ff:feda:625c".to_string())
} }
} }
@ -194,7 +195,8 @@ mod tests {
..Opts::default() ..Opts::default()
}; };
let conf = config::load_config(&opts).expect("Failed to load config"); let conf = config::load_config(&opts).expect("Failed to load config");
run::<IPSourceMock>(server.base_url().as_str(), &conf) let ip_source: Box<dyn IPSource> = Box::new(IPSourceMock);
run(server.base_url().as_str(), &ip_source, &conf)
.await .await
.expect("Failed when running the update"); .expect("Failed when running the update");