allframe_core/arch.rs
1//! Clean Architecture enforcement
2//!
3//! This module provides compile-time and runtime support for Clean
4//! Architecture. AllFrame enforces architectural boundaries at compile time,
5//! preventing violations like handlers directly accessing repositories.
6//!
7//! ## Architecture Layers
8//!
9//! ```text
10//! ┌─────────────────────────────────────┐
11//! │ Layer 4: Handler │ ← HTTP/gRPC/GraphQL endpoints
12//! │ (depends on Use Cases) │
13//! └─────────────────────────────────────┘
14//! ↓
15//! ┌─────────────────────────────────────┐
16//! │ Layer 3: Use Case │ ← Application logic
17//! │ (depends on Repositories, Domain) │
18//! └─────────────────────────────────────┘
19//! ↓
20//! ┌─────────────────────────────────────┐
21//! │ Layer 2: Repository │ ← Data access
22//! │ (depends on Domain) │
23//! └─────────────────────────────────────┘
24//! ↓
25//! ┌─────────────────────────────────────┐
26//! │ Layer 1: Domain │ ← Business logic (no deps)
27//! └─────────────────────────────────────┘
28//! ```
29//!
30//! ## Usage
31//!
32//! ```rust,ignore
33//! use allframe_core::arch::{domain, repository, use_case, handler};
34//!
35//! // Domain entities - pure business logic
36//! #[domain]
37//! struct User {
38//! id: String,
39//! email: String,
40//! }
41//!
42//! // Repository - data access
43//! #[repository]
44//! trait UserRepository: Send + Sync {
45//! async fn find(&self, id: &str) -> Option<User>;
46//! }
47//!
48//! // Use case - application logic
49//! #[use_case]
50//! struct GetUserUseCase {
51//! repo: Arc<dyn UserRepository>, // ✅ Can depend on repository
52//! }
53//!
54//! // Handler - entry point
55//! #[handler]
56//! struct GetUserHandler {
57//! use_case: Arc<GetUserUseCase>, // ✅ Can depend on use case
58//! // repo: Arc<dyn UserRepository>, // ❌ COMPILE ERROR - cannot skip use case!
59//! }
60//! ```
61
62// Re-export the architecture macros
63#[cfg(feature = "di")]
64pub use allframe_macros::{domain, handler, repository, use_case};
65
66/// Layer metadata for runtime introspection
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct LayerMetadata {
69 /// Layer name
70 pub layer_name: &'static str,
71 /// Layer number (1-4)
72 pub layer_number: u8,
73 /// Type name
74 pub type_name: &'static str,
75}
76
77impl LayerMetadata {
78 /// Create new layer metadata
79 pub const fn new(layer_name: &'static str, layer_number: u8, type_name: &'static str) -> Self {
80 Self {
81 layer_name,
82 layer_number,
83 type_name,
84 }
85 }
86
87 /// Get the layer name
88 pub fn layer_name(&self) -> &'static str {
89 self.layer_name
90 }
91
92 /// Get the layer number
93 pub fn layer_number(&self) -> u8 {
94 self.layer_number
95 }
96
97 /// Get the type name
98 pub fn type_name(&self) -> &'static str {
99 self.type_name
100 }
101
102 /// Check if this layer can depend on another layer
103 pub fn can_depend_on(&self, other_layer: &str) -> bool {
104 let other_number = match other_layer {
105 "domain" => 1,
106 "repository" => 2,
107 "use_case" => 3,
108 "handler" => 4,
109 _ => return false,
110 };
111
112 // A layer can only depend on layers with lower numbers
113 self.layer_number > other_number
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_layer_metadata_creation() {
123 let metadata = LayerMetadata::new("domain", 1, "User");
124
125 assert_eq!(metadata.layer_name(), "domain");
126 assert_eq!(metadata.layer_number(), 1);
127 assert_eq!(metadata.type_name(), "User");
128 }
129
130 #[test]
131 fn test_can_depend_on() {
132 let domain = LayerMetadata::new("domain", 1, "User");
133 let repository = LayerMetadata::new("repository", 2, "UserRepository");
134 let use_case = LayerMetadata::new("use_case", 3, "GetUserUseCase");
135 let handler = LayerMetadata::new("handler", 4, "GetUserHandler");
136
137 // Domain cannot depend on anything
138 assert!(!domain.can_depend_on("repository"));
139 assert!(!domain.can_depend_on("use_case"));
140 assert!(!domain.can_depend_on("handler"));
141
142 // Repository can only depend on domain
143 assert!(repository.can_depend_on("domain"));
144 assert!(!repository.can_depend_on("use_case"));
145 assert!(!repository.can_depend_on("handler"));
146
147 // Use case can depend on repository and domain
148 assert!(use_case.can_depend_on("domain"));
149 assert!(use_case.can_depend_on("repository"));
150 assert!(!use_case.can_depend_on("handler"));
151
152 // Handler can depend on all lower layers
153 assert!(handler.can_depend_on("domain"));
154 assert!(handler.can_depend_on("repository"));
155 assert!(handler.can_depend_on("use_case"));
156 }
157}