1use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct NodeId {
18 pub provider: String,
20 pub region: String,
22 pub node: String,
24 pub deployment_hash: String,
26 pub count: u32,
28}
29
30impl NodeId {
31 #[must_use]
33 pub fn to_canonical(&self) -> String {
34 format!(
35 "{}-{}-{}-{}-{}",
36 self.provider, self.region, self.node, self.deployment_hash, self.count
37 )
38 }
39
40 #[must_use]
43 pub fn parse(s: &str) -> Option<Self> {
44 let (head, count) = s.rsplit_once('-')?;
46 let count: u32 = count.parse().ok()?;
47 let (head, deployment_hash) = head.rsplit_once('-')?;
48 let (provider, rest) = head.split_once('-')?;
50 let (region, node) = rest.rsplit_once('-')?;
51 if provider.is_empty() || region.is_empty() || node.is_empty() || deployment_hash.is_empty()
52 {
53 return None;
54 }
55 Some(Self {
56 provider: provider.to_owned(),
57 region: region.to_owned(),
58 node: node.to_owned(),
59 deployment_hash: deployment_hash.to_owned(),
60 count,
61 })
62 }
63
64 #[must_use]
67 pub fn region_of(node_id: &str) -> Option<String> {
68 Self::parse(node_id).map(|n| n.region)
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn round_trips_a_simple_id() {
78 let id = NodeId {
79 provider: "linode".to_owned(),
80 region: "ord".to_owned(),
81 node: "n1".to_owned(),
82 deployment_hash: "deadbeef".to_owned(),
83 count: 0,
84 };
85 let s = id.to_canonical();
86 assert_eq!(s, "linode-ord-n1-deadbeef-0");
87 assert_eq!(NodeId::parse(&s), Some(id));
88 }
89
90 #[test]
91 fn region_may_contain_dashes() {
92 let id = NodeId::parse("aws-us-east-1-host7-cafef00d-2").unwrap();
93 assert_eq!(id.provider, "aws");
94 assert_eq!(id.region, "us-east-1");
95 assert_eq!(id.node, "host7");
96 assert_eq!(id.deployment_hash, "cafef00d");
97 assert_eq!(id.count, 2);
98 assert_eq!(id.to_canonical(), "aws-us-east-1-host7-cafef00d-2");
100 }
101
102 #[test]
103 fn region_of_extracts_just_the_region() {
104 assert_eq!(
105 NodeId::region_of("aws-us-east-1-host7-cafef00d-2").as_deref(),
106 Some("us-east-1")
107 );
108 }
109
110 #[test]
111 fn non_canonical_ids_are_none() {
112 assert_eq!(NodeId::parse("local"), None);
113 assert_eq!(NodeId::parse(""), None);
114 assert_eq!(NodeId::parse("linode-ord-n1-deadbeef"), None);
116 assert_eq!(NodeId::parse("linode-ord-n1-deadbeef-x"), None);
118 assert_eq!(NodeId::region_of("local"), None);
119 }
120}