use std::{collections::BTreeMap, sync::Arc};
pub const PEERS_ENV: &str = "POLYCHROME_A2A_PEERS";
#[derive(Debug, Clone, Default)]
pub struct PeerConfig {
peers: Arc<BTreeMap<String, String>>,
}
impl PeerConfig {
#[must_use]
pub fn from_env() -> Self {
Self::parse(&std::env::var(PEERS_ENV).unwrap_or_default())
}
#[must_use]
pub fn parse(raw: &str) -> Self {
let peers = raw
.split_whitespace()
.filter_map(|entry| {
let (name, url) = entry.split_once('=')?;
(!name.is_empty() && !url.is_empty()).then(|| (name.to_owned(), url.to_owned()))
})
.collect();
Self {
peers: Arc::new(peers),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.peers.is_empty()
}
#[must_use]
pub fn names(&self) -> Vec<String> {
self.peers.keys().cloned().collect()
}
#[must_use]
pub fn base_url(&self, name: &str) -> Option<&str> {
self.peers.get(name).map(String::as_str)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs)]
use super::*;
#[test]
fn parse_multiple_peers() {
let cfg = PeerConfig::parse("finance=https://finance.example/ ops=https://ops.example/");
assert_eq!(cfg.names(), vec!["finance".to_owned(), "ops".to_owned()]);
assert_eq!(cfg.base_url("finance"), Some("https://finance.example/"));
assert_eq!(cfg.base_url("ops"), Some("https://ops.example/"));
assert!(!cfg.is_empty());
}
#[test]
fn parse_skips_malformed_entries() {
let cfg = PeerConfig::parse("bad-entry finance=https://finance.example/ =novalue nokey=");
assert_eq!(cfg.names(), vec!["finance".to_owned()]);
}
#[test]
fn empty_or_blank_env_yields_empty_config() {
assert!(PeerConfig::parse("").is_empty());
assert!(PeerConfig::parse(" ").is_empty());
assert!(PeerConfig::default().is_empty());
}
#[test]
fn unknown_name_resolves_to_none() {
let cfg = PeerConfig::parse("finance=https://finance.example/");
assert_eq!(cfg.base_url("ops"), None);
}
}