Some checks failed
CI / Rustfmt (pull_request) Successful in 19s
CI / Cargo Audit & Deny (pull_request) Successful in 33s
CI / Security Blocking Checks (pull_request) Successful in 5s
CI / Web Blocking Checks (pull_request) Successful in 49s
CI / Web Advisory Checks (pull_request) Successful in 33s
CI / Clippy (pull_request) Has been cancelled
CI / Security Advisory Checks (pull_request) Has been cancelled
CI / Tests (pull_request) Has been cancelled
321 lines
9.2 KiB
Rust
321 lines
9.2 KiB
Rust
//! Repository layer for database operations
|
|
//!
|
|
//! This module provides the repository pattern for all database entities in Attune.
|
|
//! Repositories abstract database operations and provide a clean interface for CRUD
|
|
//! operations and queries.
|
|
//!
|
|
//! # Architecture
|
|
//!
|
|
//! - Each entity has its own repository module (e.g., `pack`, `action`, `trigger`)
|
|
//! - Repositories use SQLx for database operations
|
|
//! - Transaction support is provided through SQLx's transaction types
|
|
//! - All operations return `Result<T, Error>` for consistent error handling
|
|
//!
|
|
//! # Example
|
|
//!
|
|
//! ```rust,no_run
|
|
//! use attune_common::repositories::{PackRepository, FindByRef};
|
|
//! use attune_common::db::Database;
|
|
//!
|
|
//! async fn example(db: &Database) -> attune_common::Result<()> {
|
|
//! if let Some(pack) = PackRepository::find_by_ref(db.pool(), "core").await? {
|
|
//! println!("Found pack: {}", pack.label);
|
|
//! }
|
|
//! Ok(())
|
|
//! }
|
|
//! ```
|
|
|
|
use sqlx::{Executor, Postgres, Transaction};
|
|
|
|
pub mod action;
|
|
pub mod analytics;
|
|
pub mod artifact;
|
|
pub mod entity_history;
|
|
pub mod event;
|
|
pub mod execution;
|
|
pub mod execution_admission;
|
|
pub mod identity;
|
|
pub mod inquiry;
|
|
pub mod key;
|
|
pub mod notification;
|
|
pub mod pack;
|
|
pub mod pack_test;
|
|
pub mod queue_stats;
|
|
pub mod rule;
|
|
pub mod runtime;
|
|
pub mod runtime_version;
|
|
pub mod trigger;
|
|
pub mod workflow;
|
|
|
|
// Re-export repository types
|
|
pub use action::{ActionRepository, PolicyRepository};
|
|
pub use analytics::AnalyticsRepository;
|
|
pub use artifact::{ArtifactRepository, ArtifactVersionRepository};
|
|
pub use entity_history::EntityHistoryRepository;
|
|
pub use event::{EnforcementRepository, EventRepository};
|
|
pub use execution::ExecutionRepository;
|
|
pub use execution_admission::ExecutionAdmissionRepository;
|
|
pub use identity::{IdentityRepository, PermissionAssignmentRepository, PermissionSetRepository};
|
|
pub use inquiry::InquiryRepository;
|
|
pub use key::KeyRepository;
|
|
pub use notification::NotificationRepository;
|
|
pub use pack::PackRepository;
|
|
pub use pack_test::PackTestRepository;
|
|
pub use queue_stats::QueueStatsRepository;
|
|
pub use rule::RuleRepository;
|
|
pub use runtime::{RuntimeRepository, WorkerRepository};
|
|
pub use runtime_version::RuntimeVersionRepository;
|
|
pub use trigger::{SensorRepository, TriggerRepository};
|
|
pub use workflow::{WorkflowDefinitionRepository, WorkflowExecutionRepository};
|
|
|
|
/// Explicit patch operation for update inputs where callers must distinguish
|
|
/// between "leave unchanged", "set value", and "clear to NULL".
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum Patch<T> {
|
|
Set(T),
|
|
Clear,
|
|
}
|
|
|
|
/// Type alias for database connection/transaction
|
|
pub type DbConnection<'c> = &'c mut Transaction<'c, Postgres>;
|
|
|
|
/// Base repository trait providing common functionality
|
|
///
|
|
/// This trait is not meant to be used directly, but serves as a foundation
|
|
/// for specific repository implementations.
|
|
pub trait Repository {
|
|
/// The entity type this repository manages
|
|
type Entity;
|
|
|
|
/// Get the name of the table for this repository
|
|
fn table_name() -> &'static str;
|
|
}
|
|
|
|
/// Trait for repositories that support finding by ID
|
|
#[async_trait::async_trait]
|
|
pub trait FindById: Repository {
|
|
/// Find an entity by its ID
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `executor` - Database executor (pool or transaction)
|
|
/// * `id` - The ID to search for
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Ok(Some(entity))` if found
|
|
/// * `Ok(None)` if not found
|
|
/// * `Err(error)` on database error
|
|
async fn find_by_id<'e, E>(executor: E, id: i64) -> crate::Result<Option<Self::Entity>>
|
|
where
|
|
E: Executor<'e, Database = Postgres> + 'e;
|
|
|
|
/// Get an entity by its ID, returning an error if not found
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `executor` - Database executor (pool or transaction)
|
|
/// * `id` - The ID to search for
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Ok(entity)` if found
|
|
/// * `Err(NotFound)` if not found
|
|
/// * `Err(error)` on database error
|
|
async fn get_by_id<'e, E>(executor: E, id: i64) -> crate::Result<Self::Entity>
|
|
where
|
|
E: Executor<'e, Database = Postgres> + 'e,
|
|
{
|
|
Self::find_by_id(executor, id)
|
|
.await?
|
|
.ok_or_else(|| crate::Error::not_found(Self::table_name(), "id", id.to_string()))
|
|
}
|
|
}
|
|
|
|
/// Trait for repositories that support finding by reference
|
|
#[async_trait::async_trait]
|
|
pub trait FindByRef: Repository {
|
|
/// Find an entity by its reference string
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `executor` - Database executor (pool or transaction)
|
|
/// * `ref_str` - The reference string to search for
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Ok(Some(entity))` if found
|
|
/// * `Ok(None)` if not found
|
|
/// * `Err(error)` on database error
|
|
async fn find_by_ref<'e, E>(executor: E, ref_str: &str) -> crate::Result<Option<Self::Entity>>
|
|
where
|
|
E: Executor<'e, Database = Postgres> + 'e;
|
|
|
|
/// Get an entity by its reference, returning an error if not found
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `executor` - Database executor (pool or transaction)
|
|
/// * `ref_str` - The reference string to search for
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Ok(entity)` if found
|
|
/// * `Err(NotFound)` if not found
|
|
/// * `Err(error)` on database error
|
|
async fn get_by_ref<'e, E>(executor: E, ref_str: &str) -> crate::Result<Self::Entity>
|
|
where
|
|
E: Executor<'e, Database = Postgres> + 'e,
|
|
{
|
|
Self::find_by_ref(executor, ref_str)
|
|
.await?
|
|
.ok_or_else(|| crate::Error::not_found(Self::table_name(), "ref", ref_str))
|
|
}
|
|
}
|
|
|
|
/// Trait for repositories that support listing all entities
|
|
#[async_trait::async_trait]
|
|
pub trait List: Repository {
|
|
/// List all entities
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `executor` - Database executor (pool or transaction)
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Ok(Vec<entity>)` - List of all entities
|
|
/// * `Err(error)` on database error
|
|
async fn list<'e, E>(executor: E) -> crate::Result<Vec<Self::Entity>>
|
|
where
|
|
E: Executor<'e, Database = Postgres> + 'e;
|
|
}
|
|
|
|
/// Trait for repositories that support creating entities
|
|
#[async_trait::async_trait]
|
|
pub trait Create: Repository {
|
|
/// Input type for creating a new entity
|
|
type CreateInput;
|
|
|
|
/// Create a new entity
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `executor` - Database executor (pool or transaction)
|
|
/// * `input` - The data for creating the entity
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Ok(entity)` - The created entity
|
|
/// * `Err(error)` on database error or validation failure
|
|
async fn create<'e, E>(executor: E, input: Self::CreateInput) -> crate::Result<Self::Entity>
|
|
where
|
|
E: Executor<'e, Database = Postgres> + 'e;
|
|
}
|
|
|
|
/// Trait for repositories that support updating entities
|
|
#[async_trait::async_trait]
|
|
pub trait Update: Repository {
|
|
/// Input type for updating an entity
|
|
type UpdateInput;
|
|
|
|
/// Update an existing entity by ID
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `executor` - Database executor (pool or transaction)
|
|
/// * `id` - The ID of the entity to update
|
|
/// * `input` - The data for updating the entity
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Ok(entity)` - The updated entity
|
|
/// * `Err(NotFound)` if the entity doesn't exist
|
|
/// * `Err(error)` on database error or validation failure
|
|
async fn update<'e, E>(
|
|
executor: E,
|
|
id: i64,
|
|
input: Self::UpdateInput,
|
|
) -> crate::Result<Self::Entity>
|
|
where
|
|
E: Executor<'e, Database = Postgres> + 'e;
|
|
}
|
|
|
|
/// Trait for repositories that support deleting entities
|
|
#[async_trait::async_trait]
|
|
pub trait Delete: Repository {
|
|
/// Delete an entity by ID
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `executor` - Database executor (pool or transaction)
|
|
/// * `id` - The ID of the entity to delete
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Ok(true)` if the entity was deleted
|
|
/// * `Ok(false)` if the entity didn't exist
|
|
/// * `Err(error)` on database error
|
|
async fn delete<'e, E>(executor: E, id: i64) -> crate::Result<bool>
|
|
where
|
|
E: Executor<'e, Database = Postgres> + 'e;
|
|
}
|
|
|
|
/// Helper struct for pagination parameters
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Pagination {
|
|
/// Page number (0-based)
|
|
pub page: i64,
|
|
/// Number of items per page
|
|
pub per_page: i64,
|
|
}
|
|
|
|
impl Pagination {
|
|
/// Create a new Pagination instance
|
|
pub fn new(page: i64, per_page: i64) -> Self {
|
|
Self { page, per_page }
|
|
}
|
|
|
|
/// Calculate the OFFSET for SQL queries
|
|
pub fn offset(&self) -> i64 {
|
|
self.page * self.per_page
|
|
}
|
|
|
|
/// Get the LIMIT for SQL queries
|
|
pub fn limit(&self) -> i64 {
|
|
self.per_page
|
|
}
|
|
}
|
|
|
|
impl Default for Pagination {
|
|
fn default() -> Self {
|
|
Self {
|
|
page: 0,
|
|
per_page: 50,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_pagination() {
|
|
let p = Pagination::new(0, 10);
|
|
assert_eq!(p.offset(), 0);
|
|
assert_eq!(p.limit(), 10);
|
|
|
|
let p = Pagination::new(2, 10);
|
|
assert_eq!(p.offset(), 20);
|
|
assert_eq!(p.limit(), 10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pagination_default() {
|
|
let p = Pagination::default();
|
|
assert_eq!(p.page, 0);
|
|
assert_eq!(p.per_page, 50);
|
|
}
|
|
}
|