dinio_sdk/ops/zones.rs
1//! The zone a host name belongs to, among the zones the caller can see.
2
3use crate::client::Client;
4use crate::error::{Error, Result};
5use crate::models::Zone;
6
7/// The zone of `zones` that `host` is in: the longest zone name equal to the host or a
8/// suffix of it at a label boundary. Case and a trailing dot do not matter.
9///
10/// # Examples
11///
12/// ```
13/// use dinio_sdk::models::Zone;
14/// use dinio_sdk::ops::zone_for_host;
15///
16/// # fn zone(name: &str) -> Zone {
17/// # serde_json::from_value(serde_json::json!({
18/// # "uuid": "00000000-0000-0000-0000-000000000001", "name": name,
19/// # "created_at": 0, "updated_at": 0
20/// # })).unwrap()
21/// # }
22/// let zones = [zone("example.com"), zone("shop.example.com")];
23/// assert_eq!(zone_for_host(&zones, "go.example.com").unwrap().name, "example.com");
24/// assert_eq!(zone_for_host(&zones, "Www.Shop.Example.com.").unwrap().name, "shop.example.com");
25/// assert!(zone_for_host(&zones, "badexample.com").is_none());
26/// ```
27pub fn zone_for_host<'a>(zones: &'a [Zone], host: &str) -> Option<&'a Zone> {
28 let host = normalize(host);
29 zones
30 .iter()
31 .filter(|zone| {
32 let zone_name = normalize(&zone.name);
33 host == zone_name
34 || host
35 .strip_suffix(zone_name.as_str())
36 .is_some_and(|rest| rest.ends_with('.'))
37 })
38 .max_by_key(|zone| zone.name.len())
39}
40
41/// The zone `host` belongs to, among every zone the caller can access (see
42/// [`zone_for_host`]). Core still checks that the zone serves the name (not delegated
43/// away, no more specific zone of someone else).
44///
45/// # Errors
46///
47/// * [`Error::NotFound`] if no accessible zone contains the host.
48/// * Any error of [`Zones::list_all`](crate::api::Zones::list_all).
49///
50/// # Examples
51///
52/// ```no_run
53/// # async fn example(client: &dinio_sdk::Client) -> dinio_sdk::Result<()> {
54/// let zone = dinio_sdk::ops::find_zone_for_host(client, "go.example.com").await?;
55/// println!("go.example.com is in {}", zone.name);
56/// # Ok(())
57/// # }
58/// ```
59pub async fn find_zone_for_host(client: &Client, host: &str) -> Result<Zone> {
60 let zones = client.zones().list_all().await?;
61 zone_for_host(&zones, host).cloned().ok_or_else(|| {
62 Error::NotFound(format!(
63 "no zone you can access contains {}",
64 normalize(host)
65 ))
66 })
67}
68
69fn normalize(name: &str) -> String {
70 name.trim().trim_end_matches('.').to_ascii_lowercase()
71}