use std::borrow::Cow;
use chrono::{DateTime, NaiveDate, Utc};
use url::Url;
use serde_json::{json, Value};
use crate::api::BodyError;
pub trait ParamValue<'a> {
#[allow(clippy::wrong_self_convention)]
fn as_value(&self) -> Cow<'a, str>;
}
impl ParamValue<'static> for bool {
fn as_value(&self) -> Cow<'static, str> {
if *self {
"true".into()
} else {
"false".into()
}
}
}
impl<'a> ParamValue<'a> for &'a str {
fn as_value(&self) -> Cow<'a, str> {
(*self).into()
}
}
impl ParamValue<'static> for String {
fn as_value(&self) -> Cow<'static, str> {
self.clone().into()
}
}
impl<'a> ParamValue<'a> for &'a String {
fn as_value(&self) -> Cow<'a, str> {
(*self).into()
}
}
impl<'a> ParamValue<'a> for Cow<'a, str> {
fn as_value(&self) -> Cow<'a, str> {
self.clone()
}
}
impl<'a, 'b: 'a> ParamValue<'a> for &'b Cow<'a, str> {
fn as_value(&self) -> Cow<'a, str> {
(*self).clone()
}
}
impl ParamValue<'static> for i32 {
fn as_value(&self) -> Cow<'static, str> {
self.to_string().into()
}
}
impl ParamValue<'static> for u32 {
fn as_value(&self) -> Cow<'static, str> {
self.to_string().into()
}
}
impl ParamValue<'static> for u64 {
fn as_value(&self) -> Cow<'static, str> {
self.to_string().into()
}
}
impl ParamValue<'static> for f32 {
fn as_value(&self) -> Cow<'static, str> {
self.to_string().into()
}
}
impl ParamValue<'static> for f64 {
fn as_value(&self) -> Cow<'static, str> {
self.to_string().into()
}
}
impl ParamValue<'static> for DateTime<Utc> {
fn as_value(&self) -> Cow<'static, str> {
self.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
.into()
}
}
impl ParamValue<'static> for NaiveDate {
fn as_value(&self) -> Cow<'static, str> {
format!("{}", self.format("%Y-%m-%d")).into()
}
}
#[derive(Debug, Default, Clone)]
pub struct QueryParams<'a> {
params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
params_key_only: Vec<Cow<'a, str>>,
}
impl<'a> QueryParams<'a> {
pub fn push<'b, K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: Into<Cow<'a, str>>,
V: ParamValue<'b>,
'b: 'a,
{
self.params.push((key.into(), value.as_value()));
self
}
pub fn push_opt<'b, K, V>(&mut self, key: K, value: Option<V>) -> &mut Self
where
K: Into<Cow<'a, str>>,
V: ParamValue<'b>,
'b: 'a,
{
if let Some(value) = value {
self.params.push((key.into(), value.as_value()));
}
self
}
pub fn push_opt_key_only<K, V>(&mut self, key: K, value: Option<V>) -> &mut Self
where
K: Into<Cow<'a, str>>,
{
if value.is_some() {
self.params_key_only.push(key.into());
}
self
}
pub fn extend<'b, I, K, V>(&mut self, iter: I) -> &mut Self
where
I: Iterator<Item = (K, V)>,
K: Into<Cow<'a, str>>,
V: ParamValue<'b>,
'b: 'a,
{
self.params
.extend(iter.map(|(key, value)| (key.into(), value.as_value())));
self
}
pub fn add_to_url(&self, url: &mut Url) {
let mut pairs = url.query_pairs_mut();
pairs
.extend_pairs(self.params.iter())
.extend_keys_only::<std::slice::Iter<'_, std::borrow::Cow<'_, str>>, Cow<'_, str>>(
self.params_key_only.iter(),
);
}
}
#[derive(Debug, Default, Clone)]
pub struct JsonBodyParams {
data: Value,
}
impl JsonBodyParams {
pub fn push<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: Into<String>,
V: serde::Serialize,
{
match self.data.as_object_mut() {
Some(ref mut m) => {
m.insert(key.into(), json!(value));
}
None => {
self.data = json!({key.into(): value});
}
}
self
}
pub fn push_opt<K, V>(&mut self, key: K, value: Option<V>) -> &mut Self
where
K: Into<String>,
V: serde::Serialize,
{
if let Some(value) = value {
match self.data.as_object_mut() {
Some(ref mut m) => {
m.insert(key.into(), json!(value));
}
None => {
self.data = json!({key.into(): value});
}
}
}
self
}
pub fn into_body(self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
let body = self.data.to_string();
Ok(Some(("application/json", body.into_bytes())))
}
pub fn into_body_with_root_key(
self,
key: &'static str,
) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
let body = json!({ key: self.data}).to_string();
Ok(Some(("application/json", body.into_bytes())))
}
}
#[cfg(test)]
mod tests {
use super::JsonBodyParams;
use crate::api::ParamValue;
use serde_json::json;
#[test]
fn bool_str() {
let items = &[(true, "true"), (false, "false")];
for (i, s) in items {
assert_eq!((*i).as_value(), *s);
}
}
#[test]
fn json_body() {
let mut data = JsonBodyParams::default();
data.push("foo", "bar")
.push("foo1", "bar1")
.push("foo_bool", true)
.push("foo_array", vec!["a", "b"]);
let (mime, body) = data.into_body().unwrap().unwrap();
assert_eq!("application/json", mime);
assert_eq!(
std::str::from_utf8(&body).unwrap(),
json!({"foo": "bar", "foo1": "bar1", "foo_bool": true, "foo_array": ["a", "b"]})
.to_string()
);
}
}