refactor: restructure domain crate by bounded context
This commit is contained in:
17
crates/domain/src/common/errors.rs
Normal file
17
crates/domain/src/common/errors.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DomainError {
|
||||
#[error("Not found: {0}")]
|
||||
NotFound(String),
|
||||
#[error("Conflict: {0}")]
|
||||
Conflict(String),
|
||||
#[error("Unauthorized: {0}")]
|
||||
Unauthorized(String),
|
||||
#[error("Forbidden: {0}")]
|
||||
Forbidden(String),
|
||||
#[error("Validation error: {0}")]
|
||||
Validation(String),
|
||||
#[error("Quota exceeded: {0}")]
|
||||
QuotaExceeded(String),
|
||||
#[error("Internal error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
49
crates/domain/src/common/events.rs
Normal file
49
crates/domain/src/common/events.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use crate::common::value_objects::{DateTimeStamp, SystemId};
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DomainEvent {
|
||||
AssetIngested {
|
||||
asset_id: SystemId,
|
||||
owner_user_id: SystemId,
|
||||
timestamp: DateTimeStamp,
|
||||
},
|
||||
MetadataUpdated {
|
||||
asset_id: SystemId,
|
||||
updated_by: SystemId,
|
||||
timestamp: DateTimeStamp,
|
||||
},
|
||||
AssetDeleted {
|
||||
asset_id: SystemId,
|
||||
deleted_by: SystemId,
|
||||
timestamp: DateTimeStamp,
|
||||
},
|
||||
ShareCreated {
|
||||
scope_id: SystemId,
|
||||
shareable_id: SystemId,
|
||||
created_by: SystemId,
|
||||
timestamp: DateTimeStamp,
|
||||
},
|
||||
ShareRevoked {
|
||||
scope_id: SystemId,
|
||||
revoked_by: SystemId,
|
||||
timestamp: DateTimeStamp,
|
||||
},
|
||||
SidecarSyncRequested {
|
||||
asset_id: SystemId,
|
||||
timestamp: DateTimeStamp,
|
||||
},
|
||||
JobEnqueued {
|
||||
job_id: SystemId,
|
||||
job_type: String,
|
||||
timestamp: DateTimeStamp,
|
||||
},
|
||||
JobCompleted {
|
||||
job_id: SystemId,
|
||||
timestamp: DateTimeStamp,
|
||||
},
|
||||
JobFailed {
|
||||
job_id: SystemId,
|
||||
error: String,
|
||||
timestamp: DateTimeStamp,
|
||||
},
|
||||
}
|
||||
4
crates/domain/src/common/mod.rs
Normal file
4
crates/domain/src/common/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod errors;
|
||||
pub mod events;
|
||||
pub mod ports;
|
||||
pub mod value_objects;
|
||||
8
crates/domain/src/common/ports.rs
Normal file
8
crates/domain/src/common/ports.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use async_trait::async_trait;
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::common::events::DomainEvent;
|
||||
|
||||
#[async_trait]
|
||||
pub trait EventPublisher: Send + Sync {
|
||||
async fn publish(&self, event: DomainEvent) -> Result<(), DomainError>;
|
||||
}
|
||||
29
crates/domain/src/common/value_objects/checksum.rs
Normal file
29
crates/domain/src/common/value_objects/checksum.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Checksum(String);
|
||||
|
||||
impl Checksum {
|
||||
pub fn new(hex: impl Into<String>) -> Result<Self, DomainError> {
|
||||
let hex = hex.into().to_lowercase();
|
||||
if hex.len() != 64 {
|
||||
return Err(DomainError::Validation(
|
||||
format!("Checksum must be 64 hex characters, got {}", hex.len()),
|
||||
));
|
||||
}
|
||||
if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Err(DomainError::Validation(
|
||||
"Checksum contains non-hex characters".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(Self(hex))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Checksum {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
20
crates/domain/src/common/value_objects/date_time_stamp.rs
Normal file
20
crates/domain/src/common/value_objects/date_time_stamp.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DateTimeStamp(DateTime<Utc>);
|
||||
|
||||
impl DateTimeStamp {
|
||||
pub fn now() -> Self { Self(Utc::now()) }
|
||||
pub fn from_datetime(dt: DateTime<Utc>) -> Self { Self(dt) }
|
||||
pub fn as_datetime(&self) -> &DateTime<Utc> { &self.0 }
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DateTimeStamp {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.to_rfc3339())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DateTime<Utc>> for DateTimeStamp {
|
||||
fn from(dt: DateTime<Utc>) -> Self { Self(dt) }
|
||||
}
|
||||
22
crates/domain/src/common/value_objects/email.rs
Normal file
22
crates/domain/src/common/value_objects/email.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Email(String);
|
||||
|
||||
impl Email {
|
||||
pub fn new(value: impl Into<String>) -> Result<Self, DomainError> {
|
||||
let value = value.into().trim().to_lowercase();
|
||||
if value.is_empty() || !value.contains('@') {
|
||||
return Err(DomainError::Validation("Invalid email address".to_string()));
|
||||
}
|
||||
Ok(Self(value))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Email {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
43
crates/domain/src/common/value_objects/filter_criteria.rs
Normal file
43
crates/domain/src/common/value_objects/filter_criteria.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum FilterOperator {
|
||||
Equals,
|
||||
NotEquals,
|
||||
Contains,
|
||||
Between,
|
||||
GreaterThan,
|
||||
LessThan,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct FilterCondition {
|
||||
pub field: String,
|
||||
pub op: FilterOperator,
|
||||
pub value: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum FilterCriteria {
|
||||
And(Vec<FilterCriteria>),
|
||||
Or(Vec<FilterCriteria>),
|
||||
Condition(FilterCondition),
|
||||
}
|
||||
|
||||
impl FilterCriteria {
|
||||
pub fn and(conditions: Vec<FilterCriteria>) -> Self {
|
||||
Self::And(conditions)
|
||||
}
|
||||
|
||||
pub fn or(conditions: Vec<FilterCriteria>) -> Self {
|
||||
Self::Or(conditions)
|
||||
}
|
||||
|
||||
pub fn condition(field: impl Into<String>, op: FilterOperator, value: serde_json::Value) -> Self {
|
||||
Self::Condition(FilterCondition {
|
||||
field: field.into(),
|
||||
op,
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
15
crates/domain/src/common/value_objects/mod.rs
Normal file
15
crates/domain/src/common/value_objects/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
mod checksum;
|
||||
mod date_time_stamp;
|
||||
mod email;
|
||||
pub mod filter_criteria;
|
||||
mod password;
|
||||
mod structured_data;
|
||||
mod system_id;
|
||||
|
||||
pub use checksum::Checksum;
|
||||
pub use date_time_stamp::DateTimeStamp;
|
||||
pub use email::Email;
|
||||
pub use filter_criteria::{FilterCondition, FilterCriteria, FilterOperator};
|
||||
pub use password::PasswordHash;
|
||||
pub use structured_data::{MetadataValue, StructuredData};
|
||||
pub use system_id::SystemId;
|
||||
14
crates/domain/src/common/value_objects/password.rs
Normal file
14
crates/domain/src/common/value_objects/password.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
// Manual Debug — redacts hash to prevent it appearing in logs
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PasswordHash(String);
|
||||
|
||||
impl std::fmt::Debug for PasswordHash {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("PasswordHash").field(&"[redacted]").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PasswordHash {
|
||||
pub fn from_hash(hash: String) -> Self { Self(hash) }
|
||||
pub fn as_str(&self) -> &str { &self.0 }
|
||||
}
|
||||
54
crates/domain/src/common/value_objects/structured_data.rs
Normal file
54
crates/domain/src/common/value_objects/structured_data.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum MetadataValue {
|
||||
String(String),
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
Boolean(bool),
|
||||
Null,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct StructuredData(HashMap<String, MetadataValue>);
|
||||
|
||||
impl StructuredData {
|
||||
pub fn new() -> Self { Self(HashMap::new()) }
|
||||
|
||||
pub fn insert(&mut self, key: impl Into<String>, value: MetadataValue) {
|
||||
self.0.insert(key.into(), value);
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<&MetadataValue> {
|
||||
self.0.get(key)
|
||||
}
|
||||
|
||||
pub fn get_string(&self, key: &str) -> Option<&str> {
|
||||
match self.0.get(key) {
|
||||
Some(MetadataValue::String(s)) => Some(s.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> impl Iterator<Item = &String> {
|
||||
self.0.keys()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool { self.0.is_empty() }
|
||||
pub fn len(&self) -> usize { self.0.len() }
|
||||
|
||||
pub fn merge_from(&mut self, other: StructuredData) {
|
||||
self.0.extend(other.0);
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &str) -> Option<MetadataValue> {
|
||||
self.0.remove(key)
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &HashMap<String, MetadataValue> { &self.0 }
|
||||
}
|
||||
|
||||
impl Default for StructuredData {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
24
crates/domain/src/common/value_objects/system_id.rs
Normal file
24
crates/domain/src/common/value_objects/system_id.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct SystemId(Uuid);
|
||||
|
||||
impl SystemId {
|
||||
pub fn new() -> Self { Self(Uuid::new_v4()) }
|
||||
pub fn from_uuid(id: Uuid) -> Self { Self(id) }
|
||||
pub fn as_uuid(&self) -> &Uuid { &self.0 }
|
||||
}
|
||||
|
||||
impl Default for SystemId {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SystemId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uuid> for SystemId {
|
||||
fn from(id: Uuid) -> Self { Self(id) }
|
||||
}
|
||||
Reference in New Issue
Block a user