1use reqwest::Client;
2
3use super::{CensusRequest, Filter, FilterType, Join, Sort, SortDirection, Tree};
4
5pub struct CensusRequestBuilder {
6 client: Client,
7 collection: String,
8 url: String,
9 show: Option<Vec<String>>,
10 hide: Option<Vec<String>>,
11 sort: Option<Vec<Sort>>,
12 has: Option<Vec<String>>,
13 resolve: Option<Vec<String>>,
14 case: Option<bool>,
15 limit: Option<u32>,
16 limit_per_db: Option<u32>,
17 start: Option<u32>,
18 include_null: Option<bool>,
19 lang: Option<String>,
20 join: Option<Vec<Join>>,
21 tree: Option<Vec<Tree>>,
22 timing: Option<bool>,
23 exact_match_first: Option<bool>,
24 distinct: Option<String>,
25 retry: Option<bool>,
26 filters: Option<Vec<Filter>>,
27}
28
29impl CensusRequestBuilder {
30 pub fn new(client: Client, collection: String, url: String) -> Self {
31 Self {
32 client,
33 collection,
34 url,
35 show: None,
36 hide: None,
37 sort: None,
38 has: None,
39 resolve: None,
40 case: None,
41 limit: None,
42 limit_per_db: None,
43 start: None,
44 include_null: None,
45 lang: None,
46 join: None,
47 tree: None,
48 timing: None,
49 exact_match_first: None,
50 distinct: None,
51 retry: None,
52 filters: None,
53 }
54 }
55
56 pub fn show(mut self, field: impl Into<String>) -> Self {
57 match &mut self.show {
58 None => {
59 self.show = Some(vec![field.into()]);
60 }
61 Some(show) => {
62 show.push(field.into());
63 }
64 }
65
66 self
67 }
68
69 pub fn hide(mut self, field: impl Into<String>) -> Self {
70 match &mut self.hide {
71 None => {
72 self.hide = Some(vec![field.into()]);
73 }
74 Some(hide) => {
75 hide.push(field.into());
76 }
77 }
78
79 self
80 }
81
82 pub fn sort(mut self, field: impl Into<String>, direction: SortDirection) -> Self {
83 match &mut self.sort {
84 None => {
85 self.sort = Some(vec![Sort {
86 field: field.into(),
87 direction,
88 }]);
89 }
90 Some(sort) => {
91 sort.push(Sort {
92 field: field.into(),
93 direction,
94 });
95 }
96 }
97
98 self
99 }
100
101 pub fn has(mut self, field: impl Into<String>) -> Self {
102 match &mut self.has {
103 None => {
104 self.has = Some(vec![field.into()]);
105 }
106 Some(has) => {
107 has.push(field.into());
108 }
109 }
110
111 self
112 }
113
114 pub fn resolve(mut self, field: impl Into<String>) -> Self {
115 match &mut self.resolve {
116 None => {
117 self.resolve = Some(vec![field.into()]);
118 }
119 Some(resolve) => {
120 resolve.push(field.into());
121 }
122 }
123
124 self
125 }
126
127 pub fn case(mut self, should_case: bool) -> Self {
128 self.case = Some(should_case);
129
130 self
131 }
132
133 pub fn limit(mut self, limit: u32) -> Self {
134 self.limit = Some(limit);
135
136 self
137 }
138
139 pub fn limit_per_db(mut self, limit_per_db: u32) -> Self {
140 self.limit_per_db = Some(limit_per_db);
141
142 self
143 }
144
145 pub fn start(mut self, start: u32) -> Self {
146 self.start = Some(start);
147
148 self
149 }
150
151 pub fn include_null(mut self, include_null: bool) -> Self {
152 self.include_null = Some(include_null);
153
154 self
155 }
156
157 pub fn lang(mut self, lang: impl Into<String>) -> Self {
158 self.lang = Some(lang.into());
159
160 self
161 }
162
163 pub fn join(mut self, join: Join) -> Self {
164 match &mut self.join {
165 None => {
166 self.join = Some(vec![join]);
167 }
168 Some(joins) => {
169 joins.push(join);
170 }
171 }
172
173 self
174 }
175
176 pub fn tree(mut self, tree: Tree) -> Self {
177 match &mut self.tree {
178 None => {
179 self.tree = Some(vec![tree]);
180 }
181 Some(trees) => {
182 trees.push(tree);
183 }
184 }
185
186 self
187 }
188
189 pub fn timing(mut self, value: bool) -> Self {
190 self.timing = Some(value);
191
192 self
193 }
194
195 pub fn exact_match_first(mut self, value: bool) -> Self {
196 self.exact_match_first = Some(value);
197
198 self
199 }
200
201 pub fn distinct(mut self, field: impl Into<String>) -> Self {
202 self.distinct = Some(field.into());
203
204 self
205 }
206
207 pub fn retry(mut self, value: bool) -> Self {
208 self.retry = Some(value);
209
210 self
211 }
212
213 pub fn filter(
214 mut self,
215 field: impl Into<String>,
216 filter: FilterType,
217 value: impl Into<String>,
218 ) -> Self {
219 let filter = Filter {
220 field: field.into(),
221 filter,
222 value: value.into(),
223 };
224
225 match &mut self.filters {
226 None => {
227 self.filters = Some(vec![filter]);
228 }
229 Some(filters) => {
230 filters.push(filter);
231 }
232 }
233
234 self
235 }
236
237 pub fn build(self) -> CensusRequest {
238 let mut query_params = Vec::new();
239
240 match self.filters {
241 None => {}
242 Some(filters) => {
243 for filter in filters {
244 query_params.push(filter.into_pair());
245 }
246 }
247 }
248
249 match self.show {
250 None => {}
251 Some(show) => {
252 let fields = show.join(",");
253 query_params.push(("c:show".to_string(), fields));
254 }
255 }
256
257 match self.hide {
258 None => {}
259 Some(hide) => {
260 let fields = hide.join(",");
261 query_params.push(("c:hide".to_string(), fields));
262 }
263 }
264
265 match self.sort {
266 None => {}
267 Some(sort) => {
268 let fields: String = sort
269 .iter()
270 .map(|field_sort| {
271 format!(
272 "{}:{}",
273 field_sort.field,
274 <SortDirection as Into<&'static str>>::into(
275 field_sort.direction.clone()
276 )
277 )
278 })
279 .collect::<Vec<String>>()
280 .join(",");
281
282 query_params.push(("c:sort".to_string(), fields));
283 }
284 }
285
286 match self.has {
287 None => {}
288 Some(has) => {
289 query_params.push(("c:has".to_string(), has.join(",")));
290 }
291 }
292
293 match self.resolve {
294 None => {}
295 Some(resolve) => {
296 query_params.push(("c:resolve".to_string(), resolve.join(",")));
297 }
298 }
299
300 match self.case {
301 None => {}
302 Some(case) => {
303 query_params.push(("c:case".to_string(), case.to_string()));
304 }
305 }
306
307 match self.limit {
308 None => {}
309 Some(limit) => {
310 query_params.push(("c:limit".to_string(), limit.to_string()));
311 }
312 }
313
314 match self.limit_per_db {
315 None => {}
316 Some(limit_per_db) => {
317 query_params.push(("c:limitPerDB".to_string(), limit_per_db.to_string()));
318 }
319 }
320
321 match self.start {
322 None => {}
323 Some(start) => {
324 query_params.push(("c:start".to_string(), start.to_string()));
325 }
326 }
327
328 match self.include_null {
329 None => {}
330 Some(include_null) => {
331 query_params.push(("c:includeNull".to_string(), include_null.to_string()));
332 }
333 }
334
335 match self.lang {
336 None => {}
337 Some(lang) => {
338 query_params.push(("c:lang".to_string(), lang));
339 }
340 }
341
342 match self.timing {
343 None => {}
344 Some(timing) => {
345 query_params.push(("c:timing".to_string(), timing.to_string()));
346 }
347 }
348
349 match self.exact_match_first {
350 None => {}
351 Some(exact_match_first) => {
352 query_params.push((
353 "c:exactMatchFirst".to_string(),
354 exact_match_first.to_string(),
355 ));
356 }
357 }
358
359 match self.distinct {
360 None => {}
361 Some(distinct) => {
362 query_params.push(("c:distinct".to_string(), distinct));
363 }
364 }
365
366 match self.retry {
367 None => {}
368 Some(retry) => {
369 query_params.push(("c:retry".to_string(), retry.to_string()));
370 }
371 }
372
373 match self.join {
374 None => {}
375 Some(joins) => {
376 let joins: Vec<String> = joins.iter().map(|join| join.into()).collect();
377
378 query_params.push(("c:join".to_string(), joins.join(",")));
379 }
380 }
381
382 CensusRequest {
385 client: self.client,
386 collection: self.collection,
387 url: self.url,
388 query_params,
389 }
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use reqwest::Client;
396
397 use super::CensusRequestBuilder;
398 use crate::api::request::FilterType;
399
400 #[test]
401 fn build_stores_query_params_for_encoded_request_building() {
402 let request = CensusRequestBuilder::new(
403 Client::new(),
404 "character".to_string(),
405 "https://example.com/get/ps2:v2/character".to_string(),
406 )
407 .show("name.first")
408 .filter("name.first_lower", FilterType::StartsWith, "te st")
409 .build();
410
411 let url = request
412 .client
413 .get(request.url.clone())
414 .query(&request.query_params)
415 .build()
416 .expect("request should build")
417 .url()
418 .to_string();
419
420 assert!(url.contains("c%3Ashow=name.first"));
421 assert!(url.contains("name.first_lower=%5Ete+st"));
422 }
423}