use crate::error::{CliError, CliResult};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Phase {
Snapshot,
Cdc,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReplicationState {
pub phase: Phase,
pub snapshot_done: bool,
pub position: Value,
}
impl ReplicationState {
pub fn to_value(&self) -> CliResult<Value> {
serde_json::to_value(self)
.map_err(|e| CliError::Internal(format!("replication state serialize: {e}")))
}
pub fn from_value(v: Value) -> CliResult<Self> {
serde_json::from_value(v)
.map_err(|e| CliError::Config(format!("replication state parse: {e}")))
}
}
pub fn marker_key(pipeline_name: &str) -> String {
format!("{pipeline_name}::__replication__")
}
pub fn cdc_state_key(pipeline_name: &str) -> String {
crate::executor::build_state_key(pipeline_name, "cdc", None)
}
#[derive(Debug, PartialEq, Eq)]
pub enum Plan {
Bootstrap,
ResumeSnapshot,
ResumeCdc,
}
pub fn plan_from_marker(marker: Option<&ReplicationState>) -> Plan {
match marker {
None => Plan::Bootstrap,
Some(s) if s.snapshot_done => Plan::ResumeCdc,
Some(_) => Plan::ResumeSnapshot,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn marker_round_trips() {
let s = ReplicationState {
phase: Phase::Cdc,
snapshot_done: true,
position: json!({ "last_lsn": "0/16A4F88" }),
};
let back = ReplicationState::from_value(s.to_value().unwrap()).unwrap();
assert_eq!(back, s);
}
#[test]
fn keys_have_expected_shape() {
assert_eq!(marker_key("orders"), "orders::__replication__");
assert_eq!(cdc_state_key("orders"), "orders::cdc");
}
#[test]
fn plan_decisions() {
assert_eq!(plan_from_marker(None), Plan::Bootstrap);
let snap = ReplicationState {
phase: Phase::Snapshot,
snapshot_done: false,
position: json!(null),
};
assert_eq!(plan_from_marker(Some(&snap)), Plan::ResumeSnapshot);
let done = ReplicationState {
phase: Phase::Cdc,
snapshot_done: true,
position: json!(null),
};
assert_eq!(plan_from_marker(Some(&done)), Plan::ResumeCdc);
}
#[test]
fn marker_key_is_valid_state_key() {
faucet_core::state::validate_state_key(&marker_key("orders")).unwrap();
faucet_core::state::validate_state_key(&cdc_state_key("orders")).unwrap();
}
}