use std::collections::HashMap;
#[derive(Debug, Default, Clone)]
pub struct PartnerDirectory {
endpoints: HashMap<Box<str>, Box<str>>,
}
#[derive(Debug)]
pub struct PartnerDirectoryParseError(pub String);
impl std::fmt::Display for PartnerDirectoryParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid AS4 partner entry: {}", self.0)
}
}
impl std::error::Error for PartnerDirectoryParseError {}
impl PartnerDirectory {
pub fn from_cli_pairs(pairs: &[String]) -> Result<Self, PartnerDirectoryParseError> {
let mut endpoints = HashMap::new();
for pair in pairs {
let (mp_id, url) = pair.split_once('=').ok_or_else(|| {
PartnerDirectoryParseError(format!("{pair:?} — expected format <GLN>=<HTTPS-URL>"))
})?;
let mp_id = mp_id.trim();
let url = url.trim();
if mp_id.is_empty() {
return Err(PartnerDirectoryParseError(format!(
"{pair:?} — GLN must not be empty"
)));
}
if !url.starts_with("https://") {
return Err(PartnerDirectoryParseError(format!(
"{pair:?} — endpoint URL must use HTTPS (got {url:?})"
)));
}
endpoints.insert(mp_id.into(), url.into());
}
Ok(Self { endpoints })
}
pub fn endpoint(&self, mp_id: &str) -> Option<&str> {
self.endpoints.get(mp_id).map(|s| s.as_ref())
}
pub fn is_empty(&self) -> bool {
self.endpoints.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.endpoints.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
}
pub fn len(&self) -> usize {
self.endpoints.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_single_pair() {
let pairs = vec!["9900000000002=https://partner.example/as4/inbox".to_string()];
let dir = PartnerDirectory::from_cli_pairs(&pairs).unwrap();
assert_eq!(
dir.endpoint("9900000000002"),
Some("https://partner.example/as4/inbox")
);
assert_eq!(dir.len(), 1);
assert!(!dir.is_empty());
}
#[test]
fn whitespace_trimmed() {
let pairs = vec![" 9900000000002 = https://partner.example/as4 ".to_string()];
let dir = PartnerDirectory::from_cli_pairs(&pairs).unwrap();
assert_eq!(
dir.endpoint("9900000000002"),
Some("https://partner.example/as4")
);
}
#[test]
fn missing_equals_is_error() {
let pairs = vec!["9900000000002_https://partner.example/as4".to_string()];
assert!(PartnerDirectory::from_cli_pairs(&pairs).is_err());
}
#[test]
fn empty_gln_is_error() {
let pairs = vec!["=https://partner.example/as4".to_string()];
assert!(PartnerDirectory::from_cli_pairs(&pairs).is_err());
}
#[test]
fn non_https_url_is_error() {
let pairs = vec!["9900000000002=http://partner.example/as4".to_string()];
assert!(PartnerDirectory::from_cli_pairs(&pairs).is_err());
}
#[test]
fn empty_input_gives_empty_directory() {
let dir = PartnerDirectory::from_cli_pairs(&[]).unwrap();
assert!(dir.is_empty());
assert_eq!(dir.len(), 0);
}
}