indieweb 0.10.0

A collection of utilities for working with the IndieWeb.
Documentation
use http::header::{ACCEPT, CONTENT_TYPE};
use http::StatusCode;
use url::Url;

use crate::http::CONTENT_TYPE_FORM_URLENCODED;
use crate::standards::websub::{Result, SubscribeRequest, SubscriptionMode, WebSubError};

#[derive(Debug, Clone)]
pub struct SubscribeResponse {
    pub status: StatusCode,
}

#[tracing::instrument(skip(client))]
pub async fn subscribe(
    client: &impl crate::http::Client,
    hub: &Url,
    request: &SubscribeRequest,
) -> Result<SubscribeResponse> {
    send_subscription_request(client, hub, request, SubscriptionMode::Subscribe).await
}

#[tracing::instrument(skip(client))]
pub async fn unsubscribe(
    client: &impl crate::http::Client,
    hub: &Url,
    request: &SubscribeRequest,
) -> Result<SubscribeResponse> {
    send_subscription_request(client, hub, request, SubscriptionMode::Unsubscribe).await
}

#[tracing::instrument(skip(client))]
pub async fn send_subscription_request(
    client: &impl crate::http::Client,
    hub: &Url,
    request: &SubscribeRequest,
    mode: SubscriptionMode,
) -> Result<SubscribeResponse> {
    let mut pairs = vec![
        ("hub.mode".to_string(), match mode { SubscriptionMode::Subscribe => "subscribe", SubscriptionMode::Unsubscribe => "unsubscribe" }.to_string()),
        ("hub.topic".to_string(), request.topic.as_str().to_string()),
        ("hub.callback".to_string(), request.callback.as_str().to_string()),
    ];

    if let Some(lease_seconds) = request.lease_seconds {
        pairs.push(("hub.lease_seconds".to_string(), lease_seconds.to_string()));
    }

    if let Some(secret) = request.secret.as_ref() {
        pairs.push(("hub.secret".to_string(), secret.to_string()));
    }

    let body = url::form_urlencoded::Serializer::new(String::new())
        .extend_pairs(pairs)
        .finish();

    let request = http::Request::builder()
        .method(http::Method::POST)
        .uri(hub.as_str())
        .header(ACCEPT, "*/*")
        .header(CONTENT_TYPE, CONTENT_TYPE_FORM_URLENCODED)
        .body(crate::http::Body::Bytes(body.into_bytes()))
        .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();

    if status == http::StatusCode::ACCEPTED || status == http::StatusCode::NO_CONTENT || status == http::StatusCode::OK {
        Ok(SubscribeResponse { status })
    } else {
        Err(WebSubError::Http(format!("Hub rejected request: {}", status)))
    }
}