Skip to main content

kernel/event/record/
roadmap.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! What a resident did to a plan node: the one payload the five
7//! `roadmap_*` kinds share.
8
9use serde::{Deserialize, Serialize};
10
11use crate::NodeId;
12use crate::locator::Locator;
13use crate::plan::StopCause;
14
15/// `roadmap_claimed`, `roadmap_split`, `roadmap_finished`,
16/// `roadmap_released` and `roadmap_blocked`: who moved which node, and
17/// the step. The five lines share `by`, `node` and `verb` and differ
18/// only in what follows the verb, so one struct holds them and the verb
19/// selects the step.
20///
21/// `node` reads back through [`NodeId::parse`]; a line whose node is not
22/// an index is refused, because a reader that skipped it would count a
23/// held node as free.
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct RoadmapMoved {
26    pub by: String,
27    pub node: NodeId,
28    #[serde(flatten)]
29    pub step: RoadmapStep,
30}
31
32/// The step a `roadmap_*` line records, spelled by its `verb` key.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(tag = "verb", rename_all = "snake_case")]
35pub enum RoadmapStep {
36    Claimed {
37        item: String,
38    },
39    Split {
40        children: Vec<String>,
41    },
42    Finished {
43        item: String,
44        evidence: Locator,
45    },
46    Released {
47        item: String,
48        why: StopCause,
49        line: String,
50    },
51    Blocked {
52        item: String,
53        why: StopCause,
54        line: String,
55    },
56}
57
58#[cfg(test)]
59#[allow(clippy::unwrap_used, reason = "test code")]
60mod tests {
61    use super::*;
62    use crate::event::Payload;
63
64    /// The bytes `collab::claim_effect` wrote by hand for a claim and a
65    /// block, and the claim read back whole.
66    #[test]
67    fn a_roadmap_line_writes_the_keys_the_hand_written_map_wrote() {
68        let claimed = RoadmapMoved {
69            by: "lab/parser".to_owned(),
70            node: NodeId::parse("2.3").unwrap(),
71            step: RoadmapStep::Claimed {
72                item: "the lexer".to_owned(),
73            },
74        };
75        let blocked = RoadmapMoved {
76            by: "lab/parser".to_owned(),
77            node: NodeId::parse("2").unwrap(),
78            step: RoadmapStep::Blocked {
79                item: "the lexer".to_owned(),
80                why: StopCause::Stalled { repeats: 3 },
81                line: "stalled".to_owned(),
82            },
83        };
84        let bytes =
85            |record: &RoadmapMoved| serde_json::to_string(&Payload::of(record).unwrap()).unwrap();
86        assert_eq!(
87            (bytes(&claimed), bytes(&blocked)),
88            (
89                r#"{"by":"lab/parser","item":"the lexer","node":"2.3","verb":"claimed"}"#.to_owned(),
90                r#"{"by":"lab/parser","item":"the lexer","line":"stalled","node":"2","verb":"blocked","why":{"stalled":{"repeats":3}}}"#.to_owned(),
91            )
92        );
93        assert_eq!(
94            Payload::of(&claimed)
95                .unwrap()
96                .read::<RoadmapMoved>()
97                .unwrap(),
98            claimed
99        );
100    }
101}