use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum DeliveryMode {
#[default]
AtLeastOnce,
ExactlyOnce,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReplayGuarantee {
#[default]
NonDeterministic,
Deterministic,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SinkGuarantee {
#[default]
AtLeastOnce,
KeyedUpsert,
AtomicWatermark,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EffectivelyOnceMechanism {
AtomicWatermark,
KeyedUpsert,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "guarantee", content = "via")]
pub enum DeliveryGuarantee {
AtLeastOnce,
EffectivelyOnce(EffectivelyOnceMechanism),
}
impl std::fmt::Display for DeliveryGuarantee {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AtLeastOnce => write!(f, "at-least-once"),
Self::EffectivelyOnce(EffectivelyOnceMechanism::AtomicWatermark) => {
write!(f, "effectively-once (atomic watermark)")
}
Self::EffectivelyOnce(EffectivelyOnceMechanism::KeyedUpsert) => {
write!(f, "effectively-once (keyed upsert)")
}
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct GuaranteeInputs {
pub replay: ReplayGuarantee,
pub sink_atomic: bool,
pub keyed_upsert_configured: bool,
pub durable_state: bool,
pub dlq: bool,
}
pub fn derive_delivery_guarantee(i: &GuaranteeInputs) -> DeliveryGuarantee {
if i.sink_atomic && i.replay == ReplayGuarantee::Deterministic && i.durable_state && !i.dlq {
return DeliveryGuarantee::EffectivelyOnce(EffectivelyOnceMechanism::AtomicWatermark);
}
if i.keyed_upsert_configured {
return DeliveryGuarantee::EffectivelyOnce(EffectivelyOnceMechanism::KeyedUpsert);
}
DeliveryGuarantee::AtLeastOnce
}
const EO_MARKER: &str = "__faucet_eo";
const EO_BOOKMARK: &str = "bookmark";
const EO_SEQ: &str = "seq";
const TOKEN_WIDTH: usize = 20;
const TOKEN_BOOKMARK_SEP: char = '#';
pub fn format_token(seq: u64) -> String {
format!("{seq:0TOKEN_WIDTH$}")
}
pub fn format_token_with_bookmark(seq: u64, bookmark: Option<&Value>) -> String {
match bookmark {
Some(bm) => format!("{seq:0TOKEN_WIDTH$}{TOKEN_BOOKMARK_SEP}{bm}"),
None => format_token(seq),
}
}
pub fn parse_token(s: &str) -> Option<u64> {
let seq = match s.split_once(TOKEN_BOOKMARK_SEP) {
Some((prefix, _)) => prefix,
None => s,
};
seq.trim().parse::<u64>().ok()
}
pub fn parse_token_parts(s: &str) -> Option<(u64, Option<Value>)> {
match s.split_once(TOKEN_BOOKMARK_SEP) {
Some((prefix, suffix)) => {
let seq = prefix.trim().parse::<u64>().ok()?;
Some((seq, serde_json::from_str(suffix).ok()))
}
None => Some((s.trim().parse::<u64>().ok()?, None)),
}
}
pub fn wrap_state(bookmark: Option<&Value>, seq: u64) -> Value {
serde_json::json!({
EO_MARKER: 1,
EO_BOOKMARK: bookmark.cloned().unwrap_or(Value::Null),
EO_SEQ: seq,
})
}
pub fn unwrap_state(value: &Value) -> (Option<Value>, u64) {
if let Value::Object(map) = value
&& map.get(EO_MARKER).and_then(Value::as_u64) == Some(1)
{
let bookmark = match map.get(EO_BOOKMARK) {
None | Some(Value::Null) => None,
Some(v) => Some(v.clone()),
};
let seq = map.get(EO_SEQ).and_then(Value::as_u64).unwrap_or(0);
return (bookmark, seq);
}
let bookmark = if value.is_null() {
None
} else {
Some(value.clone())
};
(bookmark, 0)
}
pub const COMMIT_TOKEN_TABLE: &str = "_faucet_commit_token";
pub const COMMIT_TOKEN_SCOPE_COL: &str = "scope";
pub const COMMIT_TOKEN_TOKEN_COL: &str = "token";
pub const ICEBERG_SCOPE_PROP: &str = "faucet.commit-scope";
pub const ICEBERG_TOKEN_PROP: &str = "faucet.commit-token";
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn token_round_trips_and_orders_lexicographically() {
assert_eq!(format_token(42).len(), TOKEN_WIDTH);
assert_eq!(parse_token(&format_token(42)), Some(42));
assert_eq!(parse_token(&format_token(0)), Some(0));
assert_eq!(parse_token(&format_token(u64::MAX)), Some(u64::MAX));
assert!(format_token(9) < format_token(10));
assert!(format_token(2) < format_token(1000));
}
#[test]
fn parse_token_rejects_garbage() {
assert_eq!(parse_token("abc"), None);
assert_eq!(parse_token(""), None);
}
#[test]
fn wrap_then_unwrap_preserves_bookmark_and_seq() {
let bm = json!({"lsn": "0/16B2D58"});
let wrapped = wrap_state(Some(&bm), 7);
let (got_bm, got_seq) = unwrap_state(&wrapped);
assert_eq!(got_bm, Some(bm));
assert_eq!(got_seq, 7);
}
#[test]
fn wrap_none_bookmark_unwraps_to_none() {
let wrapped = wrap_state(None, 3);
let (got_bm, got_seq) = unwrap_state(&wrapped);
assert_eq!(got_bm, None);
assert_eq!(got_seq, 3);
}
#[test]
fn legacy_bare_bookmark_unwraps_with_seq_zero() {
let (bm, seq) = unwrap_state(&json!("2024-12-01"));
assert_eq!(bm, Some(json!("2024-12-01")));
assert_eq!(seq, 0);
let (bm2, seq2) = unwrap_state(&json!({"updated_at": "2024-12-01"}));
assert_eq!(bm2, Some(json!({"updated_at": "2024-12-01"})));
assert_eq!(seq2, 0);
}
#[test]
fn object_with_non_sentinel_marker_is_treated_as_bare_bookmark() {
let v = json!({"__faucet_eo": null, "offset": 500});
let (bm, seq) = unwrap_state(&v);
assert_eq!(bm, Some(v));
assert_eq!(seq, 0);
}
#[test]
fn null_value_unwraps_to_none_seq_zero() {
let (bm, seq) = unwrap_state(&json!(null));
assert_eq!(bm, None);
assert_eq!(seq, 0);
}
#[test]
fn token_with_bookmark_round_trips() {
let bm = json!({"partition_offsets": [{"topic": "t", "partition": 0, "offset": 42}]});
let token = format_token_with_bookmark(7, Some(&bm));
assert!(token.starts_with(&format_token(7)));
assert_eq!(parse_token(&token), Some(7));
let (seq, parsed_bm) = parse_token_parts(&token).unwrap();
assert_eq!(seq, 7);
assert_eq!(parsed_bm, Some(bm));
}
#[test]
fn token_with_no_bookmark_is_bare_and_back_compatible() {
assert_eq!(format_token_with_bookmark(3, None), format_token(3));
let (seq, bm) = parse_token_parts(&format_token(3)).unwrap();
assert_eq!((seq, bm), (3, None));
}
#[test]
fn token_with_bookmark_orders_lexicographically_on_prefix() {
let a = format_token_with_bookmark(9, Some(&json!({"o": 1})));
let b = format_token_with_bookmark(10, Some(&json!({"o": 2})));
assert!(a < b);
}
#[test]
fn parse_token_parts_tolerates_garbage() {
assert_eq!(parse_token_parts("abc"), None);
assert_eq!(parse_token_parts(""), None);
let (seq, bm) = parse_token_parts("00000000000000000005#{not json").unwrap();
assert_eq!((seq, bm), (5, None));
assert_eq!(parse_token("00000000000000000005#{not json"), Some(5));
}
#[test]
fn derive_guarantee_prefers_atomic_then_keyed_then_at_least_once() {
use ReplayGuarantee::*;
let base = GuaranteeInputs {
replay: Deterministic,
sink_atomic: true,
keyed_upsert_configured: false,
durable_state: true,
dlq: false,
};
assert_eq!(
derive_delivery_guarantee(&base),
DeliveryGuarantee::EffectivelyOnce(EffectivelyOnceMechanism::AtomicWatermark)
);
let non_det = GuaranteeInputs {
replay: NonDeterministic,
..base
};
assert_eq!(
derive_delivery_guarantee(&non_det),
DeliveryGuarantee::AtLeastOnce
);
let keyed = GuaranteeInputs {
keyed_upsert_configured: true,
..non_det
};
assert_eq!(
derive_delivery_guarantee(&keyed),
DeliveryGuarantee::EffectivelyOnce(EffectivelyOnceMechanism::KeyedUpsert)
);
let dlq = GuaranteeInputs {
dlq: true,
keyed_upsert_configured: true,
..base
};
assert_eq!(
derive_delivery_guarantee(&dlq),
DeliveryGuarantee::EffectivelyOnce(EffectivelyOnceMechanism::KeyedUpsert)
);
let mem_state = GuaranteeInputs {
durable_state: false,
..base
};
assert_eq!(
derive_delivery_guarantee(&mem_state),
DeliveryGuarantee::AtLeastOnce
);
}
#[test]
fn guarantee_display_is_human_readable() {
assert_eq!(DeliveryGuarantee::AtLeastOnce.to_string(), "at-least-once");
assert_eq!(
DeliveryGuarantee::EffectivelyOnce(EffectivelyOnceMechanism::AtomicWatermark)
.to_string(),
"effectively-once (atomic watermark)"
);
assert_eq!(
DeliveryGuarantee::EffectivelyOnce(EffectivelyOnceMechanism::KeyedUpsert).to_string(),
"effectively-once (keyed upsert)"
);
}
#[test]
fn capability_enums_default_to_weakest() {
assert_eq!(
ReplayGuarantee::default(),
ReplayGuarantee::NonDeterministic
);
assert_eq!(SinkGuarantee::default(), SinkGuarantee::AtLeastOnce);
}
#[test]
fn delivery_mode_serde_is_snake_case_and_defaults_at_least_once() {
assert_eq!(DeliveryMode::default(), DeliveryMode::AtLeastOnce);
assert_eq!(
serde_json::to_string(&DeliveryMode::ExactlyOnce).unwrap(),
"\"exactly_once\""
);
let m: DeliveryMode = serde_json::from_str("\"at_least_once\"").unwrap();
assert_eq!(m, DeliveryMode::AtLeastOnce);
}
}