bestool-alertd 1.2.1

(Internal) BES tooling: Alert daemon
Documentation
use std::collections::HashMap;

use tracing::debug;

use crate::ExternalTarget;

/// Pick the fallback target for system-level (synthetic) events.
///
/// Rules:
/// - Empty map → `None`.
/// - Single target → use it.
/// - Otherwise, a target named `default` → use it.
/// - Otherwise, first alphabetical → use it.
///
/// The loader injects a synthesised canopy target under `"default"` when no
/// explicit `_targets.yml` configures one and canopy auth is available; this
/// function makes no canopy-specific decisions itself.
pub fn determine_default_target(
	external_targets: &HashMap<String, Vec<ExternalTarget>>,
) -> Option<&ExternalTarget> {
	if external_targets.is_empty() {
		return None;
	}

	// If there's only one target, use it
	if external_targets.len() == 1 {
		let (id, targets) = external_targets.iter().next().unwrap();
		debug!(id, "using only available target as default");
		return targets.first();
	}

	// If there's a target named "default", use that
	if let Some(targets) = external_targets.get("default") {
		debug!("using 'default' target");
		return targets.first();
	}

	// Otherwise, use the first target alphabetically
	let mut sorted_ids: Vec<_> = external_targets.keys().collect();
	sorted_ids.sort();
	if let Some(id) = sorted_ids.first() {
		debug!(id, "using first alphabetical target as default");
		if let Some(targets) = external_targets.get(*id) {
			return targets.first();
		}
	}

	None
}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::targets::{TargetConnection, TargetEmail};

	#[test]
	fn test_determine_default_target_single() {
		let mut targets = HashMap::new();
		targets.insert(
			"only-target".to_string(),
			vec![ExternalTarget {
				id: "only-target".to_string(),
				conn: TargetConnection::Email(TargetEmail {
					addresses: vec!["test@example.com".to_string()],
				}),
			}],
		);

		let default = determine_default_target(&targets);
		assert!(default.is_some());
	}

	#[test]
	fn test_determine_default_target_named_default() {
		let mut targets = HashMap::new();
		targets.insert(
			"default".to_string(),
			vec![ExternalTarget {
				id: "default".to_string(),
				conn: TargetConnection::Email(TargetEmail {
					addresses: vec!["default@example.com".to_string()],
				}),
			}],
		);
		targets.insert(
			"other".to_string(),
			vec![ExternalTarget {
				id: "other".to_string(),
				conn: TargetConnection::Email(TargetEmail {
					addresses: vec!["other@example.com".to_string()],
				}),
			}],
		);

		let default = determine_default_target(&targets);
		assert!(default.is_some());
		let default = default.unwrap();
		match &default.conn {
			TargetConnection::Email(email) => assert_eq!(email.addresses[0], "default@example.com"),
			_ => panic!("expected email target"),
		}
	}

	#[test]
	fn test_determine_default_target_alphabetical() {
		let mut targets = HashMap::new();
		targets.insert(
			"zebra".to_string(),
			vec![ExternalTarget {
				id: "zebra".to_string(),
				conn: TargetConnection::Email(TargetEmail {
					addresses: vec!["zebra@example.com".to_string()],
				}),
			}],
		);
		targets.insert(
			"alpha".to_string(),
			vec![ExternalTarget {
				id: "alpha".to_string(),
				conn: TargetConnection::Email(TargetEmail {
					addresses: vec!["alpha@example.com".to_string()],
				}),
			}],
		);

		let default = determine_default_target(&targets);
		assert!(default.is_some());
		let default = default.unwrap();
		match &default.conn {
			TargetConnection::Email(email) => assert_eq!(email.addresses[0], "alpha@example.com"),
			_ => panic!("expected email target"),
		}
	}

	#[test]
	fn test_no_default_when_empty() {
		let targets = HashMap::new();
		assert!(determine_default_target(&targets).is_none());
	}
}