cortexfs_channels/
platform.rs1use std::collections::BTreeMap;
2
3use serde_json::Value;
4
5use crate::{ChannelError, ChannelId, InboundMessage, OutboundMessage, Participant};
6
7pub mod discord;
8pub mod feishu;
9pub mod slack;
10pub mod telegram;
11
12#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct OutboundRequest {
15 pub method: String,
16 pub path: String,
17 pub content_type: String,
18 pub body: String,
19 pub headers: BTreeMap<String, String>,
20}
21
22pub trait ChannelCodec: Send + Sync {
24 fn channel(&self) -> ChannelId;
25 fn decode(&self, payload: &str) -> Result<Option<InboundMessage>, ChannelError>;
26 fn encode(&self, message: &OutboundMessage) -> Result<OutboundRequest, ChannelError>;
27 fn challenge(&self, _payload: &str) -> Option<String> {
28 None
29 }
30}
31
32pub(crate) fn object(payload: &str) -> Result<Value, ChannelError> {
33 let value: Value = serde_json::from_str(payload)
34 .map_err(|error| ChannelError::Protocol(format!("invalid webhook JSON: {error}")))?;
35 if value.is_object() {
36 Ok(value)
37 } else {
38 Err(ChannelError::Protocol(
39 "webhook payload is not an object".to_owned(),
40 ))
41 }
42}
43
44pub(crate) fn string(value: Option<&Value>, field: &str) -> Result<String, ChannelError> {
45 value
46 .and_then(Value::as_str)
47 .filter(|text| !text.is_empty())
48 .map(str::to_owned)
49 .ok_or_else(|| ChannelError::Protocol(format!("webhook field `{field}` is missing")))
50}
51
52pub(crate) fn scalar(value: Option<&Value>, field: &str) -> Result<String, ChannelError> {
53 if let Some(value) = value
54 .and_then(Value::as_str)
55 .filter(|value| !value.is_empty())
56 {
57 return Ok(value.to_owned());
58 }
59 value
60 .and_then(Value::as_number)
61 .map(ToString::to_string)
62 .ok_or_else(|| ChannelError::Protocol(format!("webhook field `{field}` is missing")))
63}
64
65pub(crate) fn participant(value: Option<&Value>, id: String) -> Participant {
66 Participant {
67 id,
68 display_name: value
69 .and_then(|item| item.get("display_name").or_else(|| item.get("username")))
70 .and_then(Value::as_str)
71 .map(str::to_owned),
72 handle: value
73 .and_then(|item| item.get("username").or_else(|| item.get("handle")))
74 .and_then(Value::as_str)
75 .map(str::to_owned),
76 }
77}
78
79pub(crate) fn timestamp_ms(value: Option<&Value>) -> Option<u64> {
80 value
81 .and_then(|item| item.as_u64().or_else(|| item.as_str()?.parse().ok()))
82 .map(|seconds| seconds.saturating_mul(1_000))
83}
84
85pub(crate) fn text(value: Option<&Value>) -> Result<crate::MessageBody, ChannelError> {
86 crate::MessageBody::text(string(value, "text")?)
87}