M src/error.rs => src/error.rs +3 -0
@@ 9,6 9,7 @@ use crate::{Op, OpPayload};
#[derive(PartialEq, Eq, Clone)]
pub enum ChronofoldError<A, T> {
UnknownReference(Op<A, T>),
+ FutureTimestamp(Op<A, T>),
ExistingTimestamp(Op<A, T>),
}
@@ 20,6 21,7 @@ where
use ChronofoldError::*;
let (name, op) = match self {
UnknownReference(op) => ("UnknownReference", op),
+ FutureTimestamp(op) => ("FutureTimestamp", op),
ExistingTimestamp(op) => ("ExistingTimestamp", op),
};
f.debug_tuple(name).field(&op.omit_value()).finish()
@@ 41,6 43,7 @@ where
.as_ref()
.expect("reference must not be `None`")
),
+ FutureTimestamp(op) => write!(f, "future timestamp {}", op.id),
ExistingTimestamp(op) => write!(f, "existing timestamp {}", op.id),
}
}
M src/lib.rs => src/lib.rs +7 -0
@@ 208,6 208,13 @@ impl<A: Author, T> Chronofold<A, T> {
return Err(ChronofoldError::ExistingTimestamp(op));
}
+ // We rely on indices in timestamps being greater or equal than their
+ // indices in every local log. This means we cannot apply an op not
+ // matching this constraint, even if we know the reference.
+ if op.id.0 .0 > self.log.len() {
+ return Err(ChronofoldError::FutureTimestamp(op));
+ }
+
use OpPayload::*;
match op.payload {
Root => {
M tests/errors.rs => tests/errors.rs +13 -0
@@ 11,6 11,19 @@ fn unknown_timestamp() {
}
#[test]
+fn future_timestamp() {
+ let mut cfold = Chronofold::<u8, char>::default();
+ let op = Op::insert(
+ Timestamp(LogIndex(9), 1),
+ Some(Timestamp(LogIndex(0), 0)),
+ '.',
+ );
+ let err = cfold.apply(op.clone()).unwrap_err();
+ assert_eq!(ChronofoldError::FutureTimestamp(op), err);
+ assert_eq!("future timestamp <9, 1>", format!("{}", err));
+}
+
+#[test]
fn existing_timestamp() {
// Applying the same op twice results in a
// `ChronofoldError::ExistingTimestamp`: