use serde::{Deserialize, Serialize};
pub const CONSISTENCY_TIER_ANNOTATION: &str = "engenho.io/consistency-tier";
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConsistencyTier {
Strong,
EventualGossip,
DurableStream,
Content,
}
impl Default for ConsistencyTier {
fn default() -> Self {
Self::Strong
}
}
impl ConsistencyTier {
#[must_use]
pub fn from_annotation(s: &str) -> Self {
match s.trim().to_ascii_lowercase().replace('-', "_").as_str() {
"strong" => Self::Strong,
"eventual_gossip" | "eventualgossip" | "gossip" => Self::EventualGossip,
"durable_stream" | "durablestream" | "stream" => Self::DurableStream,
"content" | "content_addressed" | "contentaddressed" => Self::Content,
_ => Self::Strong, }
}
#[must_use]
pub fn as_canonical(self) -> &'static str {
match self {
Self::Strong => "strong",
Self::EventualGossip => "eventual_gossip",
Self::DurableStream => "durable_stream",
Self::Content => "content",
}
}
#[must_use]
pub fn is_strong(self) -> bool {
matches!(self, Self::Strong)
}
#[must_use]
pub fn requires_quorum(self) -> bool {
matches!(self, Self::Strong)
}
#[must_use]
pub fn persists(self) -> bool {
matches!(self, Self::Strong | Self::DurableStream)
}
}
#[must_use]
pub fn tier_from_metadata(annotations: Option<&serde_json::Value>) -> ConsistencyTier {
let Some(annotations) = annotations else {
return ConsistencyTier::Strong;
};
let Some(value) = annotations.get(CONSISTENCY_TIER_ANNOTATION) else {
return ConsistencyTier::Strong;
};
let Some(s) = value.as_str() else {
return ConsistencyTier::Strong;
};
ConsistencyTier::from_annotation(s)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn default_is_strong() {
assert_eq!(ConsistencyTier::default(), ConsistencyTier::Strong);
}
#[test]
fn from_annotation_canonical() {
assert_eq!(
ConsistencyTier::from_annotation("strong"),
ConsistencyTier::Strong
);
assert_eq!(
ConsistencyTier::from_annotation("eventual_gossip"),
ConsistencyTier::EventualGossip
);
assert_eq!(
ConsistencyTier::from_annotation("durable_stream"),
ConsistencyTier::DurableStream
);
assert_eq!(
ConsistencyTier::from_annotation("content"),
ConsistencyTier::Content
);
}
#[test]
fn from_annotation_kebab_case() {
assert_eq!(
ConsistencyTier::from_annotation("eventual-gossip"),
ConsistencyTier::EventualGossip
);
assert_eq!(
ConsistencyTier::from_annotation("durable-stream"),
ConsistencyTier::DurableStream
);
}
#[test]
fn from_annotation_case_insensitive() {
assert_eq!(
ConsistencyTier::from_annotation("EventualGossip"),
ConsistencyTier::EventualGossip
);
assert_eq!(
ConsistencyTier::from_annotation("DURABLE_STREAM"),
ConsistencyTier::DurableStream
);
}
#[test]
fn from_annotation_aliases() {
assert_eq!(
ConsistencyTier::from_annotation("gossip"),
ConsistencyTier::EventualGossip
);
assert_eq!(
ConsistencyTier::from_annotation("stream"),
ConsistencyTier::DurableStream
);
assert_eq!(
ConsistencyTier::from_annotation("content_addressed"),
ConsistencyTier::Content
);
}
#[test]
fn from_annotation_unknown_defaults_to_strong() {
assert_eq!(
ConsistencyTier::from_annotation("weird-unknown"),
ConsistencyTier::Strong
);
assert_eq!(
ConsistencyTier::from_annotation(""),
ConsistencyTier::Strong
);
}
#[test]
fn as_canonical_round_trips() {
for t in [
ConsistencyTier::Strong,
ConsistencyTier::EventualGossip,
ConsistencyTier::DurableStream,
ConsistencyTier::Content,
] {
assert_eq!(ConsistencyTier::from_annotation(t.as_canonical()), t);
}
}
#[test]
fn is_strong_only_for_strong() {
assert!(ConsistencyTier::Strong.is_strong());
assert!(!ConsistencyTier::EventualGossip.is_strong());
assert!(!ConsistencyTier::DurableStream.is_strong());
assert!(!ConsistencyTier::Content.is_strong());
}
#[test]
fn requires_quorum_only_for_strong() {
assert!(ConsistencyTier::Strong.requires_quorum());
assert!(!ConsistencyTier::EventualGossip.requires_quorum());
assert!(!ConsistencyTier::DurableStream.requires_quorum());
assert!(!ConsistencyTier::Content.requires_quorum());
}
#[test]
fn persists_for_strong_and_durable_stream() {
assert!(ConsistencyTier::Strong.persists());
assert!(ConsistencyTier::DurableStream.persists());
assert!(!ConsistencyTier::EventualGossip.persists());
assert!(!ConsistencyTier::Content.persists());
}
#[test]
fn tier_from_metadata_annotations_map() {
let annotations = json!({"engenho.io/consistency-tier": "eventual_gossip"});
assert_eq!(
tier_from_metadata(Some(&annotations)),
ConsistencyTier::EventualGossip
);
}
#[test]
fn tier_from_metadata_returns_default_when_absent() {
let annotations = json!({"other": "value"});
assert_eq!(
tier_from_metadata(Some(&annotations)),
ConsistencyTier::Strong
);
assert_eq!(tier_from_metadata(None), ConsistencyTier::Strong);
}
#[test]
fn serde_round_trip() {
let t = ConsistencyTier::DurableStream;
let s = serde_json::to_string(&t).unwrap();
assert_eq!(s, "\"durable_stream\"");
let back: ConsistencyTier = serde_json::from_str(&s).unwrap();
assert_eq!(back, t);
}
}