1use crate::error::{Error, Result};
5
6pub fn qualify_name(name: &str, zone: &str) -> Result<String> {
28 let name = name.trim();
29 let zone = normalize(zone);
30 if name.is_empty() {
31 return Err(Error::InvalidInput("record name must not be empty".into()));
32 }
33 if name == "@" {
34 return Ok(zone);
35 }
36 if let Some(absolute) = name.strip_suffix('.') {
37 return Ok(absolute.to_lowercase());
38 }
39 let lower = name.to_lowercase();
40 if lower == zone || lower.ends_with(&format!(".{zone}")) {
41 return Ok(lower);
42 }
43 Ok(format!("{lower}.{zone}"))
44}
45
46pub fn relative_name(full: &str, zone: &str) -> String {
58 let zone = normalize(zone);
59 if full == zone {
60 return "@".into();
61 }
62 match full.strip_suffix(&format!(".{zone}")) {
63 Some(relative) => relative.to_owned(),
64 None => full.to_owned(),
65 }
66}
67
68fn normalize(name: &str) -> String {
69 name.trim().trim_end_matches('.').to_lowercase()
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn qualify() {
78 let z = "example.com";
79 assert_eq!(qualify_name("@", z).unwrap(), "example.com");
80 assert_eq!(qualify_name("www", z).unwrap(), "www.example.com");
81 assert_eq!(qualify_name("WWW", z).unwrap(), "www.example.com");
82 assert_eq!(
83 qualify_name("www.example.com", z).unwrap(),
84 "www.example.com"
85 );
86 assert_eq!(qualify_name("example.com", z).unwrap(), "example.com");
87 assert_eq!(qualify_name("a.b", z).unwrap(), "a.b.example.com");
88 assert_eq!(qualify_name("*.dev", z).unwrap(), "*.dev.example.com");
89 assert_eq!(qualify_name("other.org.", z).unwrap(), "other.org");
90 assert_eq!(
91 qualify_name("_sip._tcp", "Example.COM.").unwrap(),
92 "_sip._tcp.example.com"
93 );
94 assert!(qualify_name(" ", z).is_err());
95 }
96
97 #[test]
98 fn relative() {
99 let z = "example.com";
100 assert_eq!(relative_name("example.com", z), "@");
101 assert_eq!(relative_name("www.example.com", z), "www");
102 assert_eq!(relative_name("notexample.com", z), "notexample.com");
103 }
104}