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}