entertainarr-adapter-http 0.1.0

HTTP adapter for entertainarr
Documentation
use std::{collections::HashSet, hash::Hash};

#[derive(Debug, thiserror::Error)]
#[error("unable to parse enum, found {value:?}")]
pub struct ParseEnumError {
    pub value: String,
}

impl ParseEnumError {
    pub fn new(value: impl Into<String>) -> Self {
        Self {
            value: value.into(),
        }
    }
}

pub trait AsStr {
    fn as_str(&self) -> &str;
}

#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
pub struct Page<const N: u32 = 50> {
    #[serde(
        default = "Page::<N>::default_limit",
        skip_serializing_if = "Page::<N>::is_default_limit"
    )]
    pub limit: u32,
    #[serde(
        default = "Page::<N>::default_offset",
        skip_serializing_if = "Page::<N>::is_default_offset"
    )]
    pub offset: u32,
}

impl<const N: u32> Default for Page<N> {
    fn default() -> Self {
        Self {
            limit: Self::default_limit(),
            offset: Self::default_offset(),
        }
    }
}

impl<const N: u32> Page<N> {
    pub const fn default_limit() -> u32 {
        N
    }

    pub const fn is_default_limit(value: &u32) -> bool {
        *value == N
    }

    pub const fn default_offset() -> u32 {
        0
    }

    pub const fn is_default_offset(value: &u32) -> bool {
        *value == 0
    }

    pub const fn is_default(&self) -> bool {
        self.limit == Self::default_limit() && self.offset == Self::default_offset()
    }
}

#[cfg(feature = "server")]
impl From<Page> for entertainarr_domain::prelude::Page {
    fn from(value: Page) -> Self {
        Self {
            limit: value.limit,
            offset: value.offset,
        }
    }
}

#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Sort<T> {
    pub field: T,
    pub order: SortOrder,
}

impl<T: Default + PartialEq> Sort<T> {
    pub fn is_default(&self) -> bool {
        self.field == T::default() && self.order == SortOrder::Asc
    }
}

impl<T: Default> Default for Sort<T> {
    fn default() -> Self {
        Self {
            field: T::default(),
            order: SortOrder::Asc,
        }
    }
}

impl<T> serde::Serialize for Sort<T>
where
    T: AsStr,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self.order {
            SortOrder::Desc => {
                let value = format!("-{}", self.field.as_str());
                serializer.serialize_str(value.as_str())
            }
            SortOrder::Asc => serializer.serialize_str(self.field.as_str()),
        }
    }
}

impl<'de, T> serde::Deserialize<'de> for Sort<T>
where
    T: std::str::FromStr,
    T::Err: std::fmt::Display,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let value = String::deserialize(deserializer)?;
        if let Some(value) = value.strip_prefix("-") {
            Ok(Self {
                field: T::from_str(value).map_err(serde::de::Error::custom)?,
                order: SortOrder::Desc,
            })
        } else {
            let value = value.trim_start_matches("+");
            Ok(Self {
                field: T::from_str(value).map_err(serde::de::Error::custom)?,
                order: SortOrder::Asc,
            })
        }
    }
}

#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[cfg_attr(feature = "facet", repr(C))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SortOrder {
    Asc,
    Desc,
}

#[cfg(feature = "server")]
impl From<entertainarr_domain::prelude::SortOrder> for SortOrder {
    fn from(value: entertainarr_domain::prelude::SortOrder) -> Self {
        match value {
            entertainarr_domain::prelude::SortOrder::Asc => Self::Asc,
            entertainarr_domain::prelude::SortOrder::Desc => Self::Desc,
        }
    }
}

#[cfg(feature = "server")]
impl From<SortOrder> for entertainarr_domain::prelude::SortOrder {
    fn from(value: SortOrder) -> Self {
        match value {
            SortOrder::Asc => Self::Asc,
            SortOrder::Desc => Self::Desc,
        }
    }
}

#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FindQueryParams<Include>
where
    Include: Eq + Hash,
{
    #[serde(default, skip_serializing_if = "HashSet::is_empty")]
    pub include: HashSet<Include>,
}