reinhardt_deeplink/
router.rs1#![allow(deprecated)]
9
10use async_trait::async_trait;
11use hyper::Method;
12use reinhardt_core::endpoint::EndpointInfo;
13use reinhardt_http::{Handler, Request, Response};
14use reinhardt_urls::routers::{ServerRouter, UnifiedRouter};
15
16use crate::config::DeeplinkConfig;
17use crate::endpoints::{AasaHandler, AssetLinksHandler};
18use crate::error::DeeplinkError;
19
20#[derive(Clone)]
21struct AasaEndpoint {
22 handler: AasaHandler,
23}
24
25#[derive(Clone)]
26struct AasaJsonEndpoint {
27 handler: AasaHandler,
28}
29
30#[derive(Clone)]
31struct AssetLinksEndpoint {
32 handler: AssetLinksHandler,
33}
34
35impl EndpointInfo for AasaEndpoint {
36 fn path() -> &'static str {
37 "/apple-app-site-association"
38 }
39
40 fn method() -> Method {
41 Method::GET
42 }
43
44 fn name() -> &'static str {
45 "apple-app-site-association"
46 }
47}
48
49impl EndpointInfo for AasaJsonEndpoint {
50 fn path() -> &'static str {
51 "/apple-app-site-association.json"
52 }
53
54 fn method() -> Method {
55 Method::GET
56 }
57
58 fn name() -> &'static str {
59 "apple-app-site-association-json"
60 }
61}
62
63impl EndpointInfo for AssetLinksEndpoint {
64 fn path() -> &'static str {
65 "/assetlinks.json"
66 }
67
68 fn method() -> Method {
69 Method::GET
70 }
71
72 fn name() -> &'static str {
73 "assetlinks"
74 }
75}
76
77#[async_trait]
78impl Handler for AasaEndpoint {
79 async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
80 self.handler.handle(request).await
81 }
82}
83
84#[async_trait]
85impl Handler for AasaJsonEndpoint {
86 async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
87 self.handler.handle(request).await
88 }
89}
90
91#[async_trait]
92impl Handler for AssetLinksEndpoint {
93 async fn handle(&self, request: Request) -> reinhardt_core::exception::Result<Response> {
94 self.handler.handle(request).await
95 }
96}
97
98pub struct DeeplinkRouter {
131 config: DeeplinkConfig,
133
134 server: ServerRouter,
136}
137
138impl std::fmt::Debug for DeeplinkRouter {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 f.debug_struct("DeeplinkRouter")
141 .field("config", &self.config)
142 .field("server", &"ServerRouter { ... }")
143 .finish()
144 }
145}
146
147impl DeeplinkRouter {
148 pub fn new(config: DeeplinkConfig) -> Result<Self, DeeplinkError> {
156 let mut server = ServerRouter::new().with_namespace("wellknown");
157
158 if let Some(ios_config) = &config.ios {
160 let aasa_handler = AasaHandler::new(ios_config.clone())?;
161
162 server = server
164 .endpoint(|| AasaEndpoint {
165 handler: aasa_handler.clone(),
166 })
167 .endpoint(|| AasaJsonEndpoint {
168 handler: aasa_handler,
169 });
170 }
171
172 if let Some(android_config) = &config.android {
174 let assetlinks_handler = AssetLinksHandler::new(android_config.clone())?;
175 server = server.endpoint(|| AssetLinksEndpoint {
176 handler: assetlinks_handler,
177 });
178 }
179
180 Ok(Self { config, server })
181 }
182
183 pub fn into_server(self) -> ServerRouter {
188 self.server
189 }
190
191 pub fn server(&self) -> &ServerRouter {
193 &self.server
194 }
195
196 pub fn config(&self) -> &DeeplinkConfig {
198 &self.config
199 }
200}
201
202pub trait DeeplinkRouterExt {
227 type Output;
229
230 fn with_deeplinks(self, config: DeeplinkConfig) -> Result<Self::Output, DeeplinkError>;
238}
239
240impl DeeplinkRouterExt for UnifiedRouter {
241 type Output = Self;
242
243 fn with_deeplinks(self, config: DeeplinkConfig) -> Result<Self, DeeplinkError> {
244 let deeplink_router = DeeplinkRouter::new(config)?;
245 Ok(self.mount("/.well-known/", deeplink_router.into_server()))
246 }
247}
248
249impl DeeplinkRouterExt for ServerRouter {
250 type Output = Self;
251
252 fn with_deeplinks(self, config: DeeplinkConfig) -> Result<Self, DeeplinkError> {
253 let deeplink_router = DeeplinkRouter::new(config)?;
254 Ok(self.mount("/.well-known/", deeplink_router.into_server()))
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use rstest::rstest;
261
262 use super::*;
263 use crate::config::{AndroidConfig, IosConfig};
264
265 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";
266
267 fn create_ios_config() -> IosConfig {
268 IosConfig::builder()
269 .app_id("TEAM123456.com.example.app")
270 .paths(&["/products/*"])
271 .build()
272 }
273
274 fn create_android_config() -> AndroidConfig {
275 AndroidConfig::builder()
276 .package_name("com.example.app")
277 .sha256_fingerprint(VALID_FINGERPRINT)
278 .build()
279 .unwrap()
280 }
281
282 #[rstest]
283 fn test_router_creation_ios_only() {
284 let config = DeeplinkConfig::builder().ios(create_ios_config()).build();
285
286 let router = DeeplinkRouter::new(config).unwrap();
287 assert!(router.config().has_ios());
288 assert!(!router.config().has_android());
289 }
290
291 #[rstest]
292 fn test_router_creation_android_only() {
293 let config = DeeplinkConfig::builder()
294 .android(create_android_config())
295 .build();
296
297 let router = DeeplinkRouter::new(config).unwrap();
298 assert!(!router.config().has_ios());
299 assert!(router.config().has_android());
300 }
301
302 #[rstest]
303 fn test_router_creation_both() {
304 let config = DeeplinkConfig::builder()
305 .ios(create_ios_config())
306 .android(create_android_config())
307 .build();
308
309 let router = DeeplinkRouter::new(config).unwrap();
310 assert!(router.config().has_ios());
311 assert!(router.config().has_android());
312 }
313
314 #[rstest]
315 fn test_into_server() {
316 let config = DeeplinkConfig::builder().ios(create_ios_config()).build();
317
318 let router = DeeplinkRouter::new(config).unwrap();
319 let _server = router.into_server();
320 }
321
322 #[rstest]
323 fn test_extension_trait_unified() {
324 let config = DeeplinkConfig::builder().ios(create_ios_config()).build();
325
326 let router = UnifiedRouter::new().with_deeplinks(config).unwrap();
327
328 let _ = router;
330 }
331
332 #[rstest]
333 fn test_extension_trait_server() {
334 let config = DeeplinkConfig::builder().ios(create_ios_config()).build();
335
336 let router = ServerRouter::new().with_deeplinks(config).unwrap();
337
338 let _ = router;
340 }
341
342 #[rstest]
343 fn test_empty_config() {
344 let config = DeeplinkConfig::default();
345 let router = DeeplinkRouter::new(config).unwrap();
346
347 assert!(!router.config().is_configured());
349 }
350}