Skip to main content

bitculator/
params.rs

1/// A bag of query parameters. Keys mirror the API's names (snake_case); build it
2/// fluently:
3///
4/// ```
5/// use bitculator::Params;
6/// let p = Params::new().set("per_page", 50).set("sort", "-marketcap");
7/// ```
8#[derive(Debug, Clone, Default)]
9pub struct Params(Vec<(String, String)>);
10
11impl Params {
12    pub fn new() -> Self {
13        Params(Vec::new())
14    }
15
16    /// Set a parameter. A `None` value or empty slice is skipped; a repeated key
17    /// replaces the previous value.
18    pub fn set(mut self, key: &str, value: impl QueryValue) -> Self {
19        if let Some(v) = value.to_query() {
20            self.0.retain(|(k, _)| k != key);
21            self.0.push((key.to_string(), v));
22        }
23        self
24    }
25
26    pub fn is_empty(&self) -> bool {
27        self.0.is_empty()
28    }
29
30    pub(crate) fn pairs(&self) -> &[(String, String)] {
31        &self.0
32    }
33
34    pub(crate) fn with_page(&self, page: u32) -> Params {
35        self.clone().set("page", page)
36    }
37}
38
39/// Conversion into a query-string value. Returning `None` omits the parameter.
40pub trait QueryValue {
41    fn to_query(&self) -> Option<String>;
42}
43
44impl QueryValue for &str {
45    fn to_query(&self) -> Option<String> {
46        Some((*self).to_string())
47    }
48}
49
50impl QueryValue for String {
51    fn to_query(&self) -> Option<String> {
52        Some(self.clone())
53    }
54}
55
56impl QueryValue for bool {
57    fn to_query(&self) -> Option<String> {
58        Some(if *self { "true" } else { "false" }.to_string())
59    }
60}
61
62macro_rules! query_value_display {
63    ($($t:ty),*) => {
64        $(impl QueryValue for $t {
65            fn to_query(&self) -> Option<String> { Some(self.to_string()) }
66        })*
67    };
68}
69query_value_display!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64);
70
71impl<T: QueryValue> QueryValue for Option<T> {
72    fn to_query(&self) -> Option<String> {
73        self.as_ref().and_then(QueryValue::to_query)
74    }
75}
76
77impl QueryValue for Vec<&str> {
78    fn to_query(&self) -> Option<String> {
79        if self.is_empty() {
80            None
81        } else {
82            Some(self.join(","))
83        }
84    }
85}
86
87impl QueryValue for Vec<String> {
88    fn to_query(&self) -> Option<String> {
89        if self.is_empty() {
90            None
91        } else {
92            Some(self.join(","))
93        }
94    }
95}