refactor admin_signal_uuids container
This commit is contained in:
@@ -244,7 +244,7 @@ limits = [
|
||||
Duration::from_secs(10)
|
||||
);
|
||||
assert_eq!(config.gateway.admin_signal_uuids.len(), 2);
|
||||
assert!(config.gateway.admin_signal_uuids.contains_key("abc-123-uuid"));
|
||||
assert!(config.gateway.admin_signal_uuids.contains("abc-123-uuid"));
|
||||
|
||||
let syslog = config.syslog.expect("syslog should be present");
|
||||
assert_eq!(syslog.listen_addr, "0.0.0.0:1514".parse().unwrap());
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
//! Admin Signal UUIDs container with flexible deserialization.
|
||||
//!
|
||||
//! Supports two formats:
|
||||
//! - Map: `{"uuid1": ["safety1", "safety2"], "uuid2": []}`
|
||||
//! - Sequence: `["uuid1", "uuid2"]` (treated as UUIDs with no safety numbers)
|
||||
|
||||
use serde::de::{MapAccess, SeqAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
/// Container for admin Signal UUIDs mapped to their optional safety numbers.
|
||||
///
|
||||
/// Can be deserialized from either:
|
||||
/// - A map of UUID -> safety numbers: `{"uuid1": ["12345..."], "uuid2": []}`
|
||||
/// - A sequence of UUIDs (no safety numbers): `["uuid1", "uuid2"]`
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AdminSignalUuids {
|
||||
map: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
impl AdminSignalUuids {
|
||||
/// Create an empty container.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Check if a UUID is a registered admin.
|
||||
pub fn contains(&self, uuid: &str) -> bool {
|
||||
self.map.contains_key(uuid)
|
||||
}
|
||||
|
||||
/// Get all admin UUIDs.
|
||||
pub fn uuids(&self) -> impl Iterator<Item = &String> {
|
||||
self.map.keys()
|
||||
}
|
||||
|
||||
/// Get the number of admin UUIDs.
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
/// Check if empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.map.is_empty()
|
||||
}
|
||||
|
||||
/// Iterate over UUID and safety number pairs.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&String, &Vec<String>)> {
|
||||
self.map.iter()
|
||||
}
|
||||
|
||||
/// Get safety numbers for a specific UUID.
|
||||
pub fn get(&self, uuid: &str) -> Option<&Vec<String>> {
|
||||
self.map.get(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AdminSignalUuids {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(AdminSignalUuidsVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct AdminSignalUuidsVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for AdminSignalUuidsVisitor {
|
||||
type Value = AdminSignalUuids;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a map of UUIDs to safety numbers, or a sequence of UUIDs")
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(0));
|
||||
while let Some((key, value)) = access.next_entry::<String, Vec<String>>()? {
|
||||
map.insert(key, value);
|
||||
}
|
||||
Ok(AdminSignalUuids { map })
|
||||
}
|
||||
|
||||
fn visit_seq<S>(self, mut access: S) -> Result<Self::Value, S::Error>
|
||||
where
|
||||
S: SeqAccess<'de>,
|
||||
{
|
||||
let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(0));
|
||||
while let Some(uuid) = access.next_element::<String>()? {
|
||||
map.insert(uuid, Vec::new());
|
||||
}
|
||||
Ok(AdminSignalUuids { map })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<String> for AdminSignalUuids {
|
||||
fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self {
|
||||
Self {
|
||||
map: iter.into_iter().map(|uuid| (uuid, Vec::new())).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, Vec<String>)> for AdminSignalUuids {
|
||||
fn from_iter<I: IntoIterator<Item = (String, Vec<String>)>>(iter: I) -> Self {
|
||||
Self {
|
||||
map: iter.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a AdminSignalUuids {
|
||||
type Item = (&'a String, &'a Vec<String>);
|
||||
type IntoIter = std::collections::hash_map::Iter<'a, String, Vec<String>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.map.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_map() {
|
||||
let json = r#"{"uuid1": ["safety1", "safety2"], "uuid2": []}"#;
|
||||
let uuids: AdminSignalUuids = serde_json::from_str(json).unwrap();
|
||||
|
||||
assert_eq!(uuids.len(), 2);
|
||||
assert!(uuids.contains("uuid1"));
|
||||
assert!(uuids.contains("uuid2"));
|
||||
assert_eq!(uuids.get("uuid1").unwrap(), &vec!["safety1", "safety2"]);
|
||||
assert_eq!(uuids.get("uuid2").unwrap(), &Vec::<String>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_seq() {
|
||||
let json = r#"["uuid1", "uuid2", "uuid3"]"#;
|
||||
let uuids: AdminSignalUuids = serde_json::from_str(json).unwrap();
|
||||
|
||||
assert_eq!(uuids.len(), 3);
|
||||
assert!(uuids.contains("uuid1"));
|
||||
assert!(uuids.contains("uuid2"));
|
||||
assert!(uuids.contains("uuid3"));
|
||||
// All should have empty safety numbers
|
||||
assert!(uuids.get("uuid1").unwrap().is_empty());
|
||||
assert!(uuids.get("uuid2").unwrap().is_empty());
|
||||
assert!(uuids.get("uuid3").unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_map() {
|
||||
let json = r#"{}"#;
|
||||
let uuids: AdminSignalUuids = serde_json::from_str(json).unwrap();
|
||||
assert!(uuids.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_seq() {
|
||||
let json = r#"[]"#;
|
||||
let uuids: AdminSignalUuids = serde_json::from_str(json).unwrap();
|
||||
assert!(uuids.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -20,14 +20,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,
|
||||
fmt::Write,
|
||||
net::SocketAddr,
|
||||
path::PathBuf,
|
||||
sync::Mutex,
|
||||
time::Duration,
|
||||
};
|
||||
use std::{fmt::Write, net::SocketAddr, path::PathBuf, sync::Mutex, time::Duration};
|
||||
use tokio::{
|
||||
join,
|
||||
sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel},
|
||||
@@ -35,6 +28,9 @@ use tokio::{
|
||||
use tokio_util::{bytes::Buf, sync::CancellationToken};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
mod admin_uuids;
|
||||
pub use admin_uuids::AdminSignalUuids;
|
||||
|
||||
mod log_buffer;
|
||||
mod log_handler;
|
||||
use log_handler::{LogHandler, LogHandlerConfig};
|
||||
@@ -67,9 +63,9 @@ pub struct GatewayConfig {
|
||||
#[conf(long, env, default_value = "5s", value_parser = conf_extra::parse_duration, serde(use_value_parser))]
|
||||
pub signal_cli_retry_delay: Duration,
|
||||
/// Admin UUIDs mapped to their safety numbers (can be empty).
|
||||
/// Example: `{"uuid1": ["12345...", "67890..."], "uuid2": []}`
|
||||
/// Accepts either a map `{"uuid1": ["12345..."], "uuid2": []}` or a list `["uuid1", "uuid2"]`.
|
||||
#[conf(long, env, value_parser = serde_json::from_str)]
|
||||
pub admin_signal_uuids: HashMap<String, Vec<String>>,
|
||||
pub admin_signal_uuids: AdminSignalUuids,
|
||||
/// If set, alerts are sent to this group instead of individual admins.
|
||||
#[conf(long, env)]
|
||||
pub alert_group_id: Option<String>,
|
||||
@@ -84,12 +80,12 @@ pub struct GatewayConfig {
|
||||
impl GatewayConfig {
|
||||
/// Check if a UUID is a registered admin
|
||||
pub fn is_admin(&self, uuid: &str) -> bool {
|
||||
self.admin_signal_uuids.contains_key(uuid)
|
||||
self.admin_signal_uuids.contains(uuid)
|
||||
}
|
||||
|
||||
/// Get all admin UUIDs
|
||||
pub fn admin_uuids(&self) -> Vec<String> {
|
||||
self.admin_signal_uuids.keys().cloned().collect()
|
||||
self.admin_signal_uuids.uuids().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user