use http::header::{CONTENT_TYPE, LINK};
use http::StatusCode;
use time::OffsetDateTime;
use url::Url;
use crate::standards::websub::{
ContentDistribution, DeliveryResult, DeliveryStatus, Result, WebSubError,
};
#[tracing::instrument(skip(client, content))]
pub async fn distribute_to_callback(
client: &impl crate::http::Client,
hub: &Url,
topic: &Url,
callback: &Url,
content: &ContentDistribution,
) -> Result<DeliveryResult<Url>> {
let link_header_value = format!("<{}>; rel=\"hub\", <{}>; rel=\"self\"", hub, topic);
let mut builder = http::Request::builder()
.method(http::Method::POST)
.uri(callback.as_str())
.header(CONTENT_TYPE, content.content_type.as_str())
.header(LINK, link_header_value);
if let Some(signature) = &content.signature {
builder = builder.header("X-Hub-Signature", signature.as_str());
}
let request = builder
.body(crate::http::Body::Bytes(content.body.as_bytes().to_vec()))
.map_err(|err| WebSubError::Http(err.to_string()))?;
let response = client.send_request(request).await.map_err(|err| WebSubError::Network(Box::new(err)))?;
let status = response.status();
let response_code = Some(status.as_u16());
let (delivery_status, error_message) = map_status(status);
let delivered_at = if delivery_status == DeliveryStatus::Success {
Some(OffsetDateTime::now_utc())
} else {
None
};
Ok(DeliveryResult {
subscriber_id: callback.clone(),
subscriber_callback: callback.clone(),
status: delivery_status,
response_code,
error_message,
retry_count: 0,
delivered_at,
})
}
pub(crate) fn map_status(status: StatusCode) -> (DeliveryStatus, Option<String>) {
match status {
StatusCode::OK | StatusCode::ACCEPTED | StatusCode::NO_CONTENT => {
(DeliveryStatus::Success, None)
}
StatusCode::GONE => (DeliveryStatus::SubscriptionDeleted, None),
StatusCode::NOT_FOUND | StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => (
DeliveryStatus::PermanentFailure,
Some(format!("Subscriber rejected delivery: {}", status)),
),
StatusCode::TOO_MANY_REQUESTS
| StatusCode::BAD_GATEWAY
| StatusCode::SERVICE_UNAVAILABLE
| StatusCode::GATEWAY_TIMEOUT
| StatusCode::REQUEST_TIMEOUT
| StatusCode::INTERNAL_SERVER_ERROR => (
DeliveryStatus::TemporaryFailure,
Some(format!("Temporary failure delivering content: {}", status)),
),
_ => (
DeliveryStatus::TemporaryFailure,
Some(format!("Unexpected response from subscriber: {}", status)),
),
}
}