omnihook 0.1.2

Webhook client with payload builders for Slack, Discord, Telegram and generic webhooks with optional HMAC signing and idempotency key support.
Documentation
//! # Webhook Payload Builder
//!
//! Traits and implementations for constructing channel-specific JSON payloads
//! for Slack, Discord, Telegram, and generic webhooks.

pub mod discord;
pub mod generic;
pub mod slack;
pub mod telegram;

pub use discord::DiscordPayloadBuilder;
pub use generic::GenericWebhookPayloadBuilder;
pub use slack::SlackPayloadBuilder;
pub use telegram::TelegramPayloadBuilder;

/// A trait for building channel-specific webhook payloads.
pub trait WebhookPayloadBuilder: Send + Sync {
    /// Builds a webhook payload from a title and rendered body string.
    fn build_payload(&self, title: &str, body: &str) -> serde_json::Value;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bold_titles_on_all_platforms() {
        // Telegram: Title is escaped (plain text), Body is Markdown -> HTML
        let tg = TelegramPayloadBuilder {
            chat_id: "123".into(),
            disable_web_preview: true,
        };
        let payload = tg.build_payload("**Bold Title**", "Body");
        // For Telegram, the title is escaped literal text
        assert!(payload["text"].as_str().unwrap().contains("**Bold Title**"));

        // Slack: Both are concatenated into mrkdwn
        let slack = SlackPayloadBuilder;
        let payload = slack.build_payload("*Bold Title*", "Body");
        assert!(
            payload["blocks"][0]["text"]["text"]
                .as_str()
                .unwrap()
                .contains("*Bold Title*\n\nBody")
        );

        // Discord: Both are concatenated into markdown
        let discord = DiscordPayloadBuilder;
        let payload = discord.build_payload("**Bold Title**", "Body");
        assert!(
            payload["content"]
                .as_str()
                .unwrap()
                .contains("**Bold Title**\n\nBody")
        );
    }
}