pub mod action;
pub mod audit;
pub mod ctl;
pub mod drift;
pub mod envs;
pub mod explain;
pub mod lint;
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum PollDecision {
KeepPolling,
Success,
WaitForGreenTimeout,
DispatchRollback,
}
pub(crate) fn decide_poll(
status: &str,
health: &str,
elapsed_secs: u64,
wait_for_green_secs: Option<u64>,
auto_rollback_secs: Option<u64>,
wait_for_green_timeout_emitted: bool,
) -> PollDecision {
if crate::app::deploy_settled_green(status, health) {
return PollDecision::Success;
}
if let Some(d) = auto_rollback_secs {
if elapsed_secs >= d {
return PollDecision::DispatchRollback;
}
}
if let Some(d) = wait_for_green_secs {
if elapsed_secs >= d && !wait_for_green_timeout_emitted {
return PollDecision::WaitForGreenTimeout;
}
}
PollDecision::KeepPolling
}
pub(crate) fn json_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c => out.push(c),
}
}
out.push('"');
out
}
pub(crate) fn cli_esc(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\t', "\\t")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decide_poll_green_plus_ready_returns_success_regardless_of_deadlines() {
assert_eq!(
decide_poll("Ready", "Green", 600, Some(300), Some(600), false),
PollDecision::Success
);
assert_eq!(
decide_poll("Ready", "Ok", 600, Some(300), Some(600), false),
PollDecision::Success
);
}
#[test]
fn decide_poll_green_during_updating_is_not_success() {
assert_eq!(
decide_poll("Updating", "Green", 5, Some(300), Some(600), false),
PollDecision::KeepPolling
);
}
#[test]
fn decide_poll_keep_polling_before_any_deadline() {
assert_eq!(
decide_poll("Updating", "Red", 60, Some(300), Some(600), false),
PollDecision::KeepPolling
);
}
#[test]
fn decide_poll_wait_for_green_only_emits_timeout_once() {
assert_eq!(
decide_poll("Updating", "Red", 301, Some(300), None, false),
PollDecision::WaitForGreenTimeout
);
assert_eq!(
decide_poll("Updating", "Red", 302, Some(300), None, true),
PollDecision::KeepPolling
);
}
#[test]
fn decide_poll_rollback_wins_when_both_deadlines_passed() {
assert_eq!(
decide_poll("Updating", "Red", 600, Some(300), Some(600), true),
PollDecision::DispatchRollback
);
}
#[test]
fn decide_poll_wait_then_rollback_sequence() {
assert_eq!(
decide_poll("Updating", "Red", 301, Some(300), Some(600), false),
PollDecision::WaitForGreenTimeout
);
assert_eq!(
decide_poll("Updating", "Red", 600, Some(300), Some(600), true),
PollDecision::DispatchRollback
);
}
#[test]
fn decide_poll_rollback_only_no_intermediate_emission() {
assert_eq!(
decide_poll("Updating", "Red", 300, None, Some(300), false),
PollDecision::DispatchRollback
);
}
#[test]
fn cli_esc_escapes_quotes_and_backslashes() {
assert_eq!(cli_esc("hello"), "hello");
assert_eq!(cli_esc("a\"b"), "a\\\"b");
assert_eq!(cli_esc("a\\b"), "a\\\\b");
assert_eq!(cli_esc("a\nb"), "a\\nb");
assert_eq!(cli_esc("a\tb"), "a\\tb");
}
#[test]
fn json_string_wraps_in_quotes_and_escapes() {
assert_eq!(json_string(""), "\"\"");
assert_eq!(json_string("hello"), "\"hello\"");
assert_eq!(json_string("a\"b"), "\"a\\\"b\"");
let s = "line1\nline2 \"with quotes\"";
let escaped = json_string(s);
let parsed: String =
serde_yml::from_str(&escaped).expect("hand-rolled JSON should parse as YAML");
assert_eq!(parsed, s);
}
}