links/store/
backend.rs

1//! This module contains a trait and its implementations for a unified
2//! redirect, vanity path, and statistics store used as links' backend store.
3//!
4//! This store can be something simple like in-memory hashmaps, an interface to
5//! something more complex and feature-rich like Redis, or anything in between.
6//! The aim of this [`StoreBackend`] trait is to make it easy to swap between
7//! different storage backends, and to make developing them fast. For details
8//! about configuring each store backend, see that backend's documentation.
9
10use core::fmt::Debug;
11use std::collections::HashMap;
12
13use anyhow::Result;
14use async_trait::async_trait;
15use links_id::Id;
16use links_normalized::{Link, Normalized};
17
18use crate::{
19	stats::{Statistic, StatisticDescription, StatisticValue},
20	store::BackendType,
21};
22
23/// The redirect, vanity path, and statistics store trait used by links.
24#[async_trait]
25pub trait StoreBackend: Debug + Send + Sync {
26	/// Get this implementation's backend store type. This is used in
27	/// e.g. the configuration.
28	fn store_type() -> BackendType
29	where
30		Self: Sized;
31
32	/// Get this implementation's backend store type. This can be used on trait
33	/// objects, but is otherwise equivalent to calling `Self::store_type()`.
34	fn get_store_type(&self) -> BackendType;
35
36	/// Create a new instance of this `StoreBackend`. Configuration is provided
37	/// as a collection of `pico-args` arguments beginning with `--store-`. For
38	/// details about configuring each store backend, see that backend's
39	/// documentation.
40	async fn new(config: &HashMap<String, String>) -> Result<Self>
41	where
42		Self: Sized;
43
44	/// Get a redirect. Returns the full `to` link corresponding to the `from`
45	/// links ID. A link not existing is not an error, if no matching link is
46	/// found, `Ok(None)` is returned.
47	///
48	/// # Error
49	/// An error is only returned if something actually fails; if we don't know
50	/// if a link exists or not, or what it is. A link not existing is not
51	/// considered an error.
52	async fn get_redirect(&self, from: Id) -> Result<Option<Link>>;
53
54	/// Set a redirect. `from` is the ID of the link, while `to` is the full
55	/// destination link. If a mapping with this ID already exists, it must be
56	/// changed to the new one, returning the old one.
57	///
58	/// # Storage Guarantees
59	/// If an `Ok` is returned, the new value was definitely set / processed /
60	/// saved, and will be available on next request.
61	/// If an `Err` is returned, the value must not have been set / modified,
62	/// insofar as that is possible to determine from the backend.
63	async fn set_redirect(&self, from: Id, to: Link) -> Result<Option<Link>>;
64
65	/// Remove a redirect. `from` is the ID of the links link to be removed.
66	/// Returns the old value of the mapping or `None` if there was no such
67	/// mapping.
68	///
69	/// # Storage Guarantees
70	/// If an `Ok` is returned, the new value was definitely removed /
71	/// processed / saved, and will be unavailable on next request.
72	/// If an `Err` is returned, the value must not have been removed /
73	/// modified, insofar as that is possible to determine from the backend.
74	async fn rem_redirect(&self, from: Id) -> Result<Option<Link>>;
75
76	/// Get a vanity path's ID. Returns the ID of the `to` link corresponding
77	/// to the `from` vanity path. An ID not existing is not an error, if no
78	/// matching ID is found, `None` is returned.
79	///
80	/// # Error
81	/// An error is only returned if something actually fails; if we don't know
82	/// if a link exists or not, or what it is. A link not existing is not
83	/// considered an error.
84	async fn get_vanity(&self, from: Normalized) -> Result<Option<Id>>;
85
86	/// Set a vanity path for an ID. `from` is the vanity path of the links ID,
87	/// while `to` is the ID itself. If a vanity link with this path already
88	/// exists, it must be changed to the new one, returning the old one.
89	///
90	/// # Storage Guarantees
91	/// If an `Ok` is returned, the new value was definitely set / processed /
92	/// saved, and will be available on next request.
93	/// If an `Err` is returned, the value must not have been set / modified,
94	/// insofar as that is possible to determine from the backend.
95	async fn set_vanity(&self, from: Normalized, to: Id) -> Result<Option<Id>>;
96
97	/// Remove a vanity path. `from` is the vanity path to be removed. Returns
98	/// the old value of the mapping or `None` if there was no such mapping.
99	///
100	/// # Storage Guarantees
101	/// If an `Ok` is returned, the new value was definitely removed /
102	/// processed / saved, and will be unavailable on next request.
103	/// If an `Err` is returned, the value must not have been removed /
104	/// modified, insofar as that is possible to determine from the backend.
105	async fn rem_vanity(&self, from: Normalized) -> Result<Option<Id>>;
106
107	/// Get statistics' values by their description. Returns all matching
108	/// [`Statistic`]s and their values for the provided
109	/// [`StatisticDescription`]. Statistics not having been collected is not an
110	/// error, if no matching statistics are found, an empty [`Vec`] is
111	/// returned.
112	///
113	/// By default this function returns an empty [`Vec`]
114	///
115	/// # Error
116	/// An error is only returned if something fails when it should have worked.
117	/// A statistic not existing or the store not supporting statistics is not
118	/// considered an error.
119	async fn get_statistics(
120		&self,
121		_description: StatisticDescription,
122	) -> Result<Vec<(Statistic, StatisticValue)>> {
123		Ok(Vec::new())
124	}
125
126	/// Increment a statistic's count. The provided [`Statistic`]'s value is
127	/// incremented by 1. Returns the new value of the statistic after the
128	/// increment, or `None` if the statistic wasn't recorded or its new value
129	/// is not known.
130	///
131	/// By default this function does nothing and returns `Ok(None)`
132	///
133	/// # Error
134	/// An error is only returned if something fails when it should have worked.
135	/// A statistic not being recorded (immediately or ever) is not considered
136	/// and error.
137	async fn incr_statistic(&self, _statistic: Statistic) -> Result<Option<StatisticValue>> {
138		Ok(None)
139	}
140
141	/// Remove statistics by their description. Deletes all [`Statistic`]s that
142	/// match the provided [`StatisticDescription`] and returns their values
143	/// before they were deleted, if they're available. A statistic not having
144	/// been collected is not an error, if no matching statistics are found, an
145	/// empty [`Vec`] is returned.
146	///
147	/// By default this function does nothing and returns an empty [`Vec`]
148	///
149	/// # Error
150	/// An error is only returned if something fails when it should have worked.
151	/// A statistic not existing or the store not supporting statistics is not
152	/// considered an error.
153	async fn rem_statistics(
154		&self,
155		_description: StatisticDescription,
156	) -> Result<Vec<(Statistic, StatisticValue)>> {
157		Ok(Vec::new())
158	}
159}