conjure_runtime/
user_agent.rs1use once_cell::sync::Lazy;
15use regex::Regex;
16use std::fmt;
17use witchcraft_log::warn;
18
19static VALID_NODE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-zA-Z0-9][a-zA-Z0-9.\-]*$").unwrap());
20static VALID_NAME: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[a-zA-Z][a-zA-Z0-9\-]*$").unwrap());
21static VALID_VERSION: Lazy<Regex> =
22 Lazy::new(|| Regex::new(r"^[0-9]+(\.[0-9]+)*(-rc[0-9]+)?(-[0-9]+-g[a-f0-9]+)?$").unwrap());
23
24const DEFAULT_VERSION: &str = "0.0.0";
25
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
28pub struct UserAgent {
29 node_id: Option<String>,
30 primary: Agent,
31 informational: Vec<Agent>,
32}
33
34impl fmt::Display for UserAgent {
35 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
36 write!(fmt, "{}", self.primary)?;
37
38 if let Some(ref node_id) = self.node_id {
39 write!(fmt, " (nodeId:{})", node_id)?;
40 }
41
42 for agent in &self.informational {
43 write!(fmt, " {}", agent)?;
44 }
45
46 Ok(())
47 }
48}
49
50impl UserAgent {
51 pub fn new(primary: Agent) -> UserAgent {
53 UserAgent {
54 node_id: None,
55 primary,
56 informational: vec![],
57 }
58 }
59
60 pub fn push_agent(&mut self, agent: Agent) {
62 self.informational.push(agent);
63 }
64
65 pub fn set_node_id(&mut self, node_id: &str) {
69 assert!(
70 VALID_NODE.is_match(node_id),
71 "invalid user agent node ID `{}`",
72 node_id
73 );
74 self.node_id = Some(node_id.to_string());
75 }
76
77 pub fn node_id(&self) -> Option<&str> {
79 self.node_id.as_deref()
80 }
81
82 pub fn primary(&self) -> &Agent {
84 &self.primary
85 }
86
87 pub fn informational(&self) -> &[Agent] {
89 &self.informational
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash)]
95pub struct Agent {
96 name: String,
97 version: String,
98}
99
100impl fmt::Display for Agent {
101 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
102 write!(fmt, "{}/{}", self.name, self.version)
103 }
104}
105
106impl Agent {
107 pub fn new(name: &str, mut version: &str) -> Agent {
109 assert!(VALID_NAME.is_match(name), "invalid agent name `{}`", name);
110
111 if !VALID_VERSION.is_match(version) {
112 warn!(
113 "encountered invalid user agent version",
114 safe: {
115 version: version,
116 }
117 );
118 version = DEFAULT_VERSION;
119 }
120
121 Agent {
122 name: name.to_string(),
123 version: version.to_string(),
124 }
125 }
126
127 pub fn name(&self) -> &str {
129 &self.name
130 }
131
132 pub fn version(&self) -> &str {
134 &self.version
135 }
136}
137
138#[cfg(test)]
139mod test {
140 use super::*;
141
142 #[test]
143 fn fmt() {
144 let mut agent = UserAgent::new(Agent::new("foobar", "1.2.3"));
145 agent.set_node_id("127.0.0.1");
146 agent.push_agent(Agent::new("fizzbuzz", "0.0.0-1-g12345"));
147 agent.push_agent(Agent::new("btob", "1.0.0-rc1"));
148 assert_eq!(
149 agent.to_string(),
150 "foobar/1.2.3 (nodeId:127.0.0.1) fizzbuzz/0.0.0-1-g12345 btob/1.0.0-rc1"
151 );
152 }
153
154 #[test]
155 fn version_fallback() {
156 let agent = Agent::new("foobar", "some-invalid-version");
157 assert_eq!(agent.version(), "0.0.0");
158 }
159}