use client::Client;
use error::Error;
use serde::de::DeserializeOwned;
use std::collections::HashMap;
pub trait Identifiable {
fn id(&self) -> &str;
}
#[derive(Debug, Deserialize, Serialize)]
pub struct List<T> {
pub data: Vec<T>,
pub has_more: bool,
pub total_count: Option<u64>,
pub url: String,
}
impl<T: Clone> Clone for List<T> {
fn clone(&self) -> Self {
List {
data: self.data.clone(),
has_more: self.has_more.clone(),
total_count: self.total_count.clone(),
url: self.url.clone()
}
}
}
impl<T: DeserializeOwned> List<T> {
pub fn get_next(client: &Client, url: &str, last_id: &str) -> Result<List<T>, Error> {
if url.starts_with("/v1/") {
let mut url = url.trim_left_matches("/v1/").to_string();
url.push_str(&format!("?starting_after={}", last_id));
client.get(&url)
} else {
Err(Error::Unsupported("URL for fetching additional data uses different API version"))
}
}
}
impl<T: Identifiable + DeserializeOwned> List<T> {
pub fn get_all(self, client: &Client) -> Result<Vec<T>, Error> {
let mut data = Vec::new();
let mut next = self;
loop {
if next.has_more {
let resp = next.next(&client)?;
data.extend(next.data);
next = resp;
} else {
data.extend(next.data);
break;
}
}
Ok(data)
}
pub fn next(&self, client: &Client) -> Result<List<T>, Error> {
if let Some(last_id) = self.data.last().map(|d| d.id()) {
List::get_next(client, &self.url, last_id)
} else {
Ok(List {
data: Vec::new(),
has_more: false,
total_count: self.total_count,
url: self.url.clone()
})
}
}
}
pub type Metadata = HashMap<String, String>;
pub type Timestamp = i64;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub struct RangeBounds<T> {
pub gt: Option<T>,
pub gte: Option<T>,
pub lt: Option<T>,
pub lte: Option<T>,
}
impl<T> Default for RangeBounds<T> {
fn default() -> Self {
RangeBounds {
gt: None,
gte: None,
lt: None,
lte: None,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum RangeQuery<T> {
Exact(T),
Bounds(RangeBounds<T>),
}
impl<T> RangeQuery<T> {
pub fn eq(value: T) -> RangeQuery<T> {
RangeQuery::Exact(value)
}
pub fn gt(value: T) -> RangeQuery<T> {
let mut bounds = RangeBounds::default();
bounds.gt = Some(value);
RangeQuery::Bounds(bounds)
}
pub fn gte(value: T) -> RangeQuery<T> {
let mut bounds = RangeBounds::default();
bounds.gte = Some(value);
RangeQuery::Bounds(bounds)
}
pub fn lt(value: T) -> RangeQuery<T> {
let mut bounds = RangeBounds::default();
bounds.gt = Some(value);
RangeQuery::Bounds(bounds)
}
pub fn lte(value: T) -> RangeQuery<T> {
let mut bounds = RangeBounds::default();
bounds.gte = Some(value);
RangeQuery::Bounds(bounds)
}
}
pub fn to_snakecase(camel: &str) -> String {
let mut i = 0;
let mut snake = String::new();
let mut chars = camel.chars().peekable();
while let Some(ch) = chars.next() {
if ch.is_uppercase() {
if i > 0 && !chars.peek().unwrap_or(&'A').is_uppercase() {
snake.push('_');
}
snake.push(ch.to_lowercase().next().unwrap_or(ch));
} else {
snake.push(ch);
}
i += 1;
}
snake
}
#[cfg(test)]
mod tests {
#[test]
fn to_snakecase() {
use super::to_snakecase;
assert_eq!(to_snakecase("snake_case").as_str(), "snake_case");
assert_eq!(to_snakecase("CamelCase").as_str(), "camel_case");
assert_eq!(to_snakecase("XMLHttpRequest").as_str(), "xml_http_request");
assert_eq!(to_snakecase("UPPER").as_str(), "upper");
assert_eq!(to_snakecase("lower").as_str(), "lower");
}
}