Skip to main content

git_meta_lib/
session_handle.rs

1use crate::error::Result;
2use crate::session::Session;
3use crate::types::{MetaValue, Target, ValueType};
4
5/// A scoped handle for operations on a specific target within a session.
6///
7/// Created via [`Session::target()`]. Carries the target, email, and
8/// timestamp from the session so callers never have to pass them.
9///
10/// # Example
11///
12/// ```ignore
13/// let session = Session::discover()?;
14/// let handle = session.target(&Target::parse("commit:abc123")?);
15/// handle.set("agent:model", "claude")?;
16/// let val = handle.get_value("agent:model")?;
17/// ```
18pub struct SessionTargetHandle<'a> {
19    session: &'a Session,
20    target: Target,
21}
22
23impl<'a> SessionTargetHandle<'a> {
24    pub(crate) fn new(session: &'a Session, target: Target) -> Self {
25        Self { session, target }
26    }
27
28    /// Get a metadata value by key.
29    pub fn get_value(&self, key: &str) -> Result<Option<MetaValue>> {
30        self.session.store.get_value(&self.target, key)
31    }
32
33    /// Set a metadata value with convenience conversion.
34    ///
35    /// Accepts anything that converts to [`MetaValue`]: `&str`, `String`,
36    /// `Vec<ListEntry>`, `BTreeSet<String>`, or `MetaValue` directly.
37    ///
38    /// ```ignore
39    /// handle.set("key", "hello")?;                    // string
40    /// handle.set("key", MetaValue::String("hello".into()))?; // explicit
41    /// ```
42    ///
43    /// Uses the session's email and timestamp automatically.
44    pub fn set(&self, key: &str, value: impl Into<MetaValue>) -> Result<()> {
45        let meta_value = value.into();
46        self.session.store.set_value(
47            &self.target,
48            key,
49            &meta_value,
50            self.session.email(),
51            self.session.now(),
52        )
53    }
54
55    /// Remove a metadata key.
56    ///
57    /// Uses the session's email and timestamp automatically.
58    pub fn remove(&self, key: &str) -> Result<bool> {
59        self.session
60            .store
61            .remove(&self.target, key, self.session.email(), self.session.now())
62    }
63
64    /// Push a value onto a list.
65    ///
66    /// Uses the session's email and timestamp automatically.
67    pub fn list_push(&self, key: &str, value: &str) -> Result<()> {
68        self.session.store.list_push(
69            &self.target,
70            key,
71            value,
72            self.session.email(),
73            self.session.now(),
74        )
75    }
76
77    /// Pop a value from a list.
78    ///
79    /// Uses the session's email and timestamp automatically.
80    pub fn list_pop(&self, key: &str, value: &str) -> Result<()> {
81        self.session.store.list_pop(
82            &self.target,
83            key,
84            value,
85            self.session.email(),
86            self.session.now(),
87        )
88    }
89
90    /// Remove a list entry by index.
91    ///
92    /// Uses the session's email and timestamp automatically.
93    pub fn list_remove(&self, key: &str, index: usize) -> Result<()> {
94        self.session.store.list_remove(
95            &self.target,
96            key,
97            index,
98            self.session.email(),
99            self.session.now(),
100        )
101    }
102
103    /// Add a member to a set.
104    ///
105    /// Uses the session's email and timestamp automatically.
106    pub fn set_add(&self, key: &str, value: &str) -> Result<()> {
107        self.session.store.set_add(
108            &self.target,
109            key,
110            value,
111            self.session.email(),
112            self.session.now(),
113        )
114    }
115
116    /// Remove a member from a set.
117    ///
118    /// Uses the session's email and timestamp automatically.
119    pub fn set_remove(&self, key: &str, value: &str) -> Result<()> {
120        self.session.store.set_remove(
121            &self.target,
122            key,
123            value,
124            self.session.email(),
125            self.session.now(),
126        )
127    }
128
129    /// The target this handle is scoped to.
130    pub fn target(&self) -> &Target {
131        &self.target
132    }
133
134    /// Get all metadata for this target as typed (key, value) pairs.
135    ///
136    /// Optionally filters by key prefix (e.g., `Some("agent")` returns
137    /// all keys starting with `agent` or `agent:`).
138    ///
139    /// # Parameters
140    ///
141    /// - `prefix`: optional key prefix to filter by
142    ///
143    /// # Returns
144    ///
145    /// A vector of `(key, MetaValue)` pairs for matching metadata entries.
146    ///
147    /// # Errors
148    ///
149    /// Returns an error if the database read or deserialization fails.
150    pub fn get_all_values(&self, prefix: Option<&str>) -> Result<Vec<(String, MetaValue)>> {
151        let entries = self.session.store.get_all(&self.target, prefix)?;
152        let mut result = Vec::with_capacity(entries.len());
153        for entry in entries {
154            let meta_value = match entry.value_type {
155                ValueType::String => {
156                    let s: String =
157                        serde_json::from_str(&entry.value).unwrap_or_else(|_| entry.value.clone());
158                    MetaValue::String(s)
159                }
160                ValueType::List => {
161                    let entries = crate::list_value::parse_entries(&entry.value)?;
162                    MetaValue::List(entries)
163                }
164                ValueType::Set => {
165                    let members: Vec<String> = serde_json::from_str(&entry.value)?;
166                    MetaValue::Set(members.into_iter().collect())
167                }
168            };
169            result.push((entry.key, meta_value));
170        }
171        Ok(result)
172    }
173
174    /// Get list entries for a key on this target.
175    ///
176    /// # Parameters
177    ///
178    /// - `key`: the metadata key name
179    ///
180    /// # Returns
181    ///
182    /// A vector of [`ListEntry`](crate::list_value::ListEntry) values with
183    /// resolved content and timestamps.
184    ///
185    /// # Errors
186    ///
187    /// Returns an error if the key is missing, the value is not a list, or
188    /// the database read fails.
189    pub fn list_entries(&self, key: &str) -> Result<Vec<crate::list_value::ListEntry>> {
190        self.session.store.list_entries(&self.target, key)
191    }
192
193    /// Get authorship info (last author email and timestamp) for a key on this target.
194    ///
195    /// # Parameters
196    ///
197    /// - `key`: the metadata key name
198    ///
199    /// # Returns
200    ///
201    /// `Some(Authorship)` if the key has been modified at least once,
202    /// `None` otherwise.
203    ///
204    /// # Errors
205    ///
206    /// Returns an error if the database read fails.
207    pub fn get_authorship(&self, key: &str) -> Result<Option<crate::db::types::Authorship>> {
208        self.session.store.get_authorship(&self.target, key)
209    }
210}