nekosbest 0.21.6

nekos.best API wrapper
Documentation
use reqwest::RequestBuilder;
use std::{
    fmt::Debug,
    ops::{Deref, DerefMut, Index, IndexMut},
};

use serde::Deserialize;

use crate::client::ReqBuilder;
use crate::{
    Category,
    details::{GifDetails, ImageDetails},
};

pub trait STCategory: Sized {
    const CATEGORY: Category;
    type Details: for<'de> Deserialize<'de> + Debug + Clone + 'static;
    type SearchQueryType: STNekosBestSearchQueryType;
}

#[derive(Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct STNekosBestResponseSingle<C: STCategory> {
    pub url: String,
    #[serde(flatten)]
    pub details: C::Details,
    pub dimensions: STNekosBestDimensions,
}

#[derive(Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct STNekosBestDimensions {
    pub width: u32,
    pub height: u32,
}

impl<C: STCategory> STNekosBestResponseSingle<C> {
    pub fn url(&self) -> &str {
        &self.url
    }
    pub fn details(&self) -> &C::Details {
        &self.details
    }
}

macro_rules! gif_endpoints {
    ([$($name:ident),* $(,)?]) => {
        $(
            gif_endpoints!($name);
        )*
    };
    ($name:ident) => {
        #[derive(Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
        pub struct $name;

        impl STCategory for $name {
            const CATEGORY: Category = Category::$name;
            type Details = GifDetails;
            type SearchQueryType = STNekosBestSearchQueryGifType;
        }
    };
}

gif_endpoints!([
    Angry, Baka, Bite, Blush, Bored, Cry, Cuddle, Dance, Facepalm, Feed, Handhold, Handshake,
    Happy, Highfive, Hug, Kick, Kiss, Laugh, Lurk, Nod, Nom, Nope, Pat, Peck, Poke, Pout, Punch,
    Run, Shoot, Shrug, Slap, Sleep, Smile, Smug, Stare, Think, ThumbsUp, Tickle, Wave, Wink, Yawn,
    Yeet
]);

macro_rules! image_endpoints {
    ([$($name:ident),* $(,)?]) => {
        $(
            image_endpoints!($name);
        )*
    };
    ($name:ident) => {
        #[derive(Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
        pub struct $name;

        impl STCategory for $name {
            const CATEGORY: Category = Category::$name;
            type Details = ImageDetails;
            type SearchQueryType = STNekosBestSearchQueryImageType;
        }
    };
}

image_endpoints!([Husbando, Kitsune, Neko, Waifu]);

#[deprecated(since = "0.11.0", note = "Use `Neko` instead")]
pub type Nekos = Neko;

#[derive(Deserialize, Debug, Clone)]
#[serde(bound(deserialize = "C: STCategory"))]
struct STNekosBestResponseV2<C>
where
    C: STCategory,
{
    results: Vec<STNekosBestResponseSingle<C>>,
}

/// A response from the api
#[derive(Debug, Clone)]
pub struct STNekosBestResponse<C: STCategory>(pub Vec<STNekosBestResponseSingle<C>>);

impl<'de, C: STCategory> Deserialize<'de> for STNekosBestResponse<C> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        STNekosBestResponseV2::<C>::deserialize(deserializer).map(Self::from)
    }
}

impl<C> From<STNekosBestResponseV2<C>> for STNekosBestResponse<C>
where
    C: STCategory,
{
    fn from(v: STNekosBestResponseV2<C>) -> Self {
        Self(v.results)
    }
}

impl<C: STCategory> Index<usize> for STNekosBestResponse<C> {
    type Output = STNekosBestResponseSingle<C>;

    fn index(&self, index: usize) -> &Self::Output {
        self.0.index(index)
    }
}

impl<C: STCategory> IndexMut<usize> for STNekosBestResponse<C> {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        self.0.index_mut(index)
    }
}

impl<C: STCategory> Deref for STNekosBestResponse<C> {
    type Target = Vec<STNekosBestResponseSingle<C>>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<C: STCategory> DerefMut for STNekosBestResponse<C> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

#[derive(serde::Serialize)]
pub struct STNekosBestSearchQuery<C: STCategory> {
    pub(crate) query: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) amount: Option<usize>,

    #[serde(skip)]
    _phantom: std::marker::PhantomData<C>,
}

impl<C: STCategory> STNekosBestSearchQuery<C> {
    pub fn new(query: impl Into<String>) -> Self {
        Self {
            query: query.into(),
            amount: None,

            _phantom: std::marker::PhantomData,
        }
    }

    pub fn amount(mut self, amount: usize) -> Self {
        self.amount = Some(amount);
        self
    }

    pub(crate) fn apply_to(&self, r: ReqBuilder) -> ReqBuilder {
        r.query(self)
            .query(&[("type", C::SearchQueryType::TYPE)])
            .query(&[("category", C::CATEGORY.to_url_name())])
    }
}

pub trait STNekosBestSearchQueryType {
    const TYPE: i32;
}

pub struct STNekosBestSearchQueryImageType;

impl STNekosBestSearchQueryType for STNekosBestSearchQueryImageType {
    const TYPE: i32 = 1;
}

pub struct STNekosBestSearchQueryGifType;

impl STNekosBestSearchQueryType for STNekosBestSearchQueryGifType {
    const TYPE: i32 = 2;
}