reinhardt_deeplink/config/
android.rs1use serde::Serialize;
6
7use crate::error::{DeeplinkError, validate_fingerprint, validate_package_name};
8
9#[derive(Debug, Clone)]
27pub struct AndroidConfig {
28 pub statements: Vec<AssetStatement>,
30}
31
32impl Serialize for AndroidConfig {
33 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
34 where
35 S: serde::Serializer,
36 {
37 self.statements.serialize(serializer)
39 }
40}
41
42#[derive(Debug, Clone, Serialize)]
44pub struct AssetStatement {
45 pub relation: Vec<String>,
47
48 pub target: AssetTarget,
50}
51
52#[derive(Debug, Clone, Serialize)]
54pub struct AssetTarget {
55 pub namespace: String,
57
58 pub package_name: String,
60
61 pub sha256_cert_fingerprints: Vec<String>,
63}
64
65impl AndroidConfig {
66 pub fn builder() -> AndroidConfigBuilder {
68 AndroidConfigBuilder::new()
69 }
70}
71
72#[derive(Debug, Default)]
74pub struct AndroidConfigBuilder {
75 package_name: Option<String>,
76 fingerprints: Vec<String>,
77 additional_packages: Vec<(String, Vec<String>)>,
78}
79
80impl AndroidConfigBuilder {
81 pub fn new() -> Self {
83 Self::default()
84 }
85
86 pub fn package_name(mut self, name: impl Into<String>) -> Self {
92 self.package_name = Some(name.into());
93 self
94 }
95
96 pub fn sha256_fingerprint(mut self, fingerprint: impl Into<String>) -> Self {
112 self.fingerprints.push(fingerprint.into());
113 self
114 }
115
116 pub fn sha256_fingerprints(mut self, fingerprints: &[&str]) -> Self {
118 self.fingerprints
119 .extend(fingerprints.iter().map(|s| (*s).to_string()));
120 self
121 }
122
123 pub fn additional_package(mut self, package: impl Into<String>, fingerprints: &[&str]) -> Self {
127 self.additional_packages.push((
128 package.into(),
129 fingerprints.iter().map(|s| (*s).to_string()).collect(),
130 ));
131 self
132 }
133
134 pub fn validate(&self) -> Result<(), DeeplinkError> {
145 if self.package_name.is_none() {
146 return Err(DeeplinkError::MissingPackageName);
147 }
148
149 if let Some(ref name) = self.package_name {
151 validate_package_name(name)?;
152 }
153
154 if self.fingerprints.is_empty() {
155 return Err(DeeplinkError::MissingFingerprint);
156 }
157
158 for fingerprint in &self.fingerprints {
159 validate_fingerprint(fingerprint)?;
160 }
161
162 for (pkg_name, fps) in &self.additional_packages {
164 validate_package_name(pkg_name)?;
165 if fps.is_empty() {
169 return Err(DeeplinkError::MissingFingerprint);
170 }
171 for fingerprint in fps {
172 validate_fingerprint(fingerprint)?;
173 }
174 }
175
176 Ok(())
177 }
178
179 pub fn build(self) -> Result<AndroidConfig, DeeplinkError> {
185 self.validate()?;
186 Ok(self.build_unchecked())
187 }
188
189 pub fn build_unchecked(self) -> AndroidConfig {
194 let mut statements = Vec::new();
195
196 if let Some(package_name) = self.package_name {
198 statements.push(AssetStatement {
199 relation: vec!["delegate_permission/common.handle_all_urls".to_string()],
200 target: AssetTarget {
201 namespace: "android_app".to_string(),
202 package_name,
203 sha256_cert_fingerprints: self.fingerprints,
204 },
205 });
206 }
207
208 for (package_name, fingerprints) in self.additional_packages {
210 statements.push(AssetStatement {
211 relation: vec!["delegate_permission/common.handle_all_urls".to_string()],
212 target: AssetTarget {
213 namespace: "android_app".to_string(),
214 package_name,
215 sha256_cert_fingerprints: fingerprints,
216 },
217 });
218 }
219
220 AndroidConfig { statements }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use rstest::rstest;
227
228 use super::*;
229
230 const VALID_FINGERPRINT: &str = "FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C";
231
232 #[rstest]
233 fn test_basic_android_config() {
234 let config = AndroidConfig::builder()
235 .package_name("com.example.app")
236 .sha256_fingerprint(VALID_FINGERPRINT)
237 .build()
238 .unwrap();
239
240 let json = serde_json::to_string_pretty(&config).unwrap();
241 assert!(json.contains("delegate_permission/common.handle_all_urls"));
242 assert!(json.contains("android_app"));
243 assert!(json.contains("com.example.app"));
244 assert!(json.contains(VALID_FINGERPRINT));
245 }
246
247 #[rstest]
248 fn test_android_config_json_array() {
249 let config = AndroidConfig::builder()
250 .package_name("com.example.app")
251 .sha256_fingerprint(VALID_FINGERPRINT)
252 .build()
253 .unwrap();
254
255 let json = serde_json::to_string(&config).unwrap();
256 assert!(json.starts_with('['));
258 assert!(json.ends_with(']'));
259 }
260
261 #[rstest]
262 fn test_multiple_fingerprints() {
263 let fp1 = "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00";
264 let fp2 = "11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11";
265
266 let config = AndroidConfig::builder()
267 .package_name("com.example.app")
268 .sha256_fingerprints(&[fp1, fp2])
269 .build()
270 .unwrap();
271
272 let json = serde_json::to_string_pretty(&config).unwrap();
273 assert!(json.contains(fp1));
274 assert!(json.contains(fp2));
275 }
276
277 #[rstest]
278 fn test_additional_packages() {
279 let config = AndroidConfig::builder()
280 .package_name("com.example.app")
281 .sha256_fingerprint(VALID_FINGERPRINT)
282 .additional_package("com.example.app2", &[VALID_FINGERPRINT])
283 .build()
284 .unwrap();
285
286 assert_eq!(config.statements.len(), 2);
287 let json = serde_json::to_string_pretty(&config).unwrap();
288 assert!(json.contains("com.example.app"));
289 assert!(json.contains("com.example.app2"));
290 }
291
292 #[rstest]
293 fn test_validation_missing_package() {
294 let builder = AndroidConfigBuilder::new().sha256_fingerprint(VALID_FINGERPRINT);
295 assert!(matches!(
296 builder.validate(),
297 Err(DeeplinkError::MissingPackageName)
298 ));
299 }
300
301 #[rstest]
302 fn test_validation_missing_fingerprint() {
303 let builder = AndroidConfigBuilder::new().package_name("com.example.app");
304 assert!(matches!(
305 builder.validate(),
306 Err(DeeplinkError::MissingFingerprint)
307 ));
308 }
309
310 #[rstest]
311 fn test_validation_invalid_fingerprint() {
312 let builder = AndroidConfigBuilder::new()
313 .package_name("com.example.app")
314 .sha256_fingerprint("invalid");
315 assert!(matches!(
316 builder.validate(),
317 Err(DeeplinkError::InvalidFingerprint(_))
318 ));
319 }
320
321 #[rstest]
322 fn test_validation_success() {
323 let builder = AndroidConfigBuilder::new()
324 .package_name("com.example.app")
325 .sha256_fingerprint(VALID_FINGERPRINT);
326 assert!(builder.validate().is_ok());
327 }
328
329 #[rstest]
330 fn test_validation_additional_package_empty_fingerprints() {
331 let builder = AndroidConfigBuilder::new()
333 .package_name("com.example.app")
334 .sha256_fingerprint(VALID_FINGERPRINT)
335 .additional_package("com.example.app2", &[]);
336
337 let result = builder.validate();
339
340 assert!(
342 matches!(result, Err(DeeplinkError::MissingFingerprint)),
343 "additional_package with empty fingerprints should be rejected"
344 );
345 }
346}