Skip to main content

stackless_cloud/
checkpoint.rs

1//! Shared checkpoint kind policy for cloud substrates.
2//!
3//! Known ephemeral kinds (`action`, `source-ref` when it owns no checkout) are
4//! Gone / Ok on observe/destroy. Unknown kinds fail closed so a typo or old
5//! binary cannot drop a journal row while leaving a live resource.
6
7use stackless_core::substrate::ACTION_RESOURCE_KIND;
8
9/// Resource kinds that own nothing destructible on every cloud substrate.
10pub fn is_ephemeral_resource_kind(kind: &str) -> bool {
11    kind == ACTION_RESOURCE_KIND || kind == "source-ref"
12}
13
14/// Parse a checkpoint payload: empty → `Ok(None)` (legacy fallback);
15/// non-empty malformed → `Err(detail)`; valid → `Ok(Some(T))`.
16pub fn parse_payload<T: serde::de::DeserializeOwned>(payload: &str) -> Result<Option<T>, String> {
17    let trimmed = payload.trim();
18    if trimmed.is_empty() {
19        return Ok(None);
20    }
21    serde_json::from_str(trimmed)
22        .map(Some)
23        .map_err(|err| format!("malformed checkpoint payload: {err}"))
24}
25
26#[cfg(test)]
27mod tests {
28    use super::*;
29    use serde::Deserialize;
30
31    #[derive(Debug, Deserialize, PartialEq)]
32    struct Sample {
33        id: String,
34    }
35
36    #[test]
37    fn empty_payload_is_none() {
38        assert_eq!(parse_payload::<Sample>("").unwrap(), None);
39        assert_eq!(parse_payload::<Sample>("  ").unwrap(), None);
40    }
41
42    #[test]
43    fn valid_payload_parses() {
44        let got = parse_payload::<Sample>(r#"{"id":"x"}"#).unwrap();
45        assert_eq!(got, Some(Sample { id: "x".into() }));
46    }
47
48    #[test]
49    fn malformed_nonempty_errors() {
50        assert!(parse_payload::<Sample>("{").is_err());
51    }
52
53    #[test]
54    fn ephemeral_kinds() {
55        assert!(is_ephemeral_resource_kind("action"));
56        assert!(is_ephemeral_resource_kind("source-ref"));
57        assert!(!is_ephemeral_resource_kind("render-service"));
58    }
59}