aboutsummaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/Cargo.toml2
-rw-r--r--src/util/crdt/deletable.rs72
-rw-r--r--src/util/crdt/lww.rs5
-rw-r--r--src/util/crdt/lww_map.rs12
-rw-r--r--src/util/crdt/mod.rs2
-rw-r--r--src/util/error.rs29
-rw-r--r--src/util/time.rs5
7 files changed, 122 insertions, 5 deletions
diff --git a/src/util/Cargo.toml b/src/util/Cargo.toml
index e33f8a66..d5200f98 100644
--- a/src/util/Cargo.toml
+++ b/src/util/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "garage_util"
-version = "0.5.0"
+version = "0.6.0"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
diff --git a/src/util/crdt/deletable.rs b/src/util/crdt/deletable.rs
new file mode 100644
index 00000000..c76f5cbb
--- /dev/null
+++ b/src/util/crdt/deletable.rs
@@ -0,0 +1,72 @@
+use serde::{Deserialize, Serialize};
+
+use crate::crdt::crdt::*;
+
+/// Deletable object (once deleted, cannot go back)
+#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
+pub enum Deletable<T> {
+ Present(T),
+ Deleted,
+}
+
+impl<T: Crdt> Deletable<T> {
+ /// Create a new deletable object that isn't deleted
+ pub fn present(v: T) -> Self {
+ Self::Present(v)
+ }
+ /// Create a new deletable object that is deleted
+ pub fn delete() -> Self {
+ Self::Deleted
+ }
+ /// As option
+ pub fn as_option(&self) -> Option<&T> {
+ match self {
+ Self::Present(v) => Some(v),
+ Self::Deleted => None,
+ }
+ }
+ /// As option, mutable
+ pub fn as_option_mut(&mut self) -> Option<&mut T> {
+ match self {
+ Self::Present(v) => Some(v),
+ Self::Deleted => None,
+ }
+ }
+ /// Into option
+ pub fn into_option(self) -> Option<T> {
+ match self {
+ Self::Present(v) => Some(v),
+ Self::Deleted => None,
+ }
+ }
+ /// Is object deleted?
+ pub fn is_deleted(&self) -> bool {
+ matches!(self, Self::Deleted)
+ }
+}
+
+impl<T> From<Option<T>> for Deletable<T> {
+ fn from(v: Option<T>) -> Self {
+ v.map(Self::Present).unwrap_or(Self::Deleted)
+ }
+}
+
+impl<T> From<Deletable<T>> for Option<T> {
+ fn from(v: Deletable<T>) -> Option<T> {
+ match v {
+ Deletable::Present(v) => Some(v),
+ Deletable::Deleted => None,
+ }
+ }
+}
+
+impl<T: Crdt> Crdt for Deletable<T> {
+ fn merge(&mut self, other: &Self) {
+ if let Deletable::Present(v) = self {
+ match other {
+ Deletable::Deleted => *self = Deletable::Deleted,
+ Deletable::Present(v2) => v.merge(v2),
+ }
+ }
+ }
+}
diff --git a/src/util/crdt/lww.rs b/src/util/crdt/lww.rs
index 43d13f27..bc686e05 100644
--- a/src/util/crdt/lww.rs
+++ b/src/util/crdt/lww.rs
@@ -82,6 +82,11 @@ where
&self.v
}
+ /// Take the value inside the CRDT (discards the timesamp)
+ pub fn take(self) -> T {
+ self.v
+ }
+
/// Get a mutable reference to the CRDT's value
///
/// This is usefull to mutate the inside value without changing the LWW timestamp.
diff --git a/src/util/crdt/lww_map.rs b/src/util/crdt/lww_map.rs
index 3e9aba79..21cb6e12 100644
--- a/src/util/crdt/lww_map.rs
+++ b/src/util/crdt/lww_map.rs
@@ -30,8 +30,8 @@ pub struct LwwMap<K, V> {
impl<K, V> LwwMap<K, V>
where
- K: Ord,
- V: Crdt,
+ K: Clone + Ord,
+ V: Clone + Crdt,
{
/// Create a new empty map CRDT
pub fn new() -> Self {
@@ -73,6 +73,10 @@ where
};
Self { vals: new_vals }
}
+
+ pub fn update_in_place(&mut self, k: K, new_v: V) {
+ self.merge(&self.update_mutator(k, new_v));
+ }
/// Takes all of the values of the map and returns them. The current map is reset to the
/// empty map. This is very usefull to produce in-place a new map that contains only a delta
/// that modifies a certain value:
@@ -158,8 +162,8 @@ where
impl<K, V> Default for LwwMap<K, V>
where
- K: Ord,
- V: Crdt,
+ K: Clone + Ord,
+ V: Clone + Crdt,
{
fn default() -> Self {
Self::new()
diff --git a/src/util/crdt/mod.rs b/src/util/crdt/mod.rs
index 9663a5a5..6ba575ed 100644
--- a/src/util/crdt/mod.rs
+++ b/src/util/crdt/mod.rs
@@ -12,12 +12,14 @@
mod bool;
#[allow(clippy::module_inception)]
mod crdt;
+mod deletable;
mod lww;
mod lww_map;
mod map;
pub use self::bool::*;
pub use crdt::*;
+pub use deletable::*;
pub use lww::*;
pub use lww_map::*;
pub use map::*;
diff --git a/src/util/error.rs b/src/util/error.rs
index ff03d05b..08cf1302 100644
--- a/src/util/error.rs
+++ b/src/util/error.rs
@@ -119,6 +119,35 @@ where
}
}
+/// Trait to map error to the Bad Request error code
+pub trait OkOrMessage {
+ type S2;
+ fn ok_or_message<M: Into<String>>(self, message: M) -> Self::S2;
+}
+
+impl<T, E> OkOrMessage for Result<T, E>
+where
+ E: std::fmt::Display,
+{
+ type S2 = Result<T, Error>;
+ fn ok_or_message<M: Into<String>>(self, message: M) -> Result<T, Error> {
+ match self {
+ Ok(x) => Ok(x),
+ Err(e) => Err(Error::Message(format!("{}: {}", message.into(), e))),
+ }
+ }
+}
+
+impl<T> OkOrMessage for Option<T> {
+ type S2 = Result<T, Error>;
+ fn ok_or_message<M: Into<String>>(self, message: M) -> Result<T, Error> {
+ match self {
+ Some(x) => Ok(x),
+ None => Err(Error::Message(message.into())),
+ }
+ }
+}
+
// Custom serialization for our error type, for use in RPC.
// Errors are serialized as a string of their Display representation.
// Upon deserialization, they all become a RemoteError with the
diff --git a/src/util/time.rs b/src/util/time.rs
index 238db2c3..d9192443 100644
--- a/src/util/time.rs
+++ b/src/util/time.rs
@@ -10,6 +10,11 @@ pub fn now_msec() -> u64 {
.as_millis() as u64
}
+/// Increment logical clock
+pub fn increment_logical_clock(prev: u64) -> u64 {
+ std::cmp::max(prev + 1, now_msec())
+}
+
/// Convert a timestamp represented as milliseconds since UNIX Epoch to
/// its RFC3339 representation, such as "2021-01-01T12:30:00Z"
pub fn msec_to_rfc3339(msecs: u64) -> String {