indieweb 0.10.0

A collection of utilities for working with the IndieWeb.
Documentation
use super::extension;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use url::Url;

/// Represents the normalized form of pagination in Micropub queries.
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default, Hash, Eq)]
#[serde_as]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Query {
    /// Provides a limit of how many items can be returned.
    ///
    /// View <https://indieweb.org/Micropub-extensions#Limit_Parameter> for more information.
    #[serde_as(as = "DisplayFromStr")]
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub limit: Option<String>,

    /// Provides an offset from which to query or fetch items from.
    ///
    /// View <https://indieweb.org/Micropub-extensions#Offset_Parameter> for more information.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub offset: Option<String>,

    /// Provides a value that can be filtered upon.
    ///
    /// View <https://indieweb.org/Micropub-extensions#Filter_Parameter> for more information.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub filter: Option<String>,

    /// Provides a value that can be used as the left-handed boundary for a search.
    ///
    /// View <https://indieweb.org/Micropub-extensions#Pagination> for more information.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub before: Option<String>,

    /// Provides a value that can be used as the right-handed boundary for a search.
    ///
    /// View <https://indieweb.org/Micropub-extensions#Pagination> for more information.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub after: Option<String>,

    /// Provides a normalized means of ordering the content in the returned list.
    ///
    /// View <https://indieweb.org/Micropub-extensions#Order> for more information.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub order: Option<extension::Order>,
}

impl Query {
    /// Determines if this query has no values defined.
    pub fn is_empty(&self) -> bool {
        *self == Default::default()
    }

    /// Determines if there's any values set to scope where in the slice this query takes place.
    pub fn is_ranged(&self) -> bool {
        let a = self
            .after
            .clone()
            .filter(|c| !c.is_empty())
            .unwrap_or_default()
            .is_empty();
        let b = self
            .before
            .clone()
            .filter(|c| !c.is_empty())
            .unwrap_or_default()
            .is_empty();

        !a || !b
    }

    pub fn extend_url(&self, base_url: &Url) -> Url {
        let mut extended_url = base_url.clone();
        if *self == Default::default() {
            return extended_url;
        };
        let mut qp = extended_url.query_pairs_mut();

        if let Some(ref limit) = self.limit {
            qp.append_pair("limit", limit.as_str());
        }

        let _ = qp.finish();
        drop(qp);

        extended_url.clone()
    }
}

/// Represents the extended fields of a paginated response.
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Fields {
    /// Defines the current paging options used.
    #[serde(default, skip_serializing_if = "Query::is_empty")]
    pub paging: Query,

    /// An optional representation of a cursor pointing to the most recent items for this query.
    #[serde(alias = "_latest", default, skip_serializing_if = "Option::is_none")]
    pub latest: Option<String>,

    /// An optional representation of a cursor pointing to the earliest items for this query.
    #[serde(alias = "_earliest", default, skip_serializing_if = "Option::is_none")]
    pub earliest: Option<String>,
}

impl Fields {
    pub fn is_empty(&self) -> bool {
        self.earliest.is_none() && self.latest.is_none() && self.paging.is_empty()
    }
}

/// Represents the response of a paginated request.
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Response {
    /// A list of JSON-serialized objects.
    pub items: Vec<serde_json::Value>,

    /// All of the shared pagination fields.
    #[serde(default, flatten, skip_serializing_if = "Fields::is_empty")]
    pub paging: Fields,
}