remem/memory/scope_cleanup/
refs.rs1use 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}