feat(persistence): initialize SQLite database module
- Add domain types: RelayId newtype and RepositoryError enum - Implement SqliteRelayLabelRepository with in-memory test support - Create relay_labels migration with SQLx compile-time verification - Add comprehensive integration test suite (266 lines) Ref: T006 (specs/001-modbus-relay-control)
This commit is contained in:
@@ -34,3 +34,5 @@
|
||||
//! - Architecture: `specs/constitution.md` - Hexagonal Architecture principles
|
||||
//! - Type design: `specs/001-modbus-relay-control/types-design.md`
|
||||
//! - Domain specification: `specs/001-modbus-relay-control/spec.md`
|
||||
|
||||
pub mod relay;
|
||||
|
||||
9
src/domain/relay/mod.rs
Normal file
9
src/domain/relay/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! Relay domain module.
|
||||
//!
|
||||
//! This module contains the core domain logic for relay control and management,
|
||||
//! including relay types, repository abstractions, and business rules.
|
||||
|
||||
/// Repository trait and error types for relay persistence.
|
||||
pub mod repository;
|
||||
/// Domain types for relay identification and control.
|
||||
pub mod types;
|
||||
15
src/domain/relay/repository.rs
Normal file
15
src/domain/relay/repository.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use super::types::RelayId;
|
||||
|
||||
/// Errors that can occur during repository operations.
|
||||
///
|
||||
/// This enum provides structured error handling for all data persistence
|
||||
/// operations related to relay management.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RepositoryError {
|
||||
/// A database operation failed with the given error message.
|
||||
#[error("Database error: {0}")]
|
||||
DatabaseError(String),
|
||||
/// The requested relay was not found in the repository.
|
||||
#[error("Relay not found: {0}")]
|
||||
NotFound(RelayId),
|
||||
}
|
||||
14
src/domain/relay/types.rs
Normal file
14
src/domain/relay/types.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
/// Unique identifier for a relay in the system.
|
||||
///
|
||||
/// Uses the newtype pattern to provide type safety and prevent mixing relay IDs
|
||||
/// with other numeric values. Valid values range from 0-255, corresponding to
|
||||
/// individual relay channels in the Modbus controller.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct RelayId(u8);
|
||||
|
||||
impl std::fmt::Display for RelayId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
@@ -74,3 +74,5 @@
|
||||
//! - Architecture: `specs/constitution.md` - Dependency Inversion Principle
|
||||
//! - Implementation: `specs/001-modbus-relay-control/plan.md` - Infrastructure tasks
|
||||
//! - Modbus docs: `docs/Modbus_POE_ETH_Relay.md` - Hardware protocol specification
|
||||
|
||||
pub mod persistence;
|
||||
|
||||
7
src/infrastructure/persistence/mod.rs
Normal file
7
src/infrastructure/persistence/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
//! Persistence layer implementations.
|
||||
//!
|
||||
//! This module contains the concrete implementations of repository traits
|
||||
//! for data persistence, including SQLite-based storage for relay labels.
|
||||
|
||||
/// `SQLite` repository implementation for relay labels.
|
||||
pub mod sqlite_repository;
|
||||
64
src/infrastructure/persistence/sqlite_repository.rs
Normal file
64
src/infrastructure/persistence/sqlite_repository.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use crate::domain::relay::repository::RepositoryError;
|
||||
|
||||
/// `SQLite` implementation of the relay label repository.
|
||||
///
|
||||
/// This repository manages persistent storage of relay labels using `SQLite`,
|
||||
/// with automatic schema migrations via `SQLx`.
|
||||
pub struct SqliteRelayLabelRepository {
|
||||
/// The `SQLite` connection pool for database operations.
|
||||
pool: SqlitePool,
|
||||
}
|
||||
|
||||
impl SqliteRelayLabelRepository {
|
||||
/// Creates a new `SQLite` relay label repository.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `db_path` - The `SQLite` database path or connection string (e.g., `"sqlite://data.db"`)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `RepositoryError::DatabaseError` if the connection fails or migrations cannot be applied.
|
||||
pub async fn new(db_path: &str) -> Result<Self, RepositoryError> {
|
||||
let pool = SqlitePool::connect(db_path)
|
||||
.await
|
||||
.map_err(|e| RepositoryError::DatabaseError(e.to_string()))?;
|
||||
let repo = Self { pool };
|
||||
repo.run_migrations().await?;
|
||||
Ok(repo)
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying connection pool.
|
||||
///
|
||||
/// This is primarily used for testing to verify schema and constraints.
|
||||
#[must_use]
|
||||
pub const fn pool(&self) -> &SqlitePool {
|
||||
&self.pool
|
||||
}
|
||||
|
||||
/// Creates a new in-memory `SQLite` relay label repository.
|
||||
///
|
||||
/// This is useful for testing and ephemeral data storage.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `RepositoryError::DatabaseError` if the in-memory database cannot be created.
|
||||
pub async fn in_memory() -> Result<Self, RepositoryError> {
|
||||
Self::new("sqlite::memory:").await
|
||||
}
|
||||
|
||||
/// Runs all pending database migrations.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `RepositoryError::DatabaseError` if migrations fail to apply.
|
||||
async fn run_migrations(&self) -> Result<(), RepositoryError> {
|
||||
sqlx::migrate!()
|
||||
.run(&self.pool)
|
||||
.await
|
||||
.map_err(|e| RepositoryError::DatabaseError(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user