jsonapi_rs/
query.rs

1use queryst::parse;
2use std::collections::HashMap;
3use serde_json::value::Value;
4
5#[derive(Debug, PartialEq, Clone, Copy)]
6pub struct PageParams {
7    pub size: i64,
8    pub number: i64,
9}
10
11/// JSON-API Query parameters
12#[derive(Clone, Debug, PartialEq, Default)]
13pub struct Query {
14    pub _type: String,
15    pub include: Option<Vec<String>>,
16    pub fields: Option<HashMap<String, Vec<String>>>,
17    pub page: Option<PageParams>,
18    pub sort: Option<Vec<String>>,
19    pub filter: Option<HashMap<String, Vec<String>>>
20}
21
22//
23// Helper functions to break down the cyclomatic complexity of parameter parsing
24//
25
26fn ok_params_include(o:&Value) -> Option<Vec<String>> {
27    match o.pointer("/include") {
28        None => None,
29        Some(inc) => {
30            match inc.as_str() {
31                None => None,
32                Some(include_str) => {
33                    let arr: Vec<String> =
34                        include_str.split(',').map(|s| s.to_string()).collect();
35                    Some(arr)
36                }
37            }
38        }
39    }
40}
41
42fn ok_params_fields(o:&Value) -> HashMap<String, Vec<String>> {
43    let mut fields = HashMap::<String, Vec<String>>::new();
44
45    if let Some(x) = o.pointer("/fields") {
46        if x.is_object() {
47            if let Some(obj) = x.as_object() {
48                for (key, value) in obj.iter() {
49                    let arr: Vec<String> = match value.as_str() {
50                        Some(string) => {
51                            string.split(',').map(|s| s.to_string()).collect()
52                        }
53                        None => Vec::<String>::new(),
54                    };
55                    fields.insert(key.to_string(), arr);
56
57                }
58            }
59        } else {
60            warn!("Query::from_params : No fields found in {:?}", x);
61        }
62    }
63
64    fields
65}
66
67fn ok_params_sort(o:&Value) -> Option<Vec<String>> {
68    match o.pointer("/sort") {
69        None => None,
70        Some(sort) => {
71            match sort.as_str() {
72                None => None,
73                Some(sort_str) => {
74                    let arr: Vec<String> =
75                        sort_str.split(',').map(|s| s.to_string()).collect();
76                    Some(arr)
77                }
78            }
79        }
80    }
81}
82
83fn ok_params_filter(o:&Value) -> Option<HashMap<String, Vec<String>>> {
84    match o.pointer("/filter") {
85        None => None,
86        Some(x) => {
87            if x.is_object() {
88                let mut tmp_filter = HashMap::<String, Vec<String>>::new();
89                if let Some(obj) = x.as_object() {
90                    for (key, value) in obj.iter() {
91                        let arr: Vec<String> = match value.as_str() {
92                            Some(string) => {
93                                string.split(',').map(|s| s.to_string()).collect()
94                            }
95                            None => Vec::<String>::new(),
96                        };
97                        tmp_filter.insert(key.to_string(), arr);
98                    }
99                }
100                Some(tmp_filter)
101            } else {
102                warn!("Query::from_params : No filter found in {:?}", x);
103                None
104            }
105        }
106    }
107}
108
109fn ok_params_page(o:&Value) -> PageParams {
110    PageParams {
111        number: match o.pointer("/page/number") {
112            None => {
113                warn!(
114                    "Query::from_params : No page/number found in {:?}, setting \
115                                   default 0",
116                                   o
117                );
118                0
119            }
120            Some(num) => {
121                if num.is_string() {
122                    match num.as_str().map(str::parse::<i64>) {
123                        Some(y) => y.unwrap_or(0),
124                        None => {
125                            warn!(
126                                "Query::from_params : page/number found in {:?}, \
127                                               not able not able to parse it - setting default 0",
128                                               o
129                            );
130                            0
131                        }
132                    }
133                } else {
134                    warn!(
135                        "Query::from_params : page/number found in {:?}, but it is \
136                                       not an expected type - setting default 0",
137                                       o
138                    );
139                    0
140                }
141            }
142        },
143        size: match o.pointer("/page/size") {
144            None => {
145                warn!(
146                    "Query::from_params : No page/size found in {:?}, setting \
147                                   default 0",
148                                   o
149                );
150                0
151            }
152            Some(num) => {
153                if num.is_string() {
154                    match num.as_str().map(str::parse::<i64>) {
155                        Some(y) => y.unwrap_or(0),
156                        None => {
157                            warn!(
158                                "Query::from_params : page/size found in {:?}, \
159                                               not able not able to parse it - setting default 0",
160                                               o
161                            );
162                            0
163                        }
164                    }
165                } else {
166                    warn!(
167                        "Query::from_params : page/size found in {:?}, but it is \
168                                       not an expected type - setting default 0",
169                                       o
170                    );
171                    0
172                }
173            }
174        },
175    }
176}
177
178fn ok_params(o:Value) -> Query {
179    Query {
180        _type: "none".into(),
181        include : ok_params_include(&o),
182        fields: Some(ok_params_fields(&o)),
183        page: Some(ok_params_page(&o)),
184        sort: ok_params_sort(&o),
185        filter: ok_params_filter(&o),
186    }
187}
188
189/// JSON-API Query parameters
190impl Query {
191    ///
192    /// Takes a query parameter string and returns a Query
193    ///
194    /// ```
195    /// use jsonapi_rs::query::Query;
196    /// let query = Query::from_params("include=author&fields[articles]=title,\
197    ///                                 body&fields[people]=name&page[number]=3&page[size]=1");
198    /// match query.include {
199    ///     None => assert!(false),
200    ///     Some(include) => {
201    ///         assert_eq!(include.len(), 1);
202    ///         assert_eq!(include[0], "author");
203    ///     }
204    /// }
205    ///
206    /// ```
207    pub fn from_params(params: &str) -> Self {
208
209        match parse(params) {
210            Ok(o) => {
211                ok_params(o)
212            }
213            Err(err) => {
214                warn!("Query::from_params : Can't parse : {:?}", err);
215                Query {
216                    _type: "none".into(),
217                    ..Default::default()
218                }
219            }
220        }
221    }
222
223    ///
224    /// Builds a query parameter string from a Query
225    ///
226    /// ```
227    /// use jsonapi_rs::query::{Query, PageParams};
228    /// let query = Query {
229    ///   _type: "post".into(),
230    ///   include: Some(vec!["author".into()]),
231    ///   fields: None,
232    ///   page: Some(PageParams {
233    ///     size: 5,
234    ///     number: 10,
235    ///   }),
236    ///   sort: None,
237    ///   filter: None,
238    /// };
239    ///
240    /// let query_string = query.to_params();
241    /// assert_eq!(query_string, "include=author&page[size]=5&page[number]=10");
242    ///
243    /// ```
244    pub fn to_params(&self) -> String {
245        let mut params = Vec::<String>::new();
246
247        if let Some(ref include) = self.include {
248            params.push(format!("include={}", include.join(",")));
249        }
250
251        // Examples from json-api.org,
252        // fields[articles]=title,body,author&fields[people]=name
253        // fields[articles]=title,body&fields[people]=name
254
255        if let Some(ref fields) = self.fields {
256            for (name, val) in fields.iter() {
257                params.push(format!("fields[{}]={}", name, val.join(",")));
258            }
259        }
260
261        if let Some(ref sort) = self.sort {
262            params.push(format!("sort={}", sort.join(",")))
263        }
264
265        if let Some(ref filter) = self.filter {
266            for (name, val) in filter.iter() {
267                params.push(format!("filter[{}]={}", name, val.join(",")));
268            }
269        }
270
271        if let Some(ref page) = self.page {
272            params.push(page.to_params());
273        }
274
275        params.join("&")
276    }
277}
278
279impl PageParams {
280    pub fn to_params(&self) -> String {
281        format!("page[size]={}&page[number]={}", self.size, self.number)
282    }
283}