simplify message handler types

This commit is contained in:
Chris Beck
2025-12-05 03:54:06 -07:00
parent e29435907c
commit 79dd9de3d4
4 changed files with 112 additions and 41 deletions
+2 -2
View File
@@ -4,7 +4,7 @@
//! writing the message terminated with CRLF, and reading the response until CRLF.
use conf::Conf;
use signal_gateway::MessageHandlerResult;
use signal_gateway::{AdminMessageResponse, MessageHandlerResult};
use std::time::Duration;
use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
@@ -74,5 +74,5 @@ async fn handle_message(config: &AdminNetcatConfig, message: String) -> MessageH
.trim_end_matches(['\r', '\n'])
.to_owned();
Ok((text, vec![]))
Ok(AdminMessageResponse::new(text))
}
+19 -38
View File
@@ -2,6 +2,7 @@ use crate::{
alertmanager::AlertPost,
jsonrpc::{Envelope, RpcClient, RpcClientError, SignalMessage, connect_tcp},
log_message::{LogMessage, Origin},
message_handler::{AdminMessageResponse, MessageHandler, MessageHandlerResult},
prometheus::{Prometheus, PrometheusConfig},
};
use chrono::Utc;
@@ -11,10 +12,7 @@ use http::{Method, Request, Response, StatusCode};
use http_body::Body;
use http_body_util::BodyExt;
use prometheus_http_client::{AlertStatus, ExtractLabels};
use std::{
collections::HashMap, error::Error, fmt::Write, future::Future, net::SocketAddr,
path::PathBuf, pin::Pin,
};
use std::{collections::HashMap, fmt::Write, net::SocketAddr, path::PathBuf};
use tokio::{
join,
sync::{
@@ -27,15 +25,6 @@ use tokio_util::bytes::Buf;
use tokio_util::sync::CancellationToken;
use tracing::{debug, error, info, warn};
/// Response from a message handler: either success with text and optional attachments,
/// or an error with status code and message.
pub type MessageHandlerResult = Result<(String, Vec<PathBuf>), (u16, Box<dyn Error + Send + Sync>)>;
/// Handler function for admin messages that don't start with `/`.
/// Takes the message text and returns a response.
pub type MessageHandler =
Box<dyn Fn(String) -> Pin<Box<dyn Future<Output = MessageHandlerResult> + Send>> + Send + Sync>;
mod circular_buffer;
mod log_handler;
use log_handler::{LogHandler, LogHandlerConfig};
@@ -291,20 +280,18 @@ impl Gateway {
})
);
let (message, attachments) = resp.unwrap_or_else(
|(code, msg)| {
let text = format!("{code}: {msg}");
error!("Message handler error: {text}");
(text, vec![])
}
);
let resp = resp.unwrap_or_else(|(code, msg)| {
let text = format!("{code}: {msg}");
error!("Message handler error: {text}");
AdminMessageResponse::new(text)
});
let attachments = attachments.into_iter().map(|p| p.to_str().expect("attachments must have utf8 paths").to_owned()).collect();
let attachments = resp.attachments.into_iter().map(|p| p.to_str().expect("attachments must have utf8 paths").to_owned()).collect();
SignalMessage {
sender: self.config.signal_account.clone(),
recipient: vec![msg.envelope.source_uuid.clone()],
message,
message: resp.text,
attachments,
}.send(signal_cli).await?;
}
@@ -316,10 +303,7 @@ impl Gateway {
// Returns Err in case of a timeout or handler error
// Returns Ok when success or error text is generated
async fn handle_signal_admin_message(
&self,
msg: &Envelope,
) -> Result<(String, Vec<PathBuf>), (u16, Box<dyn Error + Send + Sync>)> {
async fn handle_signal_admin_message(&self, msg: &Envelope) -> MessageHandlerResult {
let data = msg.data_message.as_ref().unwrap();
// Admin messages starting with / are handled by gateway
@@ -398,15 +382,12 @@ impl Gateway {
}
}
async fn handle_gateway_command(
&self,
cmd: GatewayCommand,
) -> Result<(String, Vec<PathBuf>), (u16, Box<dyn Error + Send + Sync>)> {
async fn handle_gateway_command(&self, cmd: GatewayCommand) -> MessageHandlerResult {
match cmd {
GatewayCommand::Log { filter } => {
let handlers = self.log_handlers.read().await;
if handlers.is_empty() {
return Ok(("No log sources registered yet".to_string(), vec![]));
return Ok(AdminMessageResponse::new("No log sources registered yet"));
}
let mut text = String::new();
for (origin, handler) in handlers.iter() {
@@ -421,9 +402,9 @@ impl Gateway {
text.push('\n');
}
if text.is_empty() {
return Ok(("No matching log sources".to_string(), vec![]));
return Ok(AdminMessageResponse::new("No matching log sources"));
}
Ok((text, vec![]))
Ok(AdminMessageResponse::new(text))
}
GatewayCommand::Query { query } => {
let prometheus = self
@@ -455,7 +436,7 @@ impl Gateway {
}
}
Ok((text, vec![]))
Ok(AdminMessageResponse::new(text))
}
Err(err) => Err((500, err)),
}
@@ -469,7 +450,7 @@ impl Gateway {
prometheus.purge_old_plots();
match prometheus.create_oneoff_plot(query.clone(), duration).await {
Ok(filename) => Ok((query, vec![filename])),
Ok(filename) => Ok(AdminMessageResponse::new(query).with_attachment(filename)),
Err(err) => Err((500, err)),
}
}
@@ -493,7 +474,7 @@ impl Gateway {
if text.is_empty() {
text = "no matches".into();
}
Ok((text, vec![]))
Ok(AdminMessageResponse::new(text))
}
Err(err) => Err((500, err)),
}
@@ -514,7 +495,7 @@ impl Gateway {
if text.is_empty() {
text = "no matches".into();
}
Ok((text, vec![]))
Ok(AdminMessageResponse::new(text))
}
Err(err) => Err((500, err)),
}
@@ -554,7 +535,7 @@ impl Gateway {
if text.is_empty() {
text = "no alerts".into();
}
Ok((text, vec![]))
Ok(AdminMessageResponse::new(text))
}
Err(err) => Err((500, err)),
}
+3 -1
View File
@@ -1,5 +1,6 @@
pub mod alertmanager;
pub mod gateway;
pub mod message_handler;
pub(crate) mod human_duration;
pub(crate) mod jsonrpc;
@@ -7,5 +8,6 @@ pub(crate) mod log_message;
pub(crate) mod prometheus;
pub(crate) mod transports;
pub use gateway::{Gateway, GatewayConfig, MessageHandler, MessageHandlerResult};
pub use gateway::{Gateway, GatewayConfig};
pub use log_message::{Level, LogMessage, LogMessageBuilder};
pub use message_handler::{AdminMessageResponse, MessageHandler, MessageHandlerResult};
+88
View File
@@ -0,0 +1,88 @@
//! Message handler types for admin messages not handled by the gateway.
use std::{
error::Error,
future::Future,
path::PathBuf,
pin::Pin,
};
/// Response to an admin message.
#[non_exhaustive]
#[derive(Clone, Debug, Default)]
pub struct AdminMessageResponse {
/// The text response to send back to the admin.
pub text: String,
/// Optional file attachments to include with the response.
pub attachments: Vec<PathBuf>,
}
impl AdminMessageResponse {
/// Create a new response with the given text.
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
attachments: Vec::new(),
}
}
/// Create a builder for constructing a response.
pub fn builder() -> AdminMessageResponseBuilder {
AdminMessageResponseBuilder::default()
}
/// Add an attachment to the response.
pub fn with_attachment(mut self, path: impl Into<PathBuf>) -> Self {
self.attachments.push(path.into());
self
}
/// Add multiple attachments to the response.
pub fn with_attachments(mut self, paths: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
self.attachments.extend(paths.into_iter().map(Into::into));
self
}
}
/// Builder for constructing an [`AdminMessageResponse`].
#[derive(Clone, Debug, Default)]
pub struct AdminMessageResponseBuilder {
text: String,
attachments: Vec<PathBuf>,
}
impl AdminMessageResponseBuilder {
/// Set the text response.
pub fn text(mut self, text: impl Into<String>) -> Self {
self.text = text.into();
self
}
/// Add an attachment.
pub fn attachment(mut self, path: impl Into<PathBuf>) -> Self {
self.attachments.push(path.into());
self
}
/// Add multiple attachments.
pub fn attachments(mut self, paths: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
self.attachments.extend(paths.into_iter().map(Into::into));
self
}
/// Build the response.
pub fn build(self) -> AdminMessageResponse {
AdminMessageResponse {
text: self.text,
attachments: self.attachments,
}
}
}
/// Result type for message handler responses.
pub type MessageHandlerResult = Result<AdminMessageResponse, (u16, Box<dyn Error + Send + Sync>)>;
/// Handler function for admin messages that don't start with `/`.
/// Takes the message text and returns a response.
pub type MessageHandler =
Box<dyn Fn(String) -> Pin<Box<dyn Future<Output = MessageHandlerResult> + Send>> + Send + Sync>;