use serde::Serialize;
use crate::routing::RoutingHostname;
pub const EPHEMERAL_ID_HASH_LEN: usize = 8;
const RESERVED_APP_LABELS: &[&str] = &["auth", "cracha"];
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum HostnameError {
#[error("invalid DNS label {label:?} for segment {segment}: {reason}")]
InvalidLabel {
segment: &'static str,
label: String,
reason: &'static str,
},
#[error("app label {0:?} is reserved for the saguão control plane")]
ReservedApp(String),
}
pub trait HostnameResultExt<T>: Sized {
#[must_use = "an error wrap that isn't threaded via `?` swallows the hostname-format failure"]
fn hostname_ctx(self, context: &'static str) -> anyhow::Result<T>;
}
impl<T> HostnameResultExt<T> for Result<T, HostnameError> {
#[inline]
fn hostname_ctx(self, context: &'static str) -> anyhow::Result<T> {
self.map_err(|e| anyhow::anyhow!("{context}: {e}"))
}
}
pub fn fmt_fqdn(
app: &str,
ephemeral_id: &str,
cluster: &str,
location: &str,
domain: &str,
) -> Result<String, HostnameError> {
validate_app(app)?;
validate_label("ephemeral_id", ephemeral_id)?;
validate_label("cluster", cluster)?;
validate_label("location", location)?;
validate_domain("domain", domain)?;
Ok(format!(
"{app}.{ephemeral_id}.{cluster}.{location}.{domain}"
))
}
pub fn fmt_fqdn_stable(
app: &str,
cluster: &str,
location: &str,
domain: &str,
) -> Result<String, HostnameError> {
validate_app(app)?;
validate_label("cluster", cluster)?;
validate_label("location", location)?;
validate_domain("domain", domain)?;
Ok(format!("{app}.{cluster}.{location}.{domain}"))
}
pub fn ephemeral_id_from_spec<T: Serialize>(spec: &T) -> Result<String, HostnameError> {
let bytes =
crate::three_pillar::canonical_bytes(spec).map_err(|_| HostnameError::InvalidLabel {
segment: "spec",
label: "<unserializable>".into(),
reason: "spec failed to canonicalize",
})?;
Ok(short_hex_blake3(&bytes, EPHEMERAL_ID_HASH_LEN))
}
pub fn resolve_ephemeral_id<'a>(hostname: &'a RoutingHostname, fallback_hash: &'a str) -> &'a str {
match &hostname.instance {
Some(s) if !s.is_empty() => s.as_str(),
_ => fallback_hash,
}
}
fn validate_label(segment: &'static str, label: &str) -> Result<(), HostnameError> {
if label.is_empty() || label.len() > 63 {
return Err(HostnameError::InvalidLabel {
segment,
label: label.to_string(),
reason: "must be 1–63 characters",
});
}
if label.starts_with('-') || label.ends_with('-') {
return Err(HostnameError::InvalidLabel {
segment,
label: label.to_string(),
reason: "must not start or end with a hyphen",
});
}
if !label
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
{
return Err(HostnameError::InvalidLabel {
segment,
label: label.to_string(),
reason: "must contain only [a-z0-9-]",
});
}
Ok(())
}
fn validate_app(app: &str) -> Result<(), HostnameError> {
validate_label("app", app)?;
if RESERVED_APP_LABELS.contains(&app) {
return Err(HostnameError::ReservedApp(app.to_string()));
}
Ok(())
}
fn validate_domain(segment: &'static str, domain: &str) -> Result<(), HostnameError> {
if domain.is_empty() {
return Err(HostnameError::InvalidLabel {
segment,
label: domain.to_string(),
reason: "must not be empty",
});
}
for piece in domain.split('.') {
validate_label(segment, piece)?;
}
Ok(())
}
fn short_hex_blake3(bytes: &[u8], len: usize) -> String {
crate::hash::hex_blake3(bytes).chars().take(len).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[test]
fn fmt_fqdn_per_instance() {
let f = fmt_fqdn("api", "demo-prod", "pleme-dev", "use1", "quero.lol").unwrap();
assert_eq!(f, "api.demo-prod.pleme-dev.use1.quero.lol");
}
#[test]
fn fmt_fqdn_stable_form() {
let f = fmt_fqdn_stable("api", "pleme-dev", "use1", "quero.lol").unwrap();
assert_eq!(f, "api.pleme-dev.use1.quero.lol");
}
#[test]
fn fmt_fqdn_with_multilevel_domain() {
let f = fmt_fqdn("api", "env-a", "rio", "us", "internal.example.com").unwrap();
assert_eq!(f, "api.env-a.rio.us.internal.example.com");
}
#[test]
fn reserved_app_rejected() {
let r = fmt_fqdn("auth", "x", "y", "z", "example.com");
assert!(matches!(r, Err(HostnameError::ReservedApp(_))));
let r = fmt_fqdn_stable("cracha", "y", "z", "example.com");
assert!(matches!(r, Err(HostnameError::ReservedApp(_))));
}
#[test]
fn empty_label_rejected() {
let r = fmt_fqdn("", "x", "y", "z", "example.com");
assert!(matches!(
r,
Err(HostnameError::InvalidLabel { segment: "app", .. })
));
}
#[test]
fn too_long_label_rejected() {
let long = "a".repeat(64);
let r = fmt_fqdn(&long, "x", "y", "z", "example.com");
assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
}
#[test]
fn uppercase_label_rejected() {
let r = fmt_fqdn("API", "x", "y", "z", "example.com");
assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
}
#[test]
fn leading_hyphen_label_rejected() {
let r = fmt_fqdn("api", "-bad", "y", "z", "example.com");
assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
}
#[test]
fn underscore_label_rejected() {
let r = fmt_fqdn("api", "x_y", "z", "w", "example.com");
assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
}
#[test]
fn empty_domain_rejected() {
let r = fmt_fqdn("api", "x", "y", "z", "");
assert!(matches!(r, Err(HostnameError::InvalidLabel { .. })));
}
#[derive(Serialize, Deserialize)]
struct TestSpec {
a: u32,
b: String,
}
#[test]
fn ephemeral_id_is_8_hex_chars() {
let spec = TestSpec {
a: 1,
b: "x".into(),
};
let id = ephemeral_id_from_spec(&spec).unwrap();
assert_eq!(id.len(), EPHEMERAL_ID_HASH_LEN);
assert!(id.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn ephemeral_id_is_deterministic() {
let s1 = TestSpec {
a: 1,
b: "x".into(),
};
let s2 = TestSpec {
a: 1,
b: "x".into(),
};
assert_eq!(
ephemeral_id_from_spec(&s1).unwrap(),
ephemeral_id_from_spec(&s2).unwrap()
);
}
#[test]
fn ephemeral_id_changes_with_spec() {
let s1 = TestSpec {
a: 1,
b: "x".into(),
};
let s2 = TestSpec {
a: 2,
b: "x".into(),
};
let s3 = TestSpec {
a: 1,
b: "y".into(),
};
let id1 = ephemeral_id_from_spec(&s1).unwrap();
let id2 = ephemeral_id_from_spec(&s2).unwrap();
let id3 = ephemeral_id_from_spec(&s3).unwrap();
assert_ne!(id1, id2);
assert_ne!(id1, id3);
assert_ne!(id2, id3);
}
#[test]
fn ephemeral_id_lowercase_valid_dns_label() {
let spec = TestSpec {
a: 42,
b: "anything".into(),
};
let id = ephemeral_id_from_spec(&spec).unwrap();
validate_label("ephemeral_id", &id).unwrap();
}
#[test]
fn resolve_named_slot_wins() {
let h = RoutingHostname::instanced("api", "demo-prod");
assert_eq!(resolve_ephemeral_id(&h, "fallback"), "demo-prod");
}
#[test]
fn resolve_empty_named_falls_back() {
let h = RoutingHostname {
app: "api".into(),
instance: Some(String::new()),
cluster: None,
};
assert_eq!(resolve_ephemeral_id(&h, "abc123de"), "abc123de");
}
#[test]
fn resolve_unset_named_falls_back() {
let h = RoutingHostname::content_hashed("api");
assert_eq!(resolve_ephemeral_id(&h, "abc123de"), "abc123de");
}
fn sample_err() -> HostnameError {
HostnameError::InvalidLabel {
segment: "app",
label: "BAD".into(),
reason: "must contain only [a-z0-9-]",
}
}
#[test]
fn hostname_ctx_static_str_context_matches_pre_lift_format_bytewise() {
let raw: Result<(), HostnameError> = Err(sample_err());
let via_trait = raw.hostname_ctx("fmt_fqdn (per-instance)").unwrap_err();
let pre_lift = anyhow::anyhow!("fmt_fqdn (per-instance): {}", sample_err());
assert_eq!(
format!("{via_trait}"),
format!("{pre_lift}"),
"hostname_ctx wrap must be Display-identical to pre-lift anyhow! chain"
);
}
#[test]
fn hostname_ctx_ok_arm_is_a_pure_passthrough() {
let raw: Result<&'static str, HostnameError> = Ok("api.demo-prod.pleme-dev.use1.quero.lol");
assert_eq!(
raw.hostname_ctx("noop").unwrap(),
"api.demo-prod.pleme-dev.use1.quero.lol"
);
}
#[test]
fn hostname_ctx_threads_the_underlying_hostname_error_display_verbatim() {
let raw: Result<(), HostnameError> = Err(HostnameError::ReservedApp("auth".into()));
let wrapped = raw.hostname_ctx("fmt_fqdn_stable").unwrap_err();
let expected_tail = format!("{}", HostnameError::ReservedApp("auth".into()));
let expected = format!("fmt_fqdn_stable: {expected_tail}");
assert_eq!(format!("{wrapped}"), expected);
assert!(
format!("{wrapped}").ends_with(&expected_tail),
"wrap must end with the HostnameError Display verbatim"
);
}
#[test]
fn hostname_ctx_composes_over_ephemeral_id_from_spec_call_shape() {
#[derive(Serialize)]
struct NoSuchThingAsAnUnserializableStruct {
a: u32,
}
let v = NoSuchThingAsAnUnserializableStruct { a: 1 };
let composed: anyhow::Result<String> =
ephemeral_id_from_spec(&v).hostname_ctx("ephemeral_id_from_spec");
assert!(composed.is_ok());
assert_eq!(composed.unwrap().len(), EPHEMERAL_ID_HASH_LEN);
}
#[test]
fn validate_app_accepts_valid_lowercase_alphanumeric_label() {
validate_app("api").unwrap();
validate_app("gateway").unwrap();
validate_app("demo-app").unwrap();
validate_app("a").unwrap();
}
#[test]
fn validate_app_rejects_empty_label_with_invalid_label_variant() {
assert!(matches!(
validate_app(""),
Err(HostnameError::InvalidLabel { segment: "app", .. })
));
}
#[test]
fn validate_app_rejects_uppercase_label_with_invalid_label_variant() {
assert!(matches!(
validate_app("API"),
Err(HostnameError::InvalidLabel { segment: "app", .. })
));
}
#[test]
fn validate_app_rejects_too_long_label_with_invalid_label_variant() {
let long = "a".repeat(64);
assert!(matches!(
validate_app(&long),
Err(HostnameError::InvalidLabel { segment: "app", .. })
));
}
#[test]
fn validate_app_rejects_reserved_auth_label_with_reserved_app_variant() {
assert!(matches!(
validate_app("auth"),
Err(HostnameError::ReservedApp(ref s)) if s == "auth"
));
}
#[test]
fn validate_app_rejects_reserved_cracha_label_with_reserved_app_variant() {
assert!(matches!(
validate_app("cracha"),
Err(HostnameError::ReservedApp(ref s)) if s == "cracha"
));
}
#[test]
fn validate_app_step_order_puts_rfc_1123_check_before_reserved_check() {
assert!(matches!(
validate_app("AUTH"),
Err(HostnameError::InvalidLabel { segment: "app", .. })
));
assert!(matches!(
validate_app("Cracha"),
Err(HostnameError::InvalidLabel { segment: "app", .. })
));
}
#[test]
fn validate_app_matches_pre_lift_two_step_chain_bytewise_across_every_variant_shape() {
fn pre_lift(app: &str) -> Result<(), HostnameError> {
validate_label("app", app)?;
if RESERVED_APP_LABELS.contains(&app) {
return Err(HostnameError::ReservedApp(app.to_string()));
}
Ok(())
}
for input in [
"api",
"gateway",
"demo-app",
"a",
"",
"API",
"-bad",
"bad-",
"with_underscore",
"auth",
"cracha",
"AUTH",
"Cracha",
] {
let via_primitive = validate_app(input);
let via_pre_lift = pre_lift(input);
match (via_primitive, via_pre_lift) {
(Ok(()), Ok(())) => {}
(Err(a), Err(b)) => assert_eq!(a, b, "variant mismatch for {input:?}"),
(a, b) => panic!("arm mismatch for {input:?}: primitive={a:?} pre_lift={b:?}"),
}
}
}
#[test]
fn validate_app_covers_every_currently_reserved_label_at_the_primitive() {
for reserved in RESERVED_APP_LABELS {
assert!(
matches!(validate_app(reserved), Err(HostnameError::ReservedApp(ref s)) if s == reserved),
"RESERVED_APP_LABELS entry {reserved:?} must surface as ReservedApp at the substrate"
);
}
}
#[test]
fn fmt_fqdn_routes_app_slot_through_validate_app_primitive() {
assert!(matches!(
fmt_fqdn("AUTH", "x", "y", "z", "example.com"),
Err(HostnameError::InvalidLabel { segment: "app", .. })
));
assert!(matches!(
fmt_fqdn("auth", "x", "y", "z", "example.com"),
Err(HostnameError::ReservedApp(ref s)) if s == "auth"
));
}
#[test]
fn fmt_fqdn_stable_routes_app_slot_through_validate_app_primitive() {
assert!(matches!(
fmt_fqdn_stable("Cracha", "y", "z", "example.com"),
Err(HostnameError::InvalidLabel { segment: "app", .. })
));
assert!(matches!(
fmt_fqdn_stable("cracha", "y", "z", "example.com"),
Err(HostnameError::ReservedApp(ref s)) if s == "cracha"
));
}
#[test]
fn end_to_end_named_and_unnamed_for_same_process() {
let spec = TestSpec {
a: 1,
b: "x".into(),
};
let hash = ephemeral_id_from_spec(&spec).unwrap();
let h_named = RoutingHostname::instanced("api", "demo-prod");
let h_anon = RoutingHostname::content_hashed("gateway");
let id_named = resolve_ephemeral_id(&h_named, &hash);
let id_anon = resolve_ephemeral_id(&h_anon, &hash);
let fqdn_named =
fmt_fqdn(&h_named.app, id_named, "pleme-dev", "use1", "quero.lol").unwrap();
let fqdn_anon = fmt_fqdn(&h_anon.app, id_anon, "pleme-dev", "use1", "quero.lol").unwrap();
assert_eq!(fqdn_named, "api.demo-prod.pleme-dev.use1.quero.lol");
assert!(fqdn_anon.starts_with("gateway."));
assert!(fqdn_anon.ends_with(".pleme-dev.use1.quero.lol"));
assert_eq!(fqdn_anon.matches('.').count(), 5);
}
}