1use std::fmt;
7use std::str::FromStr;
8
9use rusqlite::{Row, ToSql};
10
11use crate::error::{Result, StoreError};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum SessionStatus {
16 Open,
17 Closed,
18}
19
20impl SessionStatus {
21 #[must_use]
22 pub const fn as_str(self) -> &'static str {
23 match self {
24 Self::Open => "open",
25 Self::Closed => "closed",
26 }
27 }
28}
29
30impl fmt::Display for SessionStatus {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 f.write_str(self.as_str())
33 }
34}
35
36impl FromStr for SessionStatus {
37 type Err = StoreError;
38 fn from_str(s: &str) -> Result<Self> {
39 match s {
40 "open" => Ok(Self::Open),
41 "closed" => Ok(Self::Closed),
42 _ => Err(StoreError::Invalid("session.status")),
43 }
44 }
45}
46
47impl ToSql for SessionStatus {
48 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
49 Ok(rusqlite::types::ToSqlOutput::from(self.as_str()))
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct Session {
56 pub id: String,
57 pub created_at: i64,
58 pub policy_id: Option<String>,
59 pub status: SessionStatus,
60 pub closed_at: Option<i64>,
61}
62
63impl Session {
64 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
65 let status_str: String = row.get("status")?;
66 Ok(Self {
67 id: row.get("id")?,
68 created_at: row.get("created_at")?,
69 policy_id: row.get("policy_id")?,
70 status: status_str.parse().map_err(|_| {
71 rusqlite::Error::FromSqlConversionFailure(
72 0,
73 rusqlite::types::Type::Text,
74 "session.status".into(),
75 )
76 })?,
77 closed_at: row.get("closed_at")?,
78 })
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct Page {
85 pub id: String,
86 pub session_id: String,
87 pub url: String,
88 pub title: Option<String>,
89 pub last_token: Option<String>,
90 pub last_dom_hash: Option<String>,
91 pub last_seen_at: i64,
92 pub closed_at: Option<i64>,
93}
94
95impl Page {
96 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
97 Ok(Self {
98 id: row.get("id")?,
99 session_id: row.get("session_id")?,
100 url: row.get("url")?,
101 title: row.get("title")?,
102 last_token: row.get("last_token")?,
103 last_dom_hash: row.get("last_dom_hash")?,
104 last_seen_at: row.get("last_seen_at")?,
105 closed_at: row.get("closed_at")?,
106 })
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct StoredRef {
113 pub session_id: String,
114 pub page_id: String,
115 pub r: u32,
116 pub dom_path: String,
117 pub role: String,
118 pub content_hash: String,
119 pub created_at: i64,
120 pub retired_at: Option<i64>,
121}
122
123impl StoredRef {
124 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
125 let r_i64: i64 = row.get("ref")?;
126 Ok(Self {
127 session_id: row.get("session_id")?,
128 page_id: row.get("page_id")?,
129 r: u32::try_from(r_i64).map_err(|_| {
130 rusqlite::Error::FromSqlConversionFailure(
131 0,
132 rusqlite::types::Type::Integer,
133 "ref out of u32 range".into(),
134 )
135 })?,
136 dom_path: row.get("dom_path")?,
137 role: row.get("role")?,
138 content_hash: row.get("content_hash")?,
139 created_at: row.get("created_at")?,
140 retired_at: row.get("retired_at")?,
141 })
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct Mark {
148 pub id: String,
149 pub session_id: String,
150 pub page_id: String,
151 pub name: String,
152 pub dom_path: String,
153 pub role: Option<String>,
154 pub content_excerpt: Option<String>,
155 pub created_at: i64,
156}
157
158impl Mark {
159 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
160 Ok(Self {
161 id: row.get("id")?,
162 session_id: row.get("session_id")?,
163 page_id: row.get("page_id")?,
164 name: row.get("name")?,
165 dom_path: row.get("dom_path")?,
166 role: row.get("role")?,
167 content_excerpt: row.get("content_excerpt")?,
168 created_at: row.get("created_at")?,
169 })
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
175pub enum AnnotationTarget {
176 Ref(u32),
177 Mark(String),
178 Page,
179}
180
181impl AnnotationTarget {
182 #[must_use]
183 pub fn kind(&self) -> &'static str {
184 match self {
185 Self::Ref(_) => "ref",
186 Self::Mark(_) => "mark",
187 Self::Page => "page",
188 }
189 }
190
191 #[must_use]
192 pub fn id(&self) -> String {
193 match self {
194 Self::Ref(r) => r.to_string(),
195 Self::Mark(name) => name.clone(),
196 Self::Page => String::new(),
197 }
198 }
199
200 pub(crate) fn parse(kind: &str, id: &str) -> Result<Self> {
201 match kind {
202 "ref" => {
203 let r: u32 = id
204 .parse()
205 .map_err(|_| StoreError::Invalid("annotation.target_id (ref)"))?;
206 Ok(Self::Ref(r))
207 }
208 "mark" => Ok(Self::Mark(id.to_string())),
209 "page" => Ok(Self::Page),
210 _ => Err(StoreError::Invalid("annotation.target_kind")),
211 }
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
217pub struct Annotation {
218 pub id: String,
219 pub target: AnnotationTarget,
220 pub key: String,
221 pub value: Option<String>,
222 pub created_at: i64,
223}
224
225impl Annotation {
226 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
227 let kind: String = row.get("target_kind")?;
228 let id: String = row.get("target_id")?;
229 let target = AnnotationTarget::parse(&kind, &id).map_err(|_| {
230 rusqlite::Error::FromSqlConversionFailure(
231 0,
232 rusqlite::types::Type::Text,
233 "annotation.target".into(),
234 )
235 })?;
236 Ok(Self {
237 id: row.get("id")?,
238 target,
239 key: row.get("key")?,
240 value: row.get("value")?,
241 created_at: row.get("created_at")?,
242 })
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq)]
248pub struct Action {
249 pub id: i64,
250 pub session_id: String,
251 pub page_id: Option<String>,
252 pub primitive: String,
253 pub args_redacted: String,
254 pub args_hash: String,
255 pub before_token: Option<String>,
256 pub after_token: Option<String>,
257 pub idempotency_hit: bool,
258 pub result_summary: Option<String>,
259 pub latency_ms: i64,
260 pub group_label: Option<String>,
261 pub started_at: i64,
262 pub finished_at: i64,
263 pub error_code: Option<String>,
264}
265
266impl Action {
267 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
268 let idem: i64 = row.get("idempotency_hit")?;
269 Ok(Self {
270 id: row.get("id")?,
271 session_id: row.get("session_id")?,
272 page_id: row.get("page_id")?,
273 primitive: row.get("primitive")?,
274 args_redacted: row.get("args_redacted")?,
275 args_hash: row.get("args_hash")?,
276 before_token: row.get("before_token")?,
277 after_token: row.get("after_token")?,
278 idempotency_hit: idem != 0,
279 result_summary: row.get("result_summary")?,
280 latency_ms: row.get("latency_ms")?,
281 group_label: row.get("group_label")?,
282 started_at: row.get("started_at")?,
283 finished_at: row.get("finished_at")?,
284 error_code: row.get("error_code")?,
285 })
286 }
287}
288
289#[derive(Debug, Clone, PartialEq, Eq)]
291pub struct ActionInsert {
292 pub session_id: String,
293 pub page_id: Option<String>,
294 pub primitive: String,
295 pub args_redacted: String,
296 pub args_hash: String,
297 pub before_token: Option<String>,
298 pub after_token: Option<String>,
299 pub idempotency_hit: bool,
300 pub result_summary: Option<String>,
301 pub latency_ms: i64,
302 pub group_label: Option<String>,
303 pub started_at: i64,
304 pub finished_at: i64,
305 pub error_code: Option<String>,
306}
307
308#[derive(Debug, Clone, Default)]
310pub struct ActionFilter {
311 pub session_id: Option<String>,
312 pub page_id: Option<String>,
313 pub group_label: Option<String>,
314 pub since_started_at: Option<i64>,
315 pub limit: Option<i64>,
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
320pub struct AuthBlobMeta {
321 pub name: String,
322 pub created_at: i64,
323 pub last_used_at: Option<i64>,
324}
325
326impl AuthBlobMeta {
327 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
328 Ok(Self {
329 name: row.get("name")?,
330 created_at: row.get("created_at")?,
331 last_used_at: row.get("last_used_at")?,
332 })
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Eq)]
338pub struct SkillEntry {
339 pub name: String,
340 pub version: String,
341 pub sha: String,
342 pub manifest: String,
343 pub last_used_at: Option<i64>,
344}
345
346impl SkillEntry {
347 pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
348 Ok(Self {
349 name: row.get("name")?,
350 version: row.get("version")?,
351 sha: row.get("sha")?,
352 manifest: row.get("manifest")?,
353 last_used_at: row.get("last_used_at")?,
354 })
355 }
356}