1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
use std::{
fmt::Display,
ops::{Deref, DerefMut},
};
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use bson::Document;
use serde::{Deserialize, Serialize};
use crate::option::CursorOptions;
/// Represents a Cursor to an Item with no special direction.
/// To Debug the contents, use `Debug`
/// When serializing or converting to String, the [`Edge`] gets encoded as url-safe Base64 String.
#[derive(Clone, Debug, PartialEq)]
pub struct Edge(Document);
impl Edge {
/// Creates a new [`Edge`] using a value Document and the sorting keys.
/// Only retains the values of the keys specified in the sort options to optimize storage.
///
/// # Arguments
/// * `document`: The Item to which the Edge will point to
/// * `options`: Used to extract the sorting keys
pub fn new(document: Document, options: &CursorOptions) -> Self {
let mut cursor = Document::new();
options
.sort
.clone()
.unwrap_or_default()
.keys()
.filter_map(|key| document.get(key).map(|value| (key, value)))
.for_each(|(key, value)| {
cursor.insert(key, value);
});
Self(cursor)
}
}
impl Display for Edge {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
&URL_SAFE_NO_PAD.encode(bson::to_vec(&self.0).map_err(serde::ser::Error::custom)?)
)
}
}
impl Serialize for Edge {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(
&URL_SAFE_NO_PAD.encode(bson::to_vec(&self.0).map_err(serde::ser::Error::custom)?),
)
}
}
impl<'de> Deserialize<'de> for Edge {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Vis;
impl serde::de::Visitor<'_> for Vis {
type Value = Edge;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a base64 string")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
let doc = URL_SAFE_NO_PAD
.decode(v)
.map_err(serde::de::Error::custom)?;
Ok(Edge(
bson::from_slice(doc.as_slice()).map_err(serde::de::Error::custom)?,
))
}
}
deserializer.deserialize_str(Vis)
}
}
#[cfg(feature = "graphql")]
#[juniper::object]
impl Edge {
fn cursor(&self) -> String {
self.cursor.to_owned()
}
}
impl Deref for Edge {
type Target = Document;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Edge {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
/// Contains information about the current Page
/// The cursor have the respecting direction to be used as is in an subsequent find:
/// start_cursor: `Backwards`
/// end_cursor: `Forward`
///
/// Note: has_xxx means if the next page has items, not if there is a next cursor
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
pub struct PageInfo {
/// True if there is a previous page which contains items
pub has_previous_page: bool,
/// True if there is a next page which contains items
pub has_next_page: bool,
/// Cursor to the first item of the page. Is set even when there is no previous page.
pub start_cursor: Option<DirectedCursor>,
/// Cursor to the last item of the page. Is set even when there is no next page.
pub end_cursor: Option<DirectedCursor>,
}
#[cfg(feature = "graphql")]
#[juniper::object]
impl PageInfo {
fn has_next_page(&self) -> bool {
self.has_next_page
}
fn has_previous_page(&self) -> bool {
self.has_previous_page
}
fn start_cursor(&self) -> Option<String> {
self.start_cursor.to_owned()
}
fn end_cursor(&self) -> Option<String> {
self.next_cursor.to_owned()
}
}
/// The result of a find method with the items, edges, pagination info, and total count of objects
#[derive(Debug, Default)]
pub struct FindResult<T> {
/// Current Page
pub page_info: PageInfo,
/// Edges to all items in the current Page, including start & end-cursor
pub edges: Vec<Edge>,
/// Total count of items in the whole collection
pub total_count: u64,
/// All items in the current Page
pub items: Vec<T>,
}
/// Cursor to an item with direction information.
/// Serializing pretains the direction Information.
/// To send only the Cursor use `to_string` which drops the direction information
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum DirectedCursor {
/// Use to invert the search e.g. go back a page
Backwards(Edge),
/// Normal direction to search
Forward(Edge),
}
impl DirectedCursor {
/// Reverses the direction of Cursor.
pub fn reverse(self) -> Self {
match self {
Self::Backwards(edge) => Self::Forward(edge),
Self::Forward(edge) => Self::Backwards(edge),
}
}
/// Returns a reference to the inner of this [`DirectedCursor`].
pub fn inner(&self) -> &Edge {
match self {
Self::Backwards(edge) => edge,
Self::Forward(edge) => edge,
}
}
/// Removes the direction information and returns an Edge
pub fn into_inner(self) -> Edge {
match self {
Self::Backwards(edge) => edge,
Self::Forward(edge) => edge,
}
}
}
impl Display for DirectedCursor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner())
}
}