1use std::borrow::Cow;
13
14use chrono::{DateTime, NaiveDate, Utc};
15use serde_json::Value;
16use url::Url;
17
18use crate::api::BodyError;
19
20pub trait ParamValue<'a> {
22 #[allow(clippy::wrong_self_convention)]
23 fn as_value(&self) -> Cow<'a, str>;
25}
26
27impl ParamValue<'static> for bool {
28 fn as_value(&self) -> Cow<'static, str> {
29 if *self {
30 "true".into()
31 } else {
32 "false".into()
33 }
34 }
35}
36
37impl<'a> ParamValue<'a> for &'a str {
38 fn as_value(&self) -> Cow<'a, str> {
39 (*self).into()
40 }
41}
42
43impl ParamValue<'static> for String {
44 fn as_value(&self) -> Cow<'static, str> {
45 self.clone().into()
46 }
47}
48
49impl<'a> ParamValue<'a> for &'a String {
50 fn as_value(&self) -> Cow<'a, str> {
51 (*self).into()
52 }
53}
54
55impl<'a> ParamValue<'a> for Cow<'a, str> {
56 fn as_value(&self) -> Cow<'a, str> {
57 self.clone()
58 }
59}
60
61impl<'a, 'b: 'a> ParamValue<'a> for &'b Cow<'a, str> {
62 fn as_value(&self) -> Cow<'a, str> {
63 (*self).clone()
64 }
65}
66
67impl ParamValue<'static> for u64 {
68 fn as_value(&self) -> Cow<'static, str> {
69 self.to_string().into()
70 }
71}
72
73impl ParamValue<'static> for f64 {
74 fn as_value(&self) -> Cow<'static, str> {
75 self.to_string().into()
76 }
77}
78
79impl ParamValue<'static> for DateTime<Utc> {
80 fn as_value(&self) -> Cow<'static, str> {
81 self.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
82 .into()
83 }
84}
85
86impl ParamValue<'static> for NaiveDate {
87 fn as_value(&self) -> Cow<'static, str> {
88 format!("{}", self.format("%Y-%m-%d")).into()
89 }
90}
91
92#[derive(Debug, Default, Clone)]
94pub struct FormParams<'a> {
95 params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
96}
97
98impl<'a> FormParams<'a> {
99 pub fn push<'b, K, V>(&mut self, key: K, value: V) -> &mut Self
101 where
102 K: Into<Cow<'a, str>>,
103 V: ParamValue<'b>,
104 'b: 'a,
105 {
106 self.params.push((key.into(), value.as_value()));
107 self
108 }
109
110 pub fn push_opt<'b, K, V>(&mut self, key: K, value: Option<V>) -> &mut Self
112 where
113 K: Into<Cow<'a, str>>,
114 V: ParamValue<'b>,
115 'b: 'a,
116 {
117 if let Some(value) = value {
118 self.params.push((key.into(), value.as_value()));
119 }
120 self
121 }
122
123 pub fn extend<'b, I, K, V>(&mut self, iter: I) -> &mut Self
125 where
126 I: Iterator<Item = (K, V)>,
127 K: Into<Cow<'a, str>>,
128 V: ParamValue<'b>,
129 'b: 'a,
130 {
131 self.params
132 .extend(iter.map(|(key, value)| (key.into(), value.as_value())));
133 self
134 }
135
136 pub fn into_body(self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
138 let body = serde_urlencoded::to_string(self.params)?;
139 Ok(Some((
140 "application/x-www-form-urlencoded",
141 body.into_bytes(),
142 )))
143 }
144}
145
146#[derive(Debug, Default, Clone)]
148#[non_exhaustive]
149pub struct JsonParams {}
150
151impl JsonParams {
152 pub fn clean(mut val: Value) -> Value {
156 if let Some(obj) = val.as_object_mut() {
157 obj.retain(|_, v| {
158 !v.is_null()
159 && v.as_array().map(|a| !a.is_empty()).unwrap_or(true)
160 && v.as_object().map(|o| !o.is_empty()).unwrap_or(true)
161 });
162 }
163
164 val
165 }
166
167 pub fn into_body(input: &Value) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
169 let body = serde_json::to_string(input)?;
170 Ok(Some(("application/json", body.into_bytes())))
171 }
172}
173
174#[derive(Debug, Default, Clone)]
176pub struct QueryParams<'a> {
177 params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
178}
179
180impl<'a> QueryParams<'a> {
181 pub fn push<'b, K, V>(&mut self, key: K, value: V) -> &mut Self
183 where
184 K: Into<Cow<'a, str>>,
185 V: ParamValue<'b>,
186 'b: 'a,
187 {
188 self.params.push((key.into(), value.as_value()));
189 self
190 }
191
192 pub fn push_opt<'b, K, V>(&mut self, key: K, value: Option<V>) -> &mut Self
194 where
195 K: Into<Cow<'a, str>>,
196 V: ParamValue<'b>,
197 'b: 'a,
198 {
199 if let Some(value) = value {
200 self.params.push((key.into(), value.as_value()));
201 }
202 self
203 }
204
205 pub fn extend<'b, I, K, V>(&mut self, iter: I) -> &mut Self
207 where
208 I: Iterator<Item = (K, V)>,
209 K: Into<Cow<'a, str>>,
210 V: ParamValue<'b>,
211 'b: 'a,
212 {
213 self.params
214 .extend(iter.map(|(key, value)| (key.into(), value.as_value())));
215 self
216 }
217
218 pub fn add_to_url(&self, url: &mut Url) {
220 let mut pairs = url.query_pairs_mut();
221 pairs.extend_pairs(self.params.iter());
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use serde_json::json;
228
229 use crate::api::{JsonParams, ParamValue};
230
231 #[test]
232 fn bool_str() {
233 let items = &[(true, "true"), (false, "false")];
234
235 for (i, s) in items {
236 assert_eq!((*i).as_value(), *s);
237 }
238 }
239
240 #[test]
241 fn test_str_as_value() {
242 let items = &["foo", "bar"];
243
244 for i in items {
245 assert_eq!(i.as_value(), *i);
246 }
247 }
248
249 #[test]
250 fn test_string_as_value() {
251 let items = &["foo", "bar"];
252
253 for i in items {
254 let s = String::from(*i);
255 assert_eq!(s.as_value(), s);
256 }
257 }
258
259 #[test]
260 fn json_params_clean() {
261 let dirty = json!({
262 "null": null,
263 "int": 1,
264 "str": "str",
265 "array": [null],
266 "empty_array": [],
267 "object": {
268 "nested_null": null,
269 "nested_empty_array": [],
270 "nested_empty_object": {},
271 },
272 "empty_object": {},
273 });
274 let clean = json!({
275 "int": 1,
276 "str": "str",
277 "array": [null],
278 "object": {
279 "nested_null": null,
280 "nested_empty_array": [],
281 "nested_empty_object": {},
282 },
283 });
284
285 assert_eq!(JsonParams::clean(dirty), clean);
286 }
287}