nym_http_api_client/
user_agent.rs1use std::{fmt, str::FromStr};
5
6use http::HeaderValue;
7use nym_bin_common::build_information::{BinaryBuildInformation, BinaryBuildInformationOwned};
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
12pub struct UserAgent {
13 pub application: String,
15 pub version: String,
17 pub platform: String,
19 pub git_commit: String,
21}
22
23#[macro_export]
27macro_rules! generate_user_agent {
28 () => {
29 $crate::UserAgent::from($crate::bin_info!())
30 };
31}
32
33#[derive(Clone, Debug, thiserror::Error)]
34#[error("invalid user agent string: {0}")]
35pub struct UserAgentError(String);
36
37impl FromStr for UserAgent {
38 type Err = UserAgentError;
39
40 fn from_str(s: &str) -> Result<Self, Self::Err> {
41 let parts: Vec<&str> = s.split('/').collect();
42 if parts.len() != 4 {
43 return Err(UserAgentError(s.to_string()));
44 }
45
46 Ok(UserAgent {
47 application: parts[0].to_string(),
48 version: parts[1].to_string(),
49 platform: parts[2].to_string(),
50 git_commit: parts[3].to_string(),
51 })
52 }
53}
54
55impl TryFrom<&str> for UserAgent {
56 type Error = UserAgentError;
57
58 fn try_from(s: &str) -> Result<Self, Self::Error> {
59 UserAgent::from_str(s)
60 }
61}
62
63impl fmt::Display for UserAgent {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 let abbreviated_commit = self.git_commit.chars().take(7).collect::<String>();
66 write!(
67 f,
68 "{}/{}/{}/{}",
69 self.application, self.version, self.platform, abbreviated_commit
70 )
71 }
72}
73
74impl TryFrom<UserAgent> for HeaderValue {
75 type Error = http::header::InvalidHeaderValue;
76
77 fn try_from(user_agent: UserAgent) -> Result<Self, Self::Error> {
78 HeaderValue::from_str(&user_agent.to_string())
79 }
80}
81
82impl From<BinaryBuildInformation> for UserAgent {
83 fn from(build_info: BinaryBuildInformation) -> Self {
84 UserAgent {
85 application: build_info.binary_name.to_string(),
86 version: build_info.build_version.to_string(),
87 platform: build_info.cargo_triple.to_string(),
88 git_commit: build_info.commit_sha.to_string(),
89 }
90 }
91}
92
93impl From<BinaryBuildInformationOwned> for UserAgent {
94 fn from(build_info: BinaryBuildInformationOwned) -> Self {
95 UserAgent {
96 application: build_info.binary_name,
97 version: build_info.build_version,
98 platform: build_info.cargo_triple,
99 git_commit: build_info.commit_sha,
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn parsing_valid_user_agent() {
110 let user_agent = "nym-mixnode/0.11.0/x86_64-unknown-linux-gnu/abcdefg";
111 let parsed = UserAgent::from_str(user_agent).unwrap();
112 assert_eq!(
113 parsed,
114 UserAgent {
115 application: "nym-mixnode".to_string(),
116 version: "0.11.0".to_string(),
117 platform: "x86_64-unknown-linux-gnu".to_string(),
118 git_commit: "abcdefg".to_string()
119 }
120 );
121 }
122
123 #[test]
124 fn parsing_invalid_user_agent() {
125 let user_agent = "nym-mixnode/0.11.0/x86_64-unknown-linux-gnu";
126 assert!(UserAgent::from_str(user_agent).is_err());
127 }
128
129 #[test]
130 fn converting_user_agent_to_string() {
131 let user_agent = UserAgent {
132 application: "nym-mixnode".to_string(),
133 version: "0.11.0".to_string(),
134 platform: "x86_64-unknown-linux-gnu".to_string(),
135 git_commit: "abcdefg".to_string(),
136 };
137
138 assert_eq!(
139 user_agent.to_string(),
140 "nym-mixnode/0.11.0/x86_64-unknown-linux-gnu/abcdefg"
141 );
142 }
143
144 #[test]
145 fn converting_user_agent_to_display() {
146 let user_agent = UserAgent {
147 application: "nym-mixnode".to_string(),
148 version: "0.11.0".to_string(),
149 platform: "x86_64-unknown-linux-gnu".to_string(),
150 git_commit: "abcdefg".to_string(),
151 };
152
153 assert_eq!(
154 format!("{user_agent}"),
155 "nym-mixnode/0.11.0/x86_64-unknown-linux-gnu/abcdefg"
156 );
157 }
158
159 #[test]
160 fn converting_user_agent_to_header_value_fails() {
161 let user_agent = UserAgent {
162 application: "nym-mixnode".to_string(),
163 version: "0.11.0".to_string(),
164 platform: "x86_64-unknown-linux-gnu".to_string(),
165 git_commit: "abcdefg".to_string(),
166 };
167
168 let header_value: Result<HeaderValue, _> = user_agent.clone().try_into();
169 assert!(header_value.is_ok());
170 }
171
172 #[test]
173 fn converting_user_agent_to_header_value_has_same_string_representation() {
174 let user_agent = UserAgent {
175 application: "nym-mixnode".to_string(),
176 version: "0.11.0".to_string(),
177 platform: "x86_64-unknown-linux-gnu".to_string(),
178 git_commit: "abcdefg".to_string(),
179 };
180
181 let header_value: HeaderValue = user_agent.clone().try_into().unwrap();
182 assert_eq!(header_value.to_str().unwrap(), user_agent.to_string());
183 }
184}