okbot_core/webhook.rs
1//! Platform-neutral HTTP types for channels that receive events by webhook.
2//!
3//! The runtime owns the HTTP server and mounts each channel's [`WebhookHandler`] at its path, so
4//! channel crates do not depend on a web framework.
5
6use crate::async_trait;
7
8/// An HTTP request delivered to a webhook.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct WebhookRequest {
11 /// Upper-case method (`POST`).
12 pub method: String,
13 /// Path relative to the handler's mount point (usually empty).
14 pub path: String,
15 /// Headers with lower-case names.
16 pub headers: Vec<(String, String)>,
17 /// Raw body.
18 pub body: Vec<u8>,
19}
20
21impl WebhookRequest {
22 /// The first value of a header; `name` is matched case-insensitively.
23 pub fn header(&self, name: &str) -> Option<&str> {
24 self.headers
25 .iter()
26 .find(|(k, _)| k.eq_ignore_ascii_case(name))
27 .map(|(_, v)| v.as_str())
28 }
29}
30
31/// The response to a webhook request.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct WebhookResponse {
34 /// HTTP status.
35 pub status: u16,
36 /// `Content-Type` of the body.
37 pub content_type: &'static str,
38 /// Body.
39 pub body: Vec<u8>,
40}
41
42impl WebhookResponse {
43 /// `200` with a JSON body.
44 pub fn json(body: &serde_json::Value) -> Self {
45 Self {
46 status: 200,
47 content_type: "application/json",
48 body: body.to_string().into_bytes(),
49 }
50 }
51
52 /// An empty response with `status`.
53 pub fn status(status: u16) -> Self {
54 Self {
55 status,
56 content_type: "text/plain",
57 body: Vec::new(),
58 }
59 }
60}
61
62/// Receives webhook requests for one channel.
63///
64/// Implementations authenticate the request, hand events to the runtime and return quickly:
65/// platforms time out slow webhooks, and agent turns are slow.
66#[async_trait]
67pub trait WebhookHandler: Send + Sync {
68 /// Mount path, without the bot prefix (for example `zalo/shop`).
69 fn path(&self) -> String;
70
71 /// Handles one request.
72 async fn handle(&self, request: WebhookRequest) -> WebhookResponse;
73}