Skip to main content

user_agent_parser/
request_guards.rs

1use std::borrow::Cow;
2
3use rocket::{
4    outcome::Outcome,
5    request::{FromRequest, Outcome as OutcomeResult, Request},
6};
7
8use crate::{UserAgentParser, models::*};
9
10const NOT_MANAGED: &str = "a `UserAgentParser` is not managed by Rocket; add \
11                           `.manage(UserAgentParser::from_path(..))` when building the instance";
12
13fn from_request_user_agent<'r>(request: &'r Request<'_>) -> UserAgent<'r> {
14    let user_agent: Option<Cow<'r, str>> =
15        request.headers().get("user-agent").next().map(Cow::from);
16
17    UserAgent {
18        user_agent,
19    }
20}
21
22#[rocket::async_trait]
23impl<'r> FromRequest<'r> for UserAgent<'r> {
24    type Error = ();
25
26    async fn from_request(request: &'r Request<'_>) -> OutcomeResult<Self, Self::Error> {
27        Outcome::Success(from_request_user_agent(request))
28    }
29}
30
31#[rocket::async_trait]
32impl<'r> FromRequest<'r> for &UserAgent<'r> {
33    type Error = ();
34
35    async fn from_request(request: &'r Request<'_>) -> OutcomeResult<Self, Self::Error> {
36        let cache = request.local_cache(|| from_request_user_agent(request).into_owned());
37
38        Outcome::Success(cache)
39    }
40}
41
42/// Implements both the owned and the cached-reference request guard of one model.
43macro_rules! impl_from_request {
44    ($model:ident, $parse:ident) => {
45        #[rocket::async_trait]
46        impl<'r> FromRequest<'r> for $model<'r> {
47            type Error = ();
48
49            async fn from_request(request: &'r Request<'_>) -> OutcomeResult<Self, Self::Error> {
50                Outcome::Success(parse(request))
51            }
52        }
53
54        #[rocket::async_trait]
55        impl<'r> FromRequest<'r> for &$model<'r> {
56            type Error = ();
57
58            async fn from_request(request: &'r Request<'_>) -> OutcomeResult<Self, Self::Error> {
59                let cache = request.local_cache_async(async { parse(request).into_owned() }).await;
60
61                Outcome::Success(cache)
62            }
63        }
64
65        fn parse<'r>(request: &'r Request<'_>) -> $model<'r> {
66            let user_agent_parser = request.rocket().state::<UserAgentParser>().expect(NOT_MANAGED);
67
68            match request.headers().get("user-agent").next() {
69                Some(user_agent) => user_agent_parser.$parse(user_agent),
70                None => $model::default(),
71            }
72        }
73    };
74}
75
76// Each model gets its own module, so that the `parse` function of the macro does not clash.
77mod product {
78    use super::*;
79
80    impl_from_request!(Product, parse_product);
81}
82
83mod os {
84    use super::*;
85
86    impl_from_request!(OS, parse_os);
87}
88
89mod device {
90    use super::*;
91
92    impl_from_request!(Device, parse_device);
93}
94
95mod cpu {
96    use super::*;
97
98    impl_from_request!(CPU, parse_cpu);
99}
100
101mod engine {
102    use super::*;
103
104    impl_from_request!(Engine, parse_engine);
105}