containers_api/
opts.rs

1/// Types that implement Filter can be used in filter queries.
2pub 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]
37/// Implements methods to set a parameter of a specified type serialized as JSON.
38macro_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]
54/// Implements methods to set a specified parameter that contains a seuquence of items serialized as JSON.
55macro_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]
85/// Implements methods to set a string parameter serialized as JSON.
86macro_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]
102/// Implements methods to set a urlencoded string enum parameter.
103macro_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]
119/// Implements methods to set a urlencoded string parameter.
120macro_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]
136/// Implements methods to set a urlencoded parameter of a specified type.
137macro_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]
152/// Implements methods to set a urlencoded parameter of a sequence of items.
153macro_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]
171/// Implements methods to set a urlencoded parameter of a boolean.
172macro_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]
187/// Implements methods to set a urlencoded enum parameter.
188macro_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]
204/// Implements methods to set a urlencoded squence of key:value items.
205macro_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]
230/// Implements a filter method that uses a [`Filter`](crate::opts::Filter) trait parameter
231macro_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            // structure is a a json encoded object mapping string keys to a list
245            // of string values
246            self.params
247                .insert("filters", serde_json::to_string(&param).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                /// Serialize options as a URL query String. Returns None if no options are defined.
259                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                /// Serialize options as a JSON String. Returns an error if the options will fail
288                /// to serialize.
289                pub fn serialize(&self) -> crate::Result<String> {
290                    serde_json::to_string(&self.params).map_err(crate::Error::from)
291                }
292
293                /// Serialize options as a JSON bytes. Returns an error if the options will fail
294                /// to serialize.
295                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]
305/// Initialize a `Opts` struct with a `OptsBuilder` struct to construct it.
306macro_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]
347/// Initialize a `Opts` struct with a `OptsBuilder` struct to construct it.
348macro_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]
404/// Initialize a `Opts` struct with a required parameter and `OptsBuilder` struct to construct it.
405macro_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}