1pub trait Filter {
3 fn query_item(&self) -> FilterItem;
4}
5
6pub struct FilterItem {
7 key: &'static str,
8 value: String,
9}
10
11impl FilterItem {
12 pub fn new(key: &'static str, value: impl Into<String>) -> Self {
13 Self {
14 key,
15 value: value.into(),
16 }
17 }
18
19 pub fn key(&self) -> &'static str {
20 self.key
21 }
22}
23
24impl std::fmt::Display for FilterItem {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 write!(f, "{}", self.value)
27 }
28}
29
30impl From<(&'static str, String)> for FilterItem {
31 fn from(it: (&'static str, String)) -> Self {
32 Self::new(it.0, it.1)
33 }
34}
35
36#[macro_export]
37macro_rules! impl_field {
39 ($(#[doc = $docs:expr])* $name:ident: $ty:ty => $param_name:literal) => {
40 paste::item! {
41 $(
42 #[doc= $docs]
43 )*
44 pub fn [< $name >](mut self, $name: $ty)-> Self
45 {
46 self.params.insert($param_name, serde_json::json!($name));
47 self
48 }
49 }
50 };
51}
52
53#[macro_export]
54macro_rules! impl_vec_field {
56 ($(#[doc = $docs:expr])* $name:ident => $param_name:literal) => {
57 paste::item! {
58 $(
59 #[doc= $docs]
60 )*
61 pub fn [< $name >]<S>(mut self, $name: impl IntoIterator<Item = S>)-> Self
62 where
63 S: serde::Serialize
64 {
65 self.params.insert($param_name, serde_json::json!($name.into_iter().collect::<Vec<_>>()));
66 self
67 }
68 }
69 };
70 ($(#[doc = $docs:expr])* $name:ident: $ty:ty => $param_name:literal) => {
71 paste::item! {
72 $(
73 #[doc= $docs]
74 )*
75 pub fn [< $name >](mut self, $name: impl IntoIterator<Item = $ty>)-> Self
76 {
77 self.params.insert($param_name, serde_json::json!($name.into_iter().collect::<Vec<_>>()));
78 self
79 }
80 }
81 };
82}
83
84#[macro_export]
85macro_rules! impl_str_field {
87 ($(#[doc = $docs:expr])* $name:ident => $param_name:literal) => {
88 paste::item! {
89 $(
90 #[doc= $docs]
91 )*
92 pub fn [< $name >](mut self, $name: impl serde::Serialize)-> Self
93 {
94 self.params.insert($param_name, serde_json::json!($name));
95 self
96 }
97 }
98 };
99}
100
101#[macro_export]
102macro_rules! impl_str_enum_field {
104 ($(#[doc = $docs:expr])* $name:ident: $ty:tt => $param_name:literal) => {
105 paste::item! {
106 $(
107 #[doc= $docs]
108 )*
109 pub fn [< $name >](mut self, $name: $ty)-> Self
110 {
111 self.params.insert($param_name, serde_json::json!($name.to_string()));
112 self
113 }
114 }
115 };
116}
117
118#[macro_export]
119macro_rules! impl_url_str_field {
121 ($(#[doc = $docs:expr])* $name:ident => $param_name:literal) => {
122 paste::item! {
123 $(
124 #[doc= $docs]
125 )*
126 pub fn [< $name >](mut self, $name: impl Into<String>)-> Self
127 {
128 self.params.insert($param_name, $name.into());
129 self
130 }
131 }
132 };
133}
134
135#[macro_export]
136macro_rules! impl_url_field {
138 ($(#[doc = $docs:expr])* $name:ident : $ty:tt => $param_name:literal) => {
139 paste::item! {
140 $(
141 #[doc= $docs]
142 )*
143 pub fn [< $name >](mut self, $name: $ty)-> Self {
144 self.params.insert($param_name, $name.to_string());
145 self
146 }
147 }
148 };
149}
150
151#[macro_export]
152macro_rules! impl_url_vec_field {
154 ($(#[doc = $docs:expr])* $name:ident => $param_name:literal) => {
155 paste::item! {
156 $(
157 #[doc= $docs]
158 )*
159 pub fn [< $name >]<S>(mut self, $name: impl IntoIterator<Item = S>)-> Self
160 where
161 S: Into<String>
162 {
163 self.vec_params.insert($param_name, $name.into_iter().map(|s| s.into()).collect());
164 self
165 }
166 }
167 };
168}
169
170#[macro_export]
171macro_rules! impl_url_bool_field {
173 ($(#[doc = $docs:expr])* $name:ident => $param_name:literal) => {
174 paste::item! {
175 $(
176 #[doc= $docs]
177 )*
178 pub fn [< $name >](mut self, $name: bool)-> Self {
179 self.params.insert($param_name, $name.to_string());
180 self
181 }
182 }
183 };
184}
185
186#[macro_export]
187macro_rules! impl_url_enum_field {
189 ($(#[doc = $docs:expr])* $name:ident: $ty:tt => $param_name:literal) => {
190 paste::item! {
191 $(
192 #[doc= $docs]
193 )*
194 pub fn [< $name >](mut self, $name: $ty)-> Self
195 {
196 self.params.insert($param_name, $name.to_string());
197 self
198 }
199 }
200 };
201}
202
203#[macro_export]
204macro_rules! impl_map_field {
206 (url $(#[doc = $docs:expr])* $name:ident => $param_name:literal) => {
207 impl_map_field! { $(#[doc = $docs])* $name => $param_name => serde_json::to_string(&$name.into_iter().collect::<std::collections::HashMap<_, _>>()).unwrap_or_default() }
208 };
209 (json $(#[doc = $docs:expr])* $name:ident => $param_name:literal) => {
210 impl_map_field! { $(#[doc = $docs])* $name => $param_name => serde_json::json!($name.into_iter().collect::<std::collections::HashMap<_, _>>()) }
211 };
212 ($(#[doc = $docs:expr])* $name:ident => $param_name:literal => $ret:expr) => {
213 paste::item! {
214 $(
215 #[doc= $docs]
216 )*
217 pub fn [< $name >]<K, V>(mut self, $name: impl IntoIterator<Item = (K, V)>)-> Self
218 where
219 K: serde::Serialize + Eq + std::hash::Hash,
220 V: serde::Serialize
221 {
222 self.params.insert($param_name, $ret);
223 self
224 }
225 }
226 };
227}
228
229#[macro_export]
230macro_rules! impl_filter_func {
232 ($(#[doc = $doc:expr])* $filter_ty:ident) => {
233 $(
234 #[doc = $doc]
235 )*
236 pub fn filter(mut self, filters: impl IntoIterator<Item = $filter_ty>) -> Self
237 {
238 let mut param = std::collections::BTreeMap::new();
239 for filter_item in filters.into_iter().map(|f| f.query_item()) {
240 let key = filter_item.key();
241 let entry_vec = param.entry(key).or_insert(Vec::new());
242 entry_vec.push(filter_item.to_string());
243 }
244 self.params
247 .insert("filters", serde_json::to_string(¶m).unwrap_or_default());
248 self
249 }
250 };
251}
252
253#[macro_export]
254macro_rules! impl_url_serialize {
255 ($name: ident) => {
256 paste::item! {
257 impl [< $name Opts >] {
258 pub fn serialize(&self) -> Option<String> {
260 let mut serialized = $crate::url::encoded_pairs(&self.params);
261 let vec_p = $crate::url::encoded_vec_pairs(&self.vec_params);
262
263 if !vec_p.is_empty() {
264 if !serialized.is_empty() {
265 serialized.push('&');
266 }
267 serialized.push_str(&vec_p);
268 }
269
270 if serialized.is_empty() {
271 None
272 } else {
273 Some(serialized)
274 }
275 }
276 }
277 }
278 };
279}
280
281#[allow(clippy::crate_in_macro_def)]
282#[macro_export]
283macro_rules! impl_json_serialize {
284 ($name: ident) => {
285 paste::item! {
286 impl [< $name Opts >] {
287 pub fn serialize(&self) -> crate::Result<String> {
290 serde_json::to_string(&self.params).map_err(crate::Error::from)
291 }
292
293 pub fn serialize_vec(&self) -> crate::Result<Vec<u8>> {
296 serde_json::to_vec(&self.params).map_err(crate::Error::from)
297 }
298 }
299 }
300 };
301}
302
303#[allow(clippy::crate_in_macro_def)]
304#[macro_export]
305macro_rules! define_opts_builder {
307 (base_json $(#[doc = $docs:expr])* $name:ident $ty:expr) => {
308 paste::item! {
309 $(
310 #[doc= $docs]
311 )*
312 #[derive(serde::Serialize, Debug, Default, Clone)]
313 pub struct [< $name Opts >] {
314 pub(crate) params: std::collections::BTreeMap<&'static str, $ty>,
315 }
316
317 #[doc = concat!("A builder struct for ", stringify!($name), "Opts.")]
318 #[derive(Default, Debug, Clone)]
319 pub struct [< $name OptsBuilder >] {
320 pub(crate) params: std::collections::BTreeMap<&'static str, $ty>,
321 }
322 }
323 };
324 (base_url $(#[doc = $docs:expr])* $name:ident $ty:expr) => {
325 paste::item! {
326 $(
327 #[doc= $docs]
328 )*
329 #[derive(serde::Serialize, Debug, Default, Clone)]
330 pub struct [< $name Opts >] {
331 pub(crate) params: std::collections::BTreeMap<&'static str, $ty>,
332 pub(crate) vec_params: std::collections::BTreeMap<&'static str, Vec<$ty>>,
333 }
334
335 #[doc = concat!("A builder struct for ", stringify!($name), "Opts.")]
336 #[derive(Default, Debug, Clone)]
337 pub struct [< $name OptsBuilder >] {
338 pub(crate) params: std::collections::BTreeMap<&'static str, $ty>,
339 pub(crate) vec_params: std::collections::BTreeMap<&'static str, Vec<$ty>>,
340 }
341 }
342 }
343}
344
345#[allow(clippy::crate_in_macro_def)]
346#[macro_export]
347macro_rules! impl_opts_builder {
349 (__builder $name:ident) => {
350 paste::item! {
351 impl [< $name Opts >] {
352 #[doc = concat!("Returns a new instance of a builder for ", stringify!($name), "Opts.")]
353 pub fn builder() -> [< $name OptsBuilder >] {
354 [< $name OptsBuilder >]::default()
355 }
356 }
357 }
358 };
359 (base_json $(#[doc = $docs:expr])* $name:ident $ty:expr) => {
360 $crate::define_opts_builder!(base_json $(#[doc = $docs])* $name $ty);
361 impl_opts_builder!(__builder $name);
362 paste::item! {
363 impl [< $name OptsBuilder >] {
364 #[doc = concat!("Finish building ", stringify!($name), "Opts.")]
365 pub fn build(self) -> [< $name Opts >] {
366 [< $name Opts >] {
367 params: self.params,
368 }
369 }
370 }
371 }
372 };
373 (base_url $(#[doc = $docs:expr])* $name:ident $ty:expr) => {
374 $crate::define_opts_builder!(base_url $(#[doc = $docs])* $name $ty);
375 impl_opts_builder!(__builder $name);
376 paste::item! {
377 impl [< $name OptsBuilder >] {
378 #[doc = concat!("Finish building ", stringify!($name), "Opts.")]
379 pub fn build(self) -> [< $name Opts >] {
380 [< $name Opts >] {
381 params: self.params,
382 vec_params: self.vec_params
383 }
384 }
385 }
386 }
387 };
388 (json => $(#[doc = $docs:expr])* $name:ident) => {
389 paste::item! {
390 impl_opts_builder!(base_json $(#[doc = $docs])* $name serde_json::Value);
391 $crate::impl_json_serialize!($name);
392 }
393 };
394 (url => $(#[doc = $docs:expr])* $name:ident) => {
395 paste::item! {
396 impl_opts_builder!(base_url $(#[doc = $docs])* $name String);
397 $crate::impl_url_serialize!($name);
398 }
399 };
400}
401
402#[allow(clippy::crate_in_macro_def)]
403#[macro_export]
404macro_rules! impl_opts_required_builder {
406 (__builder $name:ident $ty:expr; $(#[doc = $param_docs:expr])* $param:ident: $param_ty:expr) => {
407 paste::item! {
408 impl [< $name Opts >] {
409 #[doc = concat!("Returns a new instance of a builder for ", stringify!($name), "Opts.")]
410 $(
411 #[doc= $param_docs]
412 )*
413 pub fn builder($param: impl Into<$param_ty>) -> [< $name OptsBuilder >] {
414 [< $name OptsBuilder >]::new($param)
415 }
416
417 pub fn get_param(&self, key: &str) -> Option<&$ty> {
418 self.params.get(key)
419 }
420 }
421 }
422 };
423 (base_json $(#[doc = $docs:expr])* $name:ident, $(#[doc = $param_docs:expr])* $param:ident: $param_ty:expr => $param_key:literal) => {
424 impl_opts_required_builder!(__builder $name serde_json::Value; $(#[doc = $param_docs])* $param: $param_ty);
425 paste::item! {
426 $(
427 #[doc= $docs]
428 )*
429 #[derive(serde::Serialize, Debug, Default, Clone)]
430 pub struct [< $name Opts >] {
431 pub(crate) params: std::collections::BTreeMap<&'static str, serde_json::Value>,
432 [< $param >]: $param_ty,
433 }
434 impl [< $name Opts >] {
435 pub fn [< $param >](&self) -> &$param_ty {
436 &self.$param
437 }
438 }
439
440 #[doc = concat!("A builder struct for ", stringify!($name), "Opts.")]
441 #[derive(Default, Debug, Clone)]
442 pub struct [< $name OptsBuilder >] {
443 pub(crate) params: std::collections::BTreeMap<&'static str, serde_json::Value>,
444 [< $param >]: $param_ty,
445 }
446 impl [< $name OptsBuilder >] {
447 #[doc = concat!("A builder struct for ", stringify!($name), "Opts.")]
448 $(
449 #[doc= $param_docs]
450 )*
451 pub fn new($param: impl Into<$param_ty>) -> Self {
452 let param = $param.into();
453 Self {
454 params: [($param_key, serde_json::json!(param.clone()))].into(),
455 [< $param >]: param,
456 }
457 }
458
459 #[doc = concat!("Finish building ", stringify!($name), "Opts.")]
460 pub fn build(self) -> [< $name Opts >] {
461 [< $name Opts >] {
462 params: self.params,
463 [< $param >]: self.$param
464 }
465 }
466 }
467 }
468 };
469 (base_url $(#[doc = $docs:expr])* $name:ident, $(#[doc = $param_docs:expr])* $param:ident: $param_ty:expr => $param_key:literal) => {
470 impl_opts_required_builder!(__builder $name String; $(#[doc = $param_docs])* $param: $param_ty);
471 paste::item! {
472 $(
473 #[doc= $docs]
474 )*
475 #[derive(serde::Serialize, Debug, Default, Clone)]
476 pub struct [< $name Opts >] {
477 pub(crate) params: std::collections::BTreeMap<&'static str, String>,
478 pub(crate) vec_params: std::collections::BTreeMap<&'static str, Vec<String>>,
479 [< $param >]: $param_ty,
480 }
481 impl [< $name Opts >] {
482 pub fn [< $param >](&self) -> &$param_ty {
483 &self.$param
484 }
485 }
486
487 #[doc = concat!("A builder struct for ", stringify!($name), "Opts.")]
488 #[derive(Debug, Clone)]
489 pub struct [< $name OptsBuilder >] {
490 pub(crate) params: std::collections::BTreeMap<&'static str, String>,
491 pub(crate) vec_params: std::collections::BTreeMap<&'static str, Vec<String>>,
492 [< $param >]: $param_ty,
493 }
494
495 impl [< $name OptsBuilder >] {
496 #[doc = concat!("A builder struct for ", stringify!($name), "Opts.")]
497 $(
498 #[doc= $param_docs]
499 )*
500 pub fn new($param: impl Into<$param_ty>) -> Self {
501 let param = $param.into();
502 Self {
503 params: [($param_key, param.clone())].into(),
504 vec_params: Default::default(),
505 [< $param >]: param,
506 }
507 }
508
509 #[doc = concat!("Finish building ", stringify!($name), "Opts.")]
510 pub fn build(self) -> [< $name Opts >] {
511 [< $name Opts >] {
512 params: self.params,
513 vec_params: self.vec_params,
514 [< $param >]: self.$param,
515 }
516 }
517 }
518 }
519 };
520 (json => $(#[doc = $docs:expr])* $name:ident, $(#[doc = $param_docs:expr])* $param:ident: $param_ty:expr => $param_key:literal) => {
521 impl_opts_required_builder!(base_json $(#[doc = $docs])* $name, $(#[doc = $param_docs])* $param: $param_ty => $param_key);
522 $crate::impl_json_serialize!($name);
523 };
524 (json => $(#[doc = $docs:expr])* $name:ident, $(#[doc = $param_docs:expr])* $param:ident => $param_key:literal) => {
525 impl_opts_required_builder!(base_json $(#[doc = $docs])* $name, $(#[doc = $param_docs])* $param: serde_json::Value => $param_key);
526 $crate::impl_json_serialize!($name);
527 };
528 (url => $(#[doc = $docs:expr])* $name:ident, $(#[doc = $param_docs:expr])* $param:ident: $param_ty:expr => $param_key:literal) => {
529 impl_opts_required_builder!(base_url $(#[doc = $docs])* $name, $(#[doc = $param_docs])* $param => $param_key);
530 $crate::impl_url_serialize!($name);
531 };
532 (url => $(#[doc = $docs:expr])* $name:ident, $(#[doc = $param_docs:expr])* $param:ident => $param_key:literal) => {
533 impl_opts_required_builder!(base_url $(#[doc = $docs])* $name, $(#[doc = $param_docs])* $param: String => $param_key);
534 $crate::impl_url_serialize!($name);
535 };
536}
537
538#[cfg(test)]
539mod test {
540 use super::*;
541
542 #[test]
543 fn url_filter_query() {
544 pub enum ListFilter {
545 Id(crate::id::Id),
546 LabelKey(String),
547 LabelKeyVal(String, String),
548 }
549
550 impl Filter for ListFilter {
551 fn query_item(&self) -> FilterItem {
552 use ListFilter::*;
553 match &self {
554 Id(id) => FilterItem::new("id", id.to_string()),
555 LabelKey(key) => FilterItem::new("label", key.clone()),
556 LabelKeyVal(key, val) => FilterItem::new("label", format!("{key}={val}")),
557 }
558 }
559 }
560
561 impl_opts_builder! (url =>
562 UrlTest
563 );
564
565 impl UrlTestOptsBuilder {
566 impl_filter_func!(ListFilter);
567 }
568
569 let opts = UrlTestOpts::builder()
570 .filter([
571 ListFilter::Id("testid".into()),
572 ListFilter::LabelKey("test1".into()),
573 ListFilter::LabelKeyVal("test2".into(), "key".into()),
574 ])
575 .build();
576
577 let want = Some("filters=%7B%22id%22%3A%5B%22testid%22%5D%2C%22label%22%3A%5B%22test1%22%2C%22test2%3Dkey%22%5D%7D".into());
578 let got = opts.serialize();
579 assert_eq!(got, want);
580 }
581
582 #[test]
583 fn url_vec_query() {
584 impl_opts_builder! (url =>
585 UrlTest
586 );
587
588 impl UrlTestOptsBuilder {
589 impl_url_vec_field!(
590 test => "tests"
591 );
592 }
593
594 let opts = UrlTestOpts::builder().test(["abc", "def", "ghi"]).build();
595
596 let want = Some("tests=abc&tests=def&tests=ghi".into());
597 let got = opts.serialize();
598 assert_eq!(got, want);
599 }
600}