shopify 0.1.6

Shopify API Client for Rust.
Documentation
use reqwest::{Client as HttpClient, RequestBuilder, StatusCode, Url};
use types::{DateTime, Utc};
use error::*;
pub use reqwest::Method;
use serde::Deserialize;

mod types;
pub use self::types::*;

#[doc(hidden)]
#[macro_export]
macro_rules! shopify_wrap {
  (
    pub struct $t:ident {
      $key:ident: $inner_t:ty$(,)*
    }
  ) => {
    use client::ShopifyWarpper;

    #[derive(Debug, Deserialize)]
    pub struct $t {
      $key: $inner_t,
    }

    impl ShopifyWarpper<$inner_t> for $t {
      fn into_inner(self) -> $inner_t {
        self.$key
      }
    }
  }
}

pub(crate) trait AsQueryValue {
  fn as_query_value(&self) -> String;
}

macro_rules! impl_into_query_value {
  (
    $t:ty
  ) => (
    impl AsQueryValue for $t {
      fn as_query_value(&self) -> String {
        format!("{}", self)
      }
    }
  );

  (
    $t:ty, $f:expr
  ) => (
    impl AsQueryValue for $t {
      fn as_query_value(&self) -> String {
        $f(self)
      }
    }
  );
}

impl_into_query_value!(i64);
impl_into_query_value!(String);
impl_into_query_value!(DateTime<Utc>, |date: &DateTime<Utc>| date.to_rfc3339());

impl<T> AsQueryValue for Vec<T>
where
  T: AsQueryValue,
{
  fn as_query_value(&self) -> String {
    self
      .iter()
      .map(|v| v.as_query_value())
      .collect::<Vec<_>>()
      .join(",")
  }
}

#[doc(hidden)]
#[macro_export]
macro_rules! request_query {
  (
    pub struct $t:ident {
      $(pub $key:ident: Option<$field_t:ty>),*
      $(,)*
    }
  ) => (
    use client::{ShopifyRequestQuery, AsQueryValue};

    #[derive(Debug, Default)]
    pub struct $t {
      $(
        pub $key: Option<$field_t>
      ),*
    }

    impl ShopifyRequestQuery for $t {
      fn as_query_pairs(&self) -> Vec<(String, String)> {
        let mut pairs = vec![];
        $(
          request_query!(PUSH_FIELD self, pairs, $key, $field_t);
        )*
        pairs
      }
    }
  );

  (PUSH_FIELD $s:expr, $v:expr, $key:ident, $t:tt) => {
    if let Some(ref v) = $s.$key {
      $v.push((
        stringify!($key).to_owned(),
        v.as_query_value(),
      ));
    }
  };
}

#[derive(Debug, Clone)]
pub struct Client {
  base_url: Url,
  api_key: String,
  password: String,
  client: HttpClient,
}

impl Client {
  pub fn new(base_url: &str, api_key: &str, password: &str) -> Result<Self> {
    Ok(Client {
      base_url: Url::parse(base_url)?,
      api_key: api_key.to_owned(),
      password: password.to_owned(),
      client: HttpClient::new(),
    })
  }

  pub fn with_http_client(
    client: HttpClient,
    base_url: &str,
    api_key: &str,
    password: &str,
  ) -> Result<Self> {
    Ok(Client {
      base_url: Url::parse(base_url)?,
      api_key: api_key.to_owned(),
      password: password.to_owned(),
      client: client,
    })
  }

  pub fn request_with_params<P, T, F>(
    &self,
    method: Method,
    path: &str,
    params: &P,
    bf: F,
  ) -> Result<T>
  where
    P: ShopifyRequestQuery,
    T: for<'de> Deserialize<'de>,
    F: FnOnce(&mut RequestBuilder),
  {
    let mut url = self.base_url.join(path)?;
    url.query_pairs_mut().extend_pairs(params.as_query_pairs());
    let mut b = self.client.request(method, url);
    b.basic_auth(self.api_key.clone(), Some(self.password.clone()));

    bf(&mut b);

    let mut res = b.send()?;

    if !res.status().is_success() {
      if res.status() == StatusCode::NotFound {
        return Err(ErrorKind::NotFound.into());
      }

      let body = res.text()?;
      return Err(ErrorKind::Request(path.to_owned(), res.status(), body).into());
    }

    res.json().chain_err(|| ErrorKind::InvalidResponse)
  }

  pub fn request<T, F>(&self, method: Method, path: &str, bf: F) -> Result<T>
  where
    T: for<'de> Deserialize<'de>,
    F: FnOnce(&mut RequestBuilder),
  {
    self.request_with_params(method, path, &(), bf)
  }
}

#[cfg(test)]
pub fn get_test_client() -> Client {
  use std::env::var;
  ::dotenv::dotenv().ok();

  Client::new(
    &var("SHOPIFY_BASE_URL").unwrap(),
    &var("SHOPIFY_API_KEY").unwrap(),
    &var("SHOPIFY_PASSWORD").unwrap(),
  ).unwrap()
}