Skip to main content

perfgate_server/storage/
mod.rs

1//! Storage trait and implementations for baseline persistence.
2//!
3//! This module provides the [`BaselineStore`] trait for abstracting storage
4//! operations and implementations for different backends.
5
6mod artifacts;
7pub mod fleet;
8mod key_store;
9mod memory;
10mod postgres;
11mod sqlite;
12
13pub use artifacts::ObjectArtifactStore;
14pub use fleet::{FleetStore, InMemoryFleetStore};
15pub use key_store::{InMemoryKeyStore, KeyRecord, KeyStore, SqliteKeyStore, hash_key, key_prefix};
16pub use memory::InMemoryStore;
17pub use postgres::PostgresStore;
18pub use sqlite::SqliteStore;
19pub(crate) use sqlite::open_configured_connection as open_configured_sqlite_connection;
20
21use crate::error::StoreError;
22use crate::models::{
23    AuditEvent, BaselineRecord, BaselineVersion, DecisionRecord, ListAuditEventsQuery,
24    ListAuditEventsResponse, ListBaselinesQuery, ListBaselinesResponse, ListDecisionsQuery,
25    ListDecisionsResponse, ListVerdictsQuery, ListVerdictsResponse, PoolMetrics,
26    PruneDecisionsResponse, VerdictRecord,
27};
28use async_trait::async_trait;
29use chrono::{DateTime, Utc};
30
31/// Metadata for a stored artifact object.
32#[derive(Debug, Clone)]
33pub struct ArtifactMeta {
34    /// Object path/key.
35    pub path: String,
36    /// Last-modified timestamp (if available from the backend).
37    pub last_modified: DateTime<Utc>,
38    /// Size in bytes.
39    pub size: u64,
40}
41
42/// Trait for storing raw artifacts (receipts).
43#[async_trait]
44pub trait ArtifactStore: std::fmt::Debug + Send + Sync {
45    /// Stores an artifact at the given path.
46    async fn put(&self, path: &str, data: Vec<u8>) -> Result<(), StoreError>;
47
48    /// Retrieves an artifact from the given path.
49    async fn get(&self, path: &str) -> Result<Vec<u8>, StoreError>;
50
51    /// Deletes an artifact from the given path.
52    async fn delete(&self, path: &str) -> Result<(), StoreError>;
53
54    /// Lists all objects under the given prefix, returning their metadata.
55    async fn list(&self, prefix: Option<&str>) -> Result<Vec<ArtifactMeta>, StoreError>;
56}
57
58/// Trait for baseline storage operations.
59///
60/// This trait abstracts the storage layer, allowing different backends
61/// (in-memory, SQLite, PostgreSQL) to be used interchangeably.
62#[async_trait]
63pub trait BaselineStore: Send + Sync {
64    /// Stores a new baseline record.
65    async fn create(&self, record: &BaselineRecord) -> Result<(), StoreError>;
66
67    /// Retrieves a baseline by project, benchmark, and version.
68    async fn get(
69        &self,
70        project: &str,
71        benchmark: &str,
72        version: &str,
73    ) -> Result<Option<BaselineRecord>, StoreError>;
74
75    /// Retrieves the latest baseline for a project and benchmark.
76    async fn get_latest(
77        &self,
78        project: &str,
79        benchmark: &str,
80    ) -> Result<Option<BaselineRecord>, StoreError>;
81
82    /// Lists baselines with optional filtering.
83    async fn list(
84        &self,
85        project: &str,
86        query: &ListBaselinesQuery,
87    ) -> Result<ListBaselinesResponse, StoreError>;
88
89    /// Updates an existing baseline record.
90    async fn update(&self, record: &BaselineRecord) -> Result<(), StoreError>;
91
92    /// Deletes a baseline (soft delete).
93    async fn delete(
94        &self,
95        project: &str,
96        benchmark: &str,
97        version: &str,
98    ) -> Result<bool, StoreError>;
99
100    /// Permanently removes a deleted baseline.
101    async fn hard_delete(
102        &self,
103        project: &str,
104        benchmark: &str,
105        version: &str,
106    ) -> Result<bool, StoreError>;
107
108    /// Lists all versions for a benchmark.
109    async fn list_versions(
110        &self,
111        project: &str,
112        benchmark: &str,
113    ) -> Result<Vec<BaselineVersion>, StoreError>;
114
115    /// Checks if the storage backend is healthy.
116    async fn health_check(&self) -> Result<StorageHealth, StoreError>;
117
118    /// Returns the backend type name.
119    fn backend_type(&self) -> &'static str;
120
121    /// Returns connection pool metrics, if the backend uses a pool.
122    ///
123    /// The default implementation returns `None`, which is appropriate for
124    /// backends without a connection pool (e.g., in-memory or SQLite).
125    fn pool_metrics(&self) -> Option<PoolMetrics> {
126        None
127    }
128
129    /// Stores a new verdict record.
130    async fn create_verdict(&self, record: &VerdictRecord) -> Result<(), StoreError>;
131
132    /// Lists verdicts with optional filtering.
133    async fn list_verdicts(
134        &self,
135        project: &str,
136        query: &ListVerdictsQuery,
137    ) -> Result<ListVerdictsResponse, StoreError>;
138
139    /// Stores a new performance decision record.
140    async fn create_decision(&self, record: &DecisionRecord) -> Result<(), StoreError>;
141
142    /// Retrieves the latest performance decision for a project.
143    async fn latest_decision(&self, project: &str) -> Result<Option<DecisionRecord>, StoreError>;
144
145    /// Lists performance decisions with optional filtering.
146    async fn list_decisions(
147        &self,
148        project: &str,
149        query: &ListDecisionsQuery,
150    ) -> Result<ListDecisionsResponse, StoreError>;
151
152    /// Prunes performance decision records created before a cutoff.
153    async fn prune_decisions(
154        &self,
155        project: &str,
156        older_than: DateTime<Utc>,
157        dry_run: bool,
158    ) -> Result<PruneDecisionsResponse, StoreError>;
159}
160
161/// Trait for append-only audit event storage.
162///
163/// This trait abstracts audit log persistence, allowing different backends
164/// to store and query audit events.
165#[async_trait]
166pub trait AuditStore: Send + Sync {
167    /// Appends a new audit event to the log.
168    async fn log_event(&self, event: &AuditEvent) -> Result<(), StoreError>;
169
170    /// Lists audit events with optional filtering.
171    async fn list_events(
172        &self,
173        query: &ListAuditEventsQuery,
174    ) -> Result<ListAuditEventsResponse, StoreError>;
175}
176
177/// Storage backend health status.
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum StorageHealth {
180    /// Storage is healthy and operational
181    Healthy,
182    /// Storage is degraded but functional
183    Degraded,
184    /// Storage is unavailable
185    Unhealthy,
186}
187
188impl StorageHealth {
189    /// Returns the string representation.
190    pub fn as_str(&self) -> &'static str {
191        match self {
192            Self::Healthy => "healthy",
193            Self::Degraded => "degraded",
194            Self::Unhealthy => "unhealthy",
195        }
196    }
197}
198
199impl std::fmt::Display for StorageHealth {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        write!(f, "{}", self.as_str())
202    }
203}