118 lines
3.5 KiB
Rust
118 lines
3.5 KiB
Rust
|
|
//! WhatsApp stub implementation of [`ChatTransport`].
|
||
|
|
//!
|
||
|
|
//! This is a placeholder transport that logs operations and returns stub
|
||
|
|
//! values. It exists to prove the transport abstraction works with a
|
||
|
|
//! second platform and will be replaced with a real WhatsApp Business API
|
||
|
|
//! integration in the future.
|
||
|
|
|
||
|
|
use async_trait::async_trait;
|
||
|
|
|
||
|
|
use crate::slog;
|
||
|
|
use crate::transport::{ChatTransport, MessageId};
|
||
|
|
|
||
|
|
/// Stub WhatsApp transport.
|
||
|
|
///
|
||
|
|
/// All methods log the operation and return success with placeholder values.
|
||
|
|
/// Message editing is not supported by WhatsApp — `edit_message` sends a
|
||
|
|
/// new message instead (TODO: implement via WhatsApp Business API).
|
||
|
|
pub struct WhatsAppTransport {
|
||
|
|
/// Counter for generating unique stub message IDs.
|
||
|
|
next_id: std::sync::atomic::AtomicU64,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl WhatsAppTransport {
|
||
|
|
pub fn new() -> Self {
|
||
|
|
Self {
|
||
|
|
next_id: std::sync::atomic::AtomicU64::new(1),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn next_message_id(&self) -> String {
|
||
|
|
let id = self
|
||
|
|
.next_id
|
||
|
|
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||
|
|
format!("whatsapp-stub-{id}")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Default for WhatsAppTransport {
|
||
|
|
fn default() -> Self {
|
||
|
|
Self::new()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[async_trait]
|
||
|
|
impl ChatTransport for WhatsAppTransport {
|
||
|
|
async fn send_message(
|
||
|
|
&self,
|
||
|
|
room_id: &str,
|
||
|
|
plain: &str,
|
||
|
|
_html: &str,
|
||
|
|
) -> Result<MessageId, String> {
|
||
|
|
// TODO: Send via WhatsApp Business API
|
||
|
|
let msg_id = self.next_message_id();
|
||
|
|
slog!(
|
||
|
|
"[whatsapp-stub] send_message to {room_id}: {plain:.80} (id={msg_id})"
|
||
|
|
);
|
||
|
|
Ok(msg_id)
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn edit_message(
|
||
|
|
&self,
|
||
|
|
room_id: &str,
|
||
|
|
original_message_id: &str,
|
||
|
|
plain: &str,
|
||
|
|
html: &str,
|
||
|
|
) -> Result<(), String> {
|
||
|
|
// WhatsApp does not support message editing.
|
||
|
|
// Send a new message instead.
|
||
|
|
slog!(
|
||
|
|
"[whatsapp-stub] edit_message (original={original_message_id}) — \
|
||
|
|
WhatsApp does not support edits, sending new message"
|
||
|
|
);
|
||
|
|
self.send_message(room_id, plain, html).await.map(|_| ())
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn send_typing(&self, room_id: &str, typing: bool) -> Result<(), String> {
|
||
|
|
// TODO: Send typing indicator via WhatsApp Business API
|
||
|
|
slog!("[whatsapp-stub] send_typing to {room_id}: typing={typing}");
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn send_message_returns_unique_ids() {
|
||
|
|
let transport = WhatsAppTransport::new();
|
||
|
|
let id1 = transport
|
||
|
|
.send_message("room1", "hello", "<p>hello</p>")
|
||
|
|
.await
|
||
|
|
.unwrap();
|
||
|
|
let id2 = transport
|
||
|
|
.send_message("room1", "world", "<p>world</p>")
|
||
|
|
.await
|
||
|
|
.unwrap();
|
||
|
|
assert_ne!(id1, id2, "each message should get a unique ID");
|
||
|
|
assert!(id1.starts_with("whatsapp-stub-"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn edit_message_succeeds() {
|
||
|
|
let transport = WhatsAppTransport::new();
|
||
|
|
let result = transport
|
||
|
|
.edit_message("room1", "msg-1", "updated", "<p>updated</p>")
|
||
|
|
.await;
|
||
|
|
assert!(result.is_ok(), "edit should succeed (sends new message)");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn send_typing_succeeds() {
|
||
|
|
let transport = WhatsAppTransport::new();
|
||
|
|
assert!(transport.send_typing("room1", true).await.is_ok());
|
||
|
|
assert!(transport.send_typing("room1", false).await.is_ok());
|
||
|
|
}
|
||
|
|
}
|