use std::collections::HashMap;
use anyhow::Result;
use reqwest::header::{HeaderMap, HeaderValue};
use serde::Deserialize;
use crate::replace_placehoders;
#[derive(Debug, serde::Deserialize, Clone)]
pub struct Url {
#[serde(default)]
method: HttpMethod,
url: String,
#[serde(default)]
#[serde(deserialize_with = "deserialize_headermap")]
headers: HeaderMap<HeaderValue>,
#[serde(default)]
body: Option<String>,
#[serde(skip)]
updated: bool,
}
pub fn deserialize_headermap<'a, D>(de: D) -> Result<HeaderMap<HeaderValue>, D::Error>
where
D: serde::Deserializer<'a>,
{
let headers = HashMap::<String, String>::deserialize(de)?;
let headermap = HeaderMap::<HeaderValue>::try_from(&headers);
match headermap {
Ok(m) => Ok(m),
Err(err) => Err(serde::de::Error::custom(format!("{err}"))),
}
}
impl Url {
pub async fn update(
&mut self,
next_ipv4: &str,
next_ipv6: &str,
ip_changed: bool,
client: &reqwest::Client,
) -> Result<()> {
let res = self
.update_inner(next_ipv4, next_ipv6, ip_changed, client)
.await;
self.updated = res.is_ok();
res
}
#[inline]
async fn update_inner(
&mut self,
next_ipv4: &str,
next_ipv6: &str,
ip_changed: bool,
client: &reqwest::Client,
) -> Result<()> {
if !ip_changed && self.updated {
return Ok(());
}
let url = replace_placehoders(&self.url, next_ipv4, next_ipv6);
let mut builder = client.request(self.method.into(), url);
if !self.headers.is_empty() {
let mut headers = self.headers.clone();
for v in headers.values_mut() {
let s = unsafe { std::str::from_utf8_unchecked(v.as_bytes()) };
let s = replace_placehoders(s, next_ipv4, next_ipv6);
*v = HeaderValue::from_str(&s).expect("always only visible ASCII");
}
builder = builder.headers(headers);
}
if let Some(body) = &self.body {
let body = replace_placehoders(body, next_ipv4, next_ipv6);
builder = builder.body(body);
}
let resp = client.execute(builder.build()?).await?;
log::debug!("server responded with {resp:?}");
resp.error_for_status()?;
Ok(())
}
pub fn name(&self) -> String {
if let Ok(url) = reqwest::Url::parse(&replace_placehoders(&self.url, "", "")) {
url.host_str().unwrap_or("").to_owned()
} else {
"invalid url".into()
}
}
}
#[derive(Debug, serde::Deserialize, PartialEq, Eq, Clone, Copy, Default)]
#[serde(rename_all = "lowercase")]
pub enum HttpMethod {
#[default]
Get,
Post,
Put,
Delete,
Head,
Trace,
Connect,
Patch,
Options,
}
impl From<HttpMethod> for reqwest::Method {
fn from(value: HttpMethod) -> Self {
match value {
HttpMethod::Get => reqwest::Method::GET,
HttpMethod::Post => reqwest::Method::POST,
HttpMethod::Put => reqwest::Method::PUT,
HttpMethod::Delete => reqwest::Method::DELETE,
HttpMethod::Head => reqwest::Method::HEAD,
HttpMethod::Trace => reqwest::Method::TRACE,
HttpMethod::Connect => reqwest::Method::CONNECT,
HttpMethod::Patch => reqwest::Method::PATCH,
HttpMethod::Options => reqwest::Method::OPTIONS,
}
}
}