use reqwest::Client;
use url::Url;
use super::Transform;
use crate::{
actions::transforms::{
field::Field,
result::{TransformResult, TransformedEntry, TransformedMessage},
},
entry::Entry,
error::InvalidUrlError,
sources::{self, http::HttpError as SourceHttpError, http::Request},
utils::OptionExt,
};
#[derive(Debug)]
pub struct Http {
pub from_field: Field,
client: Client,
}
#[expect(missing_docs, reason = "error message is self-documenting")]
#[derive(thiserror::Error, Debug)]
pub enum HttpError {
#[error("Missing URL in the entry {0} field")]
MissingUrl(Field),
#[error("Invalid URL in the entry {0} field")]
InvalidUrl(Field, #[source] InvalidUrlError),
#[error(transparent)]
Other(#[from] crate::sources::http::HttpError),
}
impl Http {
pub fn new(from_field: Field) -> Result<Self, SourceHttpError> {
let client = sources::http::CLIENT
.get_or_try_init(|| {
reqwest::ClientBuilder::new()
.timeout(std::time::Duration::from_secs(30))
.build()
.map_err(SourceHttpError::TlsInitFailed)
})?
.clone();
Ok(Self { from_field, client })
}
}
impl Transform for Http {
type Err = HttpError;
async fn transform_entry(&mut self, entry: Entry) -> Result<Vec<TransformedEntry>, Self::Err> {
let url: Option<Url> = match self.from_field {
Field::Title => entry.msg.title.as_deref().try_map(|s| {
Url::try_from(s).map_err(|e| {
HttpError::InvalidUrl(self.from_field, InvalidUrlError(e, s.to_owned()))
})
})?,
Field::Body => entry.msg.body.as_deref().try_map(|s| {
Url::try_from(s).map_err(|e| {
HttpError::InvalidUrl(self.from_field, InvalidUrlError(e, s.to_owned()))
})
})?,
Field::Link => entry.msg.link.as_deref().try_map(|s| {
Url::try_from(s).map_err(|e| {
HttpError::InvalidUrl(self.from_field, InvalidUrlError(e, s.to_owned()))
})
})?,
Field::Id => entry.id.as_ref().try_map(|id| {
Url::try_from(id.0.as_str()).map_err(|e| {
HttpError::InvalidUrl(
self.from_field,
InvalidUrlError(e, id.0.as_string().clone()),
)
})
})?,
Field::ReplyTo => entry.reply_to.as_ref().try_map(|id| {
Url::try_from(id.0.as_str()).map_err(|e| {
HttpError::InvalidUrl(
self.from_field,
InvalidUrlError(e, id.0.as_string().clone()),
)
})
})?,
Field::RawContents => entry.raw_contents.as_deref().try_map(|s| {
Url::try_from(s).map_err(|e| {
HttpError::InvalidUrl(self.from_field, InvalidUrlError(e, s.to_owned()))
})
})?,
};
let url = url.ok_or_else(|| HttpError::MissingUrl(self.from_field))?;
let new_page = sources::http::send_request(&self.client, &Request::Get, &url).await?;
Ok(vec![TransformedEntry {
raw_contents: TransformResult::New(new_page),
msg: TransformedMessage {
link: TransformResult::New(url.as_str().to_owned()),
..Default::default()
},
..Default::default()
}])
}
}