Skip to main content

remem/memory/scope_cleanup/
refs.rs

1use anyhow::{anyhow, bail, Context, Result};
2use serde::Serialize;
3use std::collections::HashSet;
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
7#[serde(rename_all = "kebab-case")]
8pub enum ScopeObjectKind {
9    Memory,
10    Candidate,
11    Workstream,
12    SessionSummary,
13}
14
15impl ScopeObjectKind {
16    pub fn as_str(self) -> &'static str {
17        match self {
18            Self::Memory => "memory",
19            Self::Candidate => "candidate",
20            Self::Workstream => "workstream",
21            Self::SessionSummary => "session-summary",
22        }
23    }
24
25    fn parse(value: &str) -> Option<Self> {
26        match value {
27            "memory" | "mem" => Some(Self::Memory),
28            "candidate" | "memory-candidate" => Some(Self::Candidate),
29            "workstream" | "ws" => Some(Self::Workstream),
30            "session-summary" | "summary" => Some(Self::SessionSummary),
31            _ => None,
32        }
33    }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
37pub struct ObjectRef {
38    pub kind: ScopeObjectKind,
39    pub id: i64,
40}
41
42impl ObjectRef {
43    pub fn memory(id: i64) -> Self {
44        Self {
45            kind: ScopeObjectKind::Memory,
46            id,
47        }
48    }
49
50    pub fn parse(value: &str) -> Result<Self> {
51        let value = value.trim();
52        let Some((kind, id)) = value.split_once(':') else {
53            bail!(
54                "object ref must include a kind prefix, e.g. memory:123 or workstream:18: {value}"
55            );
56        };
57        let kind = ScopeObjectKind::parse(kind.trim())
58            .ok_or_else(|| anyhow!("unsupported object ref kind: {}", kind.trim()))?;
59        let id = id
60            .trim()
61            .parse::<i64>()
62            .with_context(|| format!("invalid object ref id: {value}"))?;
63        if id <= 0 {
64            bail!("object ref id must be positive: {value}");
65        }
66        Ok(Self { kind, id })
67    }
68}
69
70impl fmt::Display for ObjectRef {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "{}:{}", self.kind.as_str(), self.id)
73    }
74}
75
76pub fn parse_object_refs(values: &[String]) -> Result<Vec<ObjectRef>> {
77    let mut refs = Vec::new();
78    let mut seen = HashSet::new();
79    for value in values {
80        for token in value.split(|ch: char| ch.is_whitespace() || ch == ',') {
81            let token = token.trim();
82            if token.is_empty() {
83                continue;
84            }
85            let object_ref = ObjectRef::parse(token)?;
86            if seen.insert(object_ref) {
87                refs.push(object_ref);
88            }
89        }
90    }
91    Ok(refs)
92}
93
94pub fn memory_refs_from_ids(ids: &[i64]) -> Result<Vec<ObjectRef>> {
95    let mut refs = Vec::new();
96    let mut seen = HashSet::new();
97    for id in ids {
98        if *id <= 0 {
99            bail!("memory id must be positive: {id}");
100        }
101        let object_ref = ObjectRef::memory(*id);
102        if seen.insert(object_ref) {
103            refs.push(object_ref);
104        }
105    }
106    Ok(refs)
107}