use strum::Display;
use crate::convert::Parameterize;
use std::{
fmt::{Display, Formatter},
ops::RangeInclusive,
};
use thiserror::Error;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Category {
NonR18,
R18,
Mixin,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Keyword(pub(crate) String);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Size(pub(crate) Vec<ImageSize>);
#[derive(Debug, Clone, PartialEq, Eq, Display)]
#[strum(serialize_all = "lowercase")]
pub enum ImageSize {
Original,
Regular,
Small,
Thumb,
Mini,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Proxy(pub(crate) String);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DateAfter(pub(crate) u64);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DateBefore(pub(crate) u64);
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum Error {
#[error("excepted {range:?}, found {actual} {filed}")]
OutOfRange {
range: RangeInclusive<usize>,
actual: usize,
filed: &'static str,
},
}
#[must_use]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Request {
category: Category,
num: u8,
uid: Vec<u32>,
keyword: Option<Keyword>,
tag: Vec<String>,
size: Size,
proxy: Proxy,
date_after: Option<DateAfter>,
date_before: Option<DateBefore>,
dsc: bool,
}
impl std::default::Default for Request {
fn default() -> Self {
Request {
category: Category::NonR18,
num: 1,
uid: vec![],
keyword: None,
tag: vec![],
size: Size(vec![ImageSize::Original]),
proxy: Proxy("i.pixiv.cat".into()),
date_after: None,
date_before: None,
dsc: false,
}
}
}
impl Request {
pub fn category(self, category: Category) -> Self {
Self { category, ..self }
}
pub fn num(self, amount: u8) -> Result<Self, Error> {
if (0..=100).contains(&amount) {
Ok(Self {
num: amount,
..self
})
} else {
Err(Error::OutOfRange {
range: 0..=100,
actual: amount as usize,
filed: "",
})
}
}
pub fn uid(self, authors: &[u32]) -> Result<Self, Error> {
if (0..=20).contains(&authors.len()) {
Ok(Self {
uid: authors.into(),
..self
})
} else {
Err(Error::OutOfRange {
range: 0..=20,
actual: authors.len(),
filed: "uid",
})
}
}
pub fn keyword(mut self, keyword: impl Into<String>) -> Self {
self.keyword = Some(Keyword(keyword.into()));
self
}
pub fn tag(self, tag: &[impl AsRef<str>]) -> Result<Self, Error> {
if (1..=20).contains(&tag.len()) {
Ok(Self {
tag: tag.iter().map(AsRef::as_ref).map(String::from).collect(),
..self
})
} else {
Err(Error::OutOfRange {
range: 1..=20,
actual: tag.len(),
filed: "tag",
})
}
}
pub fn size(self, size_list: &[ImageSize]) -> Result<Self, Error> {
match size_list.len() {
0..=5 => Ok(Self {
size: Size(size_list.into()),
..self
}),
_ => Err(Error::OutOfRange {
range: 0..=5,
actual: size_list.len(),
filed: "size",
}),
}
}
pub fn proxy(self, proxy: impl Into<String>) -> Self {
Self {
proxy: Proxy(proxy.into()),
..self
}
}
pub fn date_after(mut self, date_after: u64) -> Self {
self.date_after = Some(DateAfter(date_after));
self
}
pub fn date_before(mut self, date_before: u64) -> Self {
self.date_before = Some(DateBefore(date_before));
self
}
pub fn dsc(self, dsc: bool) -> Self {
Self { dsc, ..self }
}
}
impl Display for Request {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut url: String = "https://api.lolicon.app/setu/v2?".into();
url.append(&self.category);
url.append(&self.date_after);
url.append(&self.date_before);
url.append(&self.dsc);
url.append(&self.keyword);
url.append(&self.num);
url.append(&self.proxy);
url.append(&self.size);
url.append(&self.tag);
url.append(&self.uid);
write!(f, "{}", url)
}
}
impl From<Request> for String {
fn from(request: Request) -> Self {
request.to_string()
}
}
trait AddArgument {
fn append(&mut self, option: &impl Parameterize);
}
impl AddArgument for String {
fn append(&mut self, option: &impl Parameterize) {
option.param(self);
}
}