#![cfg(feature = "http-client")]
use std::pin::Pin;
use std::sync::Arc;
use serde_json::Value;
use super::BroadcastFn;
#[must_use]
pub fn webhook_callback(url: impl Into<String>) -> BroadcastFn {
let client = reqwest::Client::new();
webhook_callback_with_client(url, client)
}
#[must_use]
pub fn webhook_callback_with_client(
url: impl Into<String>,
client: reqwest::Client,
) -> BroadcastFn {
let url: Arc<str> = Arc::from(url.into());
Arc::new(move |value: Value| {
let url = Arc::clone(&url);
let client = client.clone();
let fut: Pin<Box<dyn std::future::Future<Output = Result<(), String>> + Send>> =
Box::pin(async move {
let payload = build_payload(value);
let resp = client
.post(url.as_ref())
.json(&payload)
.send()
.await
.map_err(|e| format!("slack POST failed: {e}"))?;
if resp.status().is_success() {
Ok(())
} else {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
Err(format!("slack returned {status}: {body}"))
}
});
fut
})
}
fn build_payload(value: Value) -> Value {
match value {
Value::String(s) => serde_json::json!({ "text": s }),
other => other,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn string_value_wraps_into_text_envelope() {
let v = build_payload(json!("hello world"));
assert_eq!(v, json!({ "text": "hello world" }));
}
#[test]
fn object_value_passes_through_unchanged() {
let blocks = json!({
"blocks": [
{ "type": "section", "text": { "type": "mrkdwn", "text": "*hi*" } }
]
});
assert_eq!(build_payload(blocks.clone()), blocks);
}
#[test]
fn array_value_passes_through_unchanged() {
let v = json!(["a", "b"]);
assert_eq!(build_payload(v.clone()), v);
}
#[tokio::test]
async fn callback_is_callable() {
let cb = webhook_callback("http://127.0.0.1:1");
drop(cb);
}
}