1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use crate::error::Error;
use crate::utils::struct_fields;

use serde::Deserialize;
use std::fmt::Debug;
use worker::Method;
use worker::Request;
use worker::RouteContext;

/// Extract typed information with the supplied struct from the query string from `Request`
/// To extract information from `Request`, `T` must implement `Deserialize` trait.
///
/// # Panics
/// Currently only regular structs are supported.
/// If the given `T` is not a regular struct (eg: tuple, unit) it will panic at runtime.
///
/// ```
/// use serde::{Deserialize, Serialize};
/// use worker::{Response, Result, RouteContext};
/// use worker_route::{get, Query};
///
/// #[derive(Debug, Serialize, Deserialize)]
/// struct StructFoo {
///     foo: String,
/// }
///
/// #[get("/foo-struct")]
/// async fn struct_foo(req: Query<StructFoo>, _: RouteContext<()>) -> Result<Response> {
///     // works
///     let Foo { foo } = req.into_inner();
///     // rest code
/// }
///
/// #[derive(Debug, Serialize, Deserialize)]
/// struct TupleFoo(String);
///
/// #[get("/foo-tuple")]
/// async fn tuple_foo(req: Query<TupleFoo>, _: RouteContext<()>) -> Result<Response> {
///     // you won't even get here
///     let TupleFoo ( foo ) = req.into_inner();
///     // rest code
/// }
///
/// ```
///
/// # Notes
/// Request can be an ommited from the parameter too.
/// When ommitting either of them, the sequence must always be in the correct order.
///
/// The correct orders are:
/// - (`Request`, `RouteContext<D>`)
/// - (`Query<T>`, `RouteContext<D>`)
/// - (`Query<T>`, `Request`, `RouteContext<D>`)
///
/// ```
/// use serde::{Deserialize, Serialize};
/// use worker::{Response, Result, RouteContext};
/// use worker_route::{get, Query};
///
/// #[derive(Debug, Serialize, Deserialize)]
/// struct Foo {
///     foo: String,
/// }
///
/// #[get("/foo-query")]
/// async fn without_req(req: Query<Foo>, _: RouteContext<()>) -> Result<Response> {
///     // rest code
/// }
///
/// #[get("/foo-with-request")]
/// async fn with_request(req: Query<Foo>, _: Request, _: RouteContext<()>) -> Result<Response> {
///     // rest code
/// }
/// ```
///
pub struct Query<T>(T);
impl<T> Query<T>
where
    T: for<'a> Deserialize<'a> + Debug,
{
    /// Acess the owned `T`
    pub fn into_inner(self) -> T {
        self.0
    }

    fn collect_fields<D>(fields: &'static [&'static str], ctx: &RouteContext<D>) -> Vec<String> {
        let mut map = Vec::with_capacity(fields.len());
        for i in fields {
            if let Some(p) = ctx.param(i) {
                map.push(format!("{}={}", i.trim(), p.trim()));
            }
        }

        map
    }

    #[allow(unused)]
    // method is unusedf or now
    fn new<D>(method: Option<Method>, req: &Request, ctx: &RouteContext<D>) -> Result<Self, Error> {
        // get the fields from the supplied <T> which is a struct
        // struct Foo { name: String, age: usize }
        // ["name", "age"]
        let fields = struct_fields::<T>();

        // NOTE: it should not panic by unwrapping it for whatever reason
        // wouldn't even get this far if it panics because it would have done much earlier
        let url = req.url().unwrap();

        // get fields from path first
        // "/my_path/:name/:age"
        let mut paths = Self::collect_fields(fields, ctx);

        // if the given route is "/my_path/{some_params}" then paths.len() should be empty
        // or if the given route is "/my_path/:name/{some_optional_params}"
        // then we try getting them from the url instead
        if paths.len() != fields.len() {
            if let Some(query) = url.query() {
                paths.push(query.to_owned())
            }
        }

        // ["name=Foo", "age=20"] becomes "name=Foo&age=20"
        let queries = paths.join("&");
        let params = serde_qs::from_str::<T>(&queries)?;

        Ok(Self(params))
    }

    /// Deserialize the given `T` from the URL query string.
    ///
    /// ```
    /// use serde::{Deserialize, Serialize};
    /// use worker::{console_log, Request, Response, Result, RouteContext};
    /// use worker_route::{get, Query};
    ///
    /// #[derive(Debug, Deserialize, Serialize)]
    /// struct Person {
    ///     name: String,
    ///     age: usize,
    /// }
    ///
    /// #[get("/persons/:name/:age")]
    /// async fn person(req: Request, ctx: RouteContext<()>) -> Result<Response> {
    ///     let person = Query::<Person>::from(&req, &ctx);
    ///     let Person { name, age } = person.unwrap().into_inner();
    ///     console_log!("name: {name}, age: {age}");
    ///     Response::empty()
    /// }
    ///
    /// ```
    pub fn from<D>(req: &Request, ctx: &RouteContext<D>) -> Result<Self, Error> {
        Self::new(None, req, ctx)
    }

    // used by macro, do not call this when constructing the query manually
    #[doc(hidden)]
    pub fn from_method<D>(
        method: Method,
        req: &Request,
        ctx: &RouteContext<D>,
    ) -> Result<Self, Error> {
        Self::new(Some(method), req, ctx)
    }
}