//! `/api/egress/allowed-domains` — the destinations the swarm lets agents //! reach through their hive's egress proxy. use serde::{Deserialize, Serialize}; /// One allowed destination and who may reach it. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(utoipa::ToSchema))] #[serde(deny_unknown_fields)] pub struct AllowedDomain { /// A DNS name of at least two labels, such as `github.com`, stored /// lower-cased. One leading dot, as in `.githubusercontent.com`, also /// allows every subdomain. IP addresses, wildcards, ports, paths and the /// proxy's own always-allowed names are refused. #[cfg_attr(feature = "schema", schema(example = "github.com"))] pub domain: String, /// The agent this entry is for. Absent means every agent in the swarm. #[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "schema", schema(example = "atlas"))] pub agent: Option, } /// The swarm's allow-list: every entry, swarm-wide and per agent, sorted. #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(utoipa::ToSchema))] pub struct AllowedDomains { pub entries: Vec, } #[cfg(test)] mod tests { use super::*; #[test] fn an_entry_leaves_out_an_unset_agent_and_refuses_unknown_fields() { let swarm_wide = AllowedDomain { domain: "github.com".to_owned(), agent: None, }; let wire = r#"{"domain":"github.com"}"#; assert_eq!(serde_json::to_string(&swarm_wide).unwrap(), wire); assert_eq!( serde_json::from_str::(wire).unwrap(), swarm_wide ); let scoped = r#"{"domain":"crates.io","agent":"atlas"}"#; assert_eq!( serde_json::from_str::(scoped) .unwrap() .agent .as_deref(), Some("atlas") ); assert!(serde_json::from_str::(r#"{"domain":"a.io","hive":"x"}"#).is_err()); } }