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