use std::fmt;
use std::str::FromStr;
use rusqlite::{Row, ToSql};
use crate::error::{Result, StoreError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SessionStatus {
Open,
Closed,
}
impl SessionStatus {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Open => "open",
Self::Closed => "closed",
}
}
}
impl fmt::Display for SessionStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for SessionStatus {
type Err = StoreError;
fn from_str(s: &str) -> Result<Self> {
match s {
"open" => Ok(Self::Open),
"closed" => Ok(Self::Closed),
_ => Err(StoreError::Invalid("session.status")),
}
}
}
impl ToSql for SessionStatus {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::from(self.as_str()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Session {
pub id: String,
pub created_at: i64,
pub policy_id: Option<String>,
pub status: SessionStatus,
pub closed_at: Option<i64>,
}
impl Session {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
let status_str: String = row.get("status")?;
Ok(Self {
id: row.get("id")?,
created_at: row.get("created_at")?,
policy_id: row.get("policy_id")?,
status: status_str.parse().map_err(|_| {
rusqlite::Error::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
"session.status".into(),
)
})?,
closed_at: row.get("closed_at")?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Page {
pub id: String,
pub session_id: String,
pub url: String,
pub title: Option<String>,
pub last_token: Option<String>,
pub last_dom_hash: Option<String>,
pub last_seen_at: i64,
pub closed_at: Option<i64>,
}
impl Page {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
Ok(Self {
id: row.get("id")?,
session_id: row.get("session_id")?,
url: row.get("url")?,
title: row.get("title")?,
last_token: row.get("last_token")?,
last_dom_hash: row.get("last_dom_hash")?,
last_seen_at: row.get("last_seen_at")?,
closed_at: row.get("closed_at")?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoredRef {
pub session_id: String,
pub page_id: String,
pub r: u32,
pub dom_path: String,
pub role: String,
pub content_hash: String,
pub created_at: i64,
pub retired_at: Option<i64>,
}
impl StoredRef {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
let r_i64: i64 = row.get("ref")?;
Ok(Self {
session_id: row.get("session_id")?,
page_id: row.get("page_id")?,
r: u32::try_from(r_i64).map_err(|_| {
rusqlite::Error::FromSqlConversionFailure(
0,
rusqlite::types::Type::Integer,
"ref out of u32 range".into(),
)
})?,
dom_path: row.get("dom_path")?,
role: row.get("role")?,
content_hash: row.get("content_hash")?,
created_at: row.get("created_at")?,
retired_at: row.get("retired_at")?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Mark {
pub id: String,
pub session_id: String,
pub page_id: String,
pub name: String,
pub dom_path: String,
pub role: Option<String>,
pub content_excerpt: Option<String>,
pub created_at: i64,
}
impl Mark {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
Ok(Self {
id: row.get("id")?,
session_id: row.get("session_id")?,
page_id: row.get("page_id")?,
name: row.get("name")?,
dom_path: row.get("dom_path")?,
role: row.get("role")?,
content_excerpt: row.get("content_excerpt")?,
created_at: row.get("created_at")?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AnnotationTarget {
Ref(u32),
Mark(String),
Page,
}
impl AnnotationTarget {
#[must_use]
pub fn kind(&self) -> &'static str {
match self {
Self::Ref(_) => "ref",
Self::Mark(_) => "mark",
Self::Page => "page",
}
}
#[must_use]
pub fn id(&self) -> String {
match self {
Self::Ref(r) => r.to_string(),
Self::Mark(name) => name.clone(),
Self::Page => String::new(),
}
}
pub(crate) fn parse(kind: &str, id: &str) -> Result<Self> {
match kind {
"ref" => {
let r: u32 = id
.parse()
.map_err(|_| StoreError::Invalid("annotation.target_id (ref)"))?;
Ok(Self::Ref(r))
}
"mark" => Ok(Self::Mark(id.to_string())),
"page" => Ok(Self::Page),
_ => Err(StoreError::Invalid("annotation.target_kind")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Annotation {
pub id: String,
pub target: AnnotationTarget,
pub key: String,
pub value: Option<String>,
pub created_at: i64,
}
impl Annotation {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
let kind: String = row.get("target_kind")?;
let id: String = row.get("target_id")?;
let target = AnnotationTarget::parse(&kind, &id).map_err(|_| {
rusqlite::Error::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
"annotation.target".into(),
)
})?;
Ok(Self {
id: row.get("id")?,
target,
key: row.get("key")?,
value: row.get("value")?,
created_at: row.get("created_at")?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Action {
pub id: i64,
pub session_id: String,
pub page_id: Option<String>,
pub primitive: String,
pub args_redacted: String,
pub args_hash: String,
pub before_token: Option<String>,
pub after_token: Option<String>,
pub idempotency_hit: bool,
pub result_summary: Option<String>,
pub latency_ms: i64,
pub group_label: Option<String>,
pub started_at: i64,
pub finished_at: i64,
pub error_code: Option<String>,
}
impl Action {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
let idem: i64 = row.get("idempotency_hit")?;
Ok(Self {
id: row.get("id")?,
session_id: row.get("session_id")?,
page_id: row.get("page_id")?,
primitive: row.get("primitive")?,
args_redacted: row.get("args_redacted")?,
args_hash: row.get("args_hash")?,
before_token: row.get("before_token")?,
after_token: row.get("after_token")?,
idempotency_hit: idem != 0,
result_summary: row.get("result_summary")?,
latency_ms: row.get("latency_ms")?,
group_label: row.get("group_label")?,
started_at: row.get("started_at")?,
finished_at: row.get("finished_at")?,
error_code: row.get("error_code")?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActionInsert {
pub session_id: String,
pub page_id: Option<String>,
pub primitive: String,
pub args_redacted: String,
pub args_hash: String,
pub before_token: Option<String>,
pub after_token: Option<String>,
pub idempotency_hit: bool,
pub result_summary: Option<String>,
pub latency_ms: i64,
pub group_label: Option<String>,
pub started_at: i64,
pub finished_at: i64,
pub error_code: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct ActionFilter {
pub session_id: Option<String>,
pub page_id: Option<String>,
pub group_label: Option<String>,
pub since_started_at: Option<i64>,
pub limit: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuthBlobMeta {
pub name: String,
pub created_at: i64,
pub last_used_at: Option<i64>,
}
impl AuthBlobMeta {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
Ok(Self {
name: row.get("name")?,
created_at: row.get("created_at")?,
last_used_at: row.get("last_used_at")?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SkillEntry {
pub name: String,
pub version: String,
pub sha: String,
pub manifest: String,
pub last_used_at: Option<i64>,
}
impl SkillEntry {
pub(crate) fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
Ok(Self {
name: row.get("name")?,
version: row.get("version")?,
sha: row.get("sha")?,
manifest: row.get("manifest")?,
last_used_at: row.get("last_used_at")?,
})
}
}