walletkit_core/
user_agent.rs1const WORLD_APP_USER_AGENT_PRODUCT: &str = "WorldApp";
4const WORLD_ID_APP_USER_AGENT_PRODUCT: &str = "WorldID";
5const WORLD_ID_ANDROID_CLIENT_NAME: &str = "android-id";
6const WORLD_ID_IOS_CLIENT_NAME: &str = "ios-id";
7
8#[derive(Debug, Clone, uniffi::Object)]
10pub struct UserAgent(pub String);
11
12impl std::fmt::Display for UserAgent {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 write!(f, "{}", self.0)?;
16 Ok(())
17 }
18}
19
20#[uniffi::export]
21impl UserAgent {
22 #[must_use]
24 pub fn header_value(&self) -> String {
25 self.0.clone()
26 }
27}
28
29#[derive(Debug, Clone, uniffi::Object)]
35pub struct UserAgentBuilder {
36 segments: Vec<String>,
37}
38
39#[uniffi::export]
40impl UserAgentBuilder {
41 #[uniffi::constructor]
43 #[must_use]
44 pub const fn new() -> Self {
45 Self {
46 segments: Vec::new(),
47 }
48 }
49
50 #[must_use]
52 pub fn with_segment(&self, name: &str, version: &str) -> Self {
53 let mut next = self.clone();
54 next.segments.push(format!("{name}/{version}"));
55 next
56 }
57
58 #[must_use]
63 pub fn with_app_segment_for_client(
64 &self,
65 app_version: &str,
66 client_name: &str,
67 ) -> Self {
68 self.with_segment(user_agent_product_for_client(client_name), app_version)
69 }
70
71 #[must_use]
73 pub fn with_walletkit_segment(&self) -> Self {
74 let mut next = self.clone();
75 let crate_version = env!("CARGO_PKG_VERSION");
76 next.segments
77 .push(format!("walletkit-core/{crate_version}"));
78 next
79 }
80
81 #[must_use]
83 pub fn with_client_segment(&self, client_name: &str, os_version: &str) -> Self {
84 self.with_segment(client_name, os_version)
85 }
86
87 #[must_use]
89 pub fn build(&self) -> UserAgent {
90 UserAgent(self.segments.join(" "))
91 }
92}
93
94impl Default for UserAgentBuilder {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100fn user_agent_product_for_client(client_name: &str) -> &'static str {
101 match client_name {
102 WORLD_ID_ANDROID_CLIENT_NAME | WORLD_ID_IOS_CLIENT_NAME => {
103 WORLD_ID_APP_USER_AGENT_PRODUCT
104 }
105 _ => WORLD_APP_USER_AGENT_PRODUCT,
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use test_case::test_case;
113
114 #[test_case(
115 &UserAgentBuilder::new().with_walletkit_segment(),
116 concat!("walletkit-core/", env!("CARGO_PKG_VERSION"));
117 "walletkit_only"
118 )]
119 #[test_case(
120 &UserAgentBuilder::new()
121 .with_segment("WorldApp", "1.0")
122 .with_walletkit_segment(),
123 concat!("WorldApp/1.0 walletkit-core/", env!("CARGO_PKG_VERSION"));
124 "world_app_then_walletkit"
125 )]
126 #[test_case(
127 &UserAgentBuilder::new()
128 .with_segment("WorldApp", "2.1")
129 .with_walletkit_segment()
130 .with_segment("iOS", "17.0"),
131 concat!("WorldApp/2.1 walletkit-core/", env!("CARGO_PKG_VERSION"), " iOS/17.0");
132 "native_style_world_walletkit_os"
133 )]
134 #[test_case(
135 &UserAgentBuilder::new()
136 .with_walletkit_segment()
137 .with_segment("CLI", "1.2.3"),
138 concat!("walletkit-core/", env!("CARGO_PKG_VERSION"), " CLI/1.2.3");
139 "walletkit_then_cli"
140 )]
141 #[test_case(
142 &UserAgentBuilder::new()
143 .with_app_segment_for_client("4.0.2500", "android")
144 .with_walletkit_segment()
145 .with_client_segment("android", "15"),
146 concat!("WorldApp/4.0.2500 walletkit-core/", env!("CARGO_PKG_VERSION"), " android/15");
147 "world_app_android_client"
148 )]
149 #[test_case(
150 &UserAgentBuilder::new()
151 .with_app_segment_for_client("4.0.2500", "ios")
152 .with_walletkit_segment()
153 .with_client_segment("ios", "26.4.2"),
154 concat!("WorldApp/4.0.2500 walletkit-core/", env!("CARGO_PKG_VERSION"), " ios/26.4.2");
155 "world_app_ios_client"
156 )]
157 #[test_case(
158 &UserAgentBuilder::new()
159 .with_app_segment_for_client("1.0.100", "android-id")
160 .with_walletkit_segment()
161 .with_client_segment("android-id", "15"),
162 concat!("WorldID/1.0.100 walletkit-core/", env!("CARGO_PKG_VERSION"), " android-id/15");
163 "world_id_android_client"
164 )]
165 #[test_case(
166 &UserAgentBuilder::new()
167 .with_app_segment_for_client("1.0.100", "ios-id")
168 .with_walletkit_segment()
169 .with_client_segment("ios-id", "26.4.2"),
170 concat!("WorldID/1.0.100 walletkit-core/", env!("CARGO_PKG_VERSION"), " ios-id/26.4.2");
171 "world_id_ios_client"
172 )]
173 fn user_agent_builder_expected(builder: &UserAgentBuilder, expected: &'static str) {
174 assert_eq!(builder.build().to_string(), expected);
175 }
176
177 #[test]
178 fn user_agent_exposes_header_value_for_ffi_consumers() {
179 let user_agent = UserAgentBuilder::new()
180 .with_app_segment_for_client("1.0.100", "android-id")
181 .with_walletkit_segment()
182 .with_client_segment("android-id", "15")
183 .build();
184
185 assert_eq!(
186 user_agent.header_value(),
187 concat!(
188 "WorldID/1.0.100 walletkit-core/",
189 env!("CARGO_PKG_VERSION"),
190 " android-id/15"
191 )
192 );
193 }
194}