Skip to main content

nodedb_lite/sync/array/
reject_policy.rs

1//! What Lite does with a locally-originated op Origin refuses.
2//!
3//! Origin's inbound array path returns one `ArrayRejectMsg` per refused
4//! frame. Each reason has one fixed `ArrayRejectClass`, and the class decides
5//! the op's fate:
6//!
7//! | Class | Reasons | Disposition |
8//! |---|---|---|
9//! | `SchemaRequired` | `ArrayUnknown`, `SchemaTooNew` | [`RejectDisposition::ResendSchema`] |
10//! | `CatchUp` | `RetentionFloor` | [`RejectDisposition::CatchUp`] |
11//! | `Permanent` | `ShapeInvalid`, `EngineRefused`, `PermissionDenied` | [`RejectDisposition::Drop`] |
12//! | `Transient` | `EngineUnavailable`, `SnapshotCapacity`, `SnapshotBufferLost` | [`RejectDisposition::Park`] |
13//! | `Reauthenticate` | `Unauthenticated` | [`RejectDisposition::Park`] |
14//!
15//! A transient reject applied nothing, so the op stays durable and the next
16//! session re-sends it. Origin deduplicates a re-sent op by its HLC. An
17//! unauthenticated session ends with a fresh handshake, and the next session
18//! re-sends the op.
19
20use nodedb_types::sync::wire::array::{ArrayRejectClass, ArrayRejectReason};
21
22/// The fate of a locally-originated op Origin refused.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum RejectDisposition {
25    /// Keep the op. Announce the array's schema again, then re-send the op.
26    ResendSchema,
27    /// The reject names this replica's catch-up cursor, not a local op. Ask
28    /// Origin for a snapshot. The pending queue is untouched.
29    CatchUp,
30    /// Origin can never accept the op. Remove it from the pending queue.
31    Drop,
32    /// Keep the op durable but stop sending it until the next session.
33    Park,
34}
35
36/// Map a reject reason to its disposition.
37pub fn reject_disposition(reason: ArrayRejectReason) -> RejectDisposition {
38    match reason.class() {
39        ArrayRejectClass::SchemaRequired => RejectDisposition::ResendSchema,
40        ArrayRejectClass::CatchUp => RejectDisposition::CatchUp,
41        ArrayRejectClass::Permanent => RejectDisposition::Drop,
42        ArrayRejectClass::Transient | ArrayRejectClass::Reauthenticate => RejectDisposition::Park,
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn schema_rejects_resend_the_schema() {
52        assert_eq!(
53            reject_disposition(ArrayRejectReason::ArrayUnknown),
54            RejectDisposition::ResendSchema
55        );
56        assert_eq!(
57            reject_disposition(ArrayRejectReason::SchemaTooNew),
58            RejectDisposition::ResendSchema
59        );
60    }
61
62    #[test]
63    fn retention_floor_requests_a_catch_up() {
64        assert_eq!(
65            reject_disposition(ArrayRejectReason::RetentionFloor),
66            RejectDisposition::CatchUp
67        );
68    }
69
70    #[test]
71    fn permanent_rejects_drop_the_op() {
72        for reason in [
73            ArrayRejectReason::ShapeInvalid,
74            ArrayRejectReason::EngineRefused,
75            ArrayRejectReason::PermissionDenied,
76        ] {
77            assert_eq!(reject_disposition(reason), RejectDisposition::Drop);
78        }
79    }
80
81    #[test]
82    fn transient_and_auth_rejects_park_the_op() {
83        for reason in [
84            ArrayRejectReason::EngineUnavailable,
85            ArrayRejectReason::SnapshotCapacity,
86            ArrayRejectReason::SnapshotBufferLost,
87            ArrayRejectReason::Unauthenticated,
88        ] {
89            assert_eq!(reject_disposition(reason), RejectDisposition::Park);
90        }
91    }
92}