use super::{
client::Client,
error::{Error, Result as Xe621Result},
post::Post,
utils::{get_json_api_time, get_json_value_as},
};
use chrono::{offset::Utc, DateTime};
use serde_json::Value as JsonValue;
use std::convert::TryFrom;
#[derive(Debug)]
pub struct PoolIter<'a> {
client: &'a Client,
query: Option<String>,
page: u64,
chunk: Vec<Xe621Result<PoolListEntry>>,
ended: bool,
}
impl PoolIter<'_> {
fn new<'a>(client: &'a Client, query: Option<&str>) -> PoolIter<'a> {
PoolIter {
client,
query: query.map(urlencoding::encode),
page: 1,
chunk: Vec::new(),
ended: false,
}
}
}
impl Iterator for PoolIter<'_> {
type Item = Xe621Result<PoolListEntry>;
fn next(&mut self) -> Option<Xe621Result<PoolListEntry>> {
if self.chunk.is_empty() {
match self.client.get_json_endpoint(&format!(
"/pool/index.json?page={}{}",
{
let page = self.page;
self.page += 1;
page
},
match &self.query {
None => String::new(),
Some(title) => format!("&query={}", title),
}
)) {
Ok(body) => {
self.chunk = body
.as_array()
.unwrap()
.iter()
.rev()
.map(|v| PoolListEntry::try_from(v))
.collect()
}
Err(e) => {
self.ended = true;
self.chunk = vec![Err(e)]
}
}
}
self.ended |= self.chunk.is_empty();
if !self.ended {
let pool = self.chunk.pop().unwrap();
Some(pool)
} else {
self.chunk.pop()
}
}
}
#[derive(Debug)]
pub struct PoolListEntry {
pub raw: String,
pub id: u64,
pub name: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub user_id: u64,
pub is_locked: bool,
pub post_count: u64,
}
impl TryFrom<&JsonValue> for PoolListEntry {
type Error = super::error::Error;
fn try_from(v: &JsonValue) -> Xe621Result<Self> {
Ok(PoolListEntry {
raw: v.to_string(),
id: get_json_value_as(&v, "id", JsonValue::as_u64)?,
name: get_json_value_as(&v, "name", JsonValue::as_str)?.to_string(),
user_id: v["user_id"].as_u64().unwrap(),
created_at: get_json_api_time(&v, "created_at")?,
updated_at: get_json_api_time(&v, "updated_at")?,
is_locked: get_json_value_as(&v, "is_locked", JsonValue::as_bool)?,
post_count: v["post_count"].as_u64().unwrap(),
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Pool {
pub raw: String,
pub id: u64,
pub name: String,
pub description: String,
pub user_id: u64,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub is_locked: bool,
pub is_active: bool,
pub posts: Vec<Post>,
}
impl TryFrom<&JsonValue> for Pool {
type Error = super::error::Error;
fn try_from(v: &JsonValue) -> Xe621Result<Self> {
Ok(Pool {
raw: v.to_string(),
id: get_json_value_as(&v, "id", JsonValue::as_u64)?,
name: get_json_value_as(&v, "name", JsonValue::as_str)?.to_string(),
description: get_json_value_as(&v, "description", JsonValue::as_str)?.to_string(),
user_id: v["user_id"].as_u64().unwrap(),
created_at: get_json_api_time(&v, "created_at")?,
updated_at: get_json_api_time(&v, "updated_at")?,
is_locked: get_json_value_as(&v, "is_locked", JsonValue::as_bool)?,
is_active: get_json_value_as(&v, "is_active", JsonValue::as_bool)?,
posts: v["posts"]
.as_array()
.unwrap()
.iter()
.map(Post::try_from)
.collect::<Xe621Result<Vec<Post>>>()?,
})
}
}
impl TryFrom<(PoolListEntry, &Client)> for Pool {
type Error = Error;
fn try_from((r, c): (PoolListEntry, &Client)) -> Xe621Result<Pool> {
c.get_pool(r.id)
}
}
impl Client {
pub fn get_pool(&self, id: u64) -> Xe621Result<Pool> {
let body = self.get_json_endpoint(&format!("/pool/show.json?id={}", id))?;
Pool::try_from(&body)
}
pub fn pool_list<'a>(&'a self) -> PoolIter<'a> {
PoolIter::new(self, None)
}
pub fn pool_search<'a>(&'a self, query: &str) -> PoolIter<'a> {
PoolIter::new(self, Some(query))
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::offset::TimeZone;
use mockito::mock;
#[test]
fn pool_list_result_from_json() {
let example_json = include_str!("mocked/pool_list_result-12668.json");
let parsed = serde_json::from_str::<JsonValue>(example_json).unwrap();
let result = PoolListEntry::try_from(&parsed).unwrap();
assert_eq!(result.id, 12668);
assert_eq!(result.is_locked, false);
assert_eq!(result.name, "Random SFW name");
assert_eq!(result.post_count, 33);
assert_eq!(result.user_id, 171621);
assert_eq!(result.created_at, Utc.timestamp(1506450220, 569794000));
assert_eq!(result.updated_at, Utc.timestamp(1568077422, 207421000));
}
#[test]
fn pool_from_json() {
let example_json = include_str!("mocked/pool_18274.json");
let parsed = serde_json::from_str::<JsonValue>(example_json).unwrap();
let pool = Pool::try_from(&parsed).unwrap();
assert_eq!(pool.id, 18274);
assert_eq!(pool.is_active, true);
assert_eq!(pool.is_locked, false);
assert_eq!(pool.name, "oBEARwatch_by_Murasaki_Yuri");
assert_eq!(pool.description, "");
assert_eq!(pool.posts.len(), 8);
assert_eq!(pool.user_id, 357072);
assert_eq!(pool.created_at, Utc.timestamp(1567963035, 63943000));
assert_eq!(pool.updated_at, Utc.timestamp(1567964144, 960193000));
}
#[test]
fn get_pool() {
let client = Client::new(b"xe621/unit_test").unwrap();
let _m = mock("GET", "/pool/show.json?id=18274")
.with_body(include_str!("mocked/pool_18274.json"))
.create();
let pool = client.get_pool(18274).unwrap();
assert_eq!(pool.id, 18274);
}
#[test]
fn pool_list() {
let client = Client::new(b"xe621/unit_test").unwrap();
let _m = [
mock("GET", "/pool/index.json?page=1")
.with_body(include_str!("mocked/pool_list-page_1.json"))
.create(),
mock("GET", "/pool/index.json?page=2")
.with_body(include_str!("mocked/pool_list-page_2.json"))
.create(),
mock("GET", "/pool/index.json?page=3")
.with_body("[]")
.create(),
];
let pools: Vec<_> = client.pool_list().collect();
assert_eq!(pools.len(), 6);
}
#[test]
fn pool_search() {
let client = Client::new(b"xe621/unit_test").unwrap();
let _m = [
mock("GET", "/pool/index.json?page=1&query=foo")
.with_body(include_str!("mocked/pool_search-foo.json"))
.create(),
mock("GET", "/pool/index.json?page=2&query=foo")
.with_body("[]")
.create(),
];
for pool in client.pool_search("foo") {
assert!(pool.unwrap().name.contains("foo"));
}
}
}