axum_gate/permissions/application_validator.rs
1use super::{PermissionCollisionChecker, ValidationReport};
2use crate::errors::{Error, Result};
3use crate::permissions::PermissionsError;
4use tracing::info;
5
6/// High-level builder pattern validator for application startup validation.
7///
8/// This is a high-level interface for validating permissions from multiple sources
9/// during application initialization. It provides an ergonomic API for collecting
10/// permissions incrementally before validation.
11///
12/// ## Use Cases
13///
14/// - **Application startup**: Validate permissions loaded from config, database, etc.
15/// - **Simple validation workflows**: Need basic validation with automatic logging
16/// - **Builder pattern preference**: Want to incrementally add permissions from different sources
17/// - **One-time validation**: Don't need post-validation analysis
18///
19/// ## Compared to PermissionCollisionChecker
20///
21/// - **State**: Stateless builder - consumed during validation
22/// - **Usage**: Builder pattern with incremental permission addition
23/// - **Methods**: Focus on building and validating, no post-validation introspection
24/// - **Lifecycle**: Single-use - transforms into validation report
25/// - **Logging**: Automatically logs validation results
26///
27/// For runtime validation or when you need to analyze collision details after validation,
28/// use [`PermissionCollisionChecker`] directly.
29///
30/// # See Also
31///
32/// - [`PermissionCollisionChecker`] - Low-level validator with detailed analysis capabilities
33///
34/// # Examples
35///
36/// ## Application startup validation
37///
38/// ```
39/// use axum_gate::permissions::ApplicationValidator;
40///
41/// # fn load_config_permissions() -> Vec<String> { vec!["user:read".to_string()] }
42/// # async fn load_db_permissions() -> Result<Vec<String>, Box<dyn std::error::Error>> { Ok(vec!["admin:write".to_string()]) }
43/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
44/// // Collect permissions from multiple sources during startup
45/// let config_permissions = load_config_permissions();
46/// let db_permissions = load_db_permissions().await?;
47///
48/// let report = ApplicationValidator::new()
49/// .add_permissions(config_permissions)
50/// .add_permissions(db_permissions)
51/// .add_permission("system:health") // Add individual permissions
52/// .validate()?; // Automatically logs results
53///
54/// if report.is_valid() {
55/// println!("✓ All permissions validated - server can start");
56/// } else {
57/// return Err(format!("Permission validation failed: {}", report.summary()).into());
58/// }
59/// # Ok(())
60/// # }
61/// ```
62///
63/// ## Simple validation workflow
64///
65/// ```
66/// use axum_gate::permissions::ApplicationValidator;
67///
68/// // For simple cases where you just need pass/fail validation
69/// let report = ApplicationValidator::new()
70/// .add_permissions(["user:read", "user:write", "admin:delete"])
71/// .validate()?;
72///
73/// // Report is automatically logged, just check if valid
74/// if !report.is_valid() {
75/// panic!("Invalid permissions detected during startup");
76/// }
77/// # Ok::<(), axum_gate::errors::Error>(())
78/// ```
79///
80/// ## Comparison with PermissionCollisionChecker
81///
82/// ```
83/// use axum_gate::permissions::{ApplicationValidator, PermissionCollisionChecker};
84///
85/// let permissions = vec!["user:read".to_string(), "user:write".to_string()];
86///
87/// // ApplicationValidator: Builder pattern, single-use, automatic logging
88/// let report1 = ApplicationValidator::new()
89/// .add_permissions(permissions.clone())
90/// .validate()?; // Validator is consumed here
91/// // Can't use validator anymore, but don't need to
92///
93/// // PermissionCollisionChecker: Direct instantiation, reusable, manual control
94/// let mut checker = PermissionCollisionChecker::new(permissions);
95/// let report2 = checker.validate()?; // Checker is still available
96///
97/// // Can continue using checker for analysis
98/// if !report2.is_valid() {
99/// let conflicts = checker.get_conflicting_permissions("user:read");
100/// println!("Conflicts found: {:?}", conflicts);
101/// }
102/// # Ok::<(), axum_gate::errors::Error>(())
103/// ```
104pub struct ApplicationValidator {
105 permissions: Vec<String>,
106}
107
108impl ApplicationValidator {
109 /// Creates a new application validator.
110 pub fn new() -> Self {
111 Self {
112 permissions: Vec::new(),
113 }
114 }
115
116 /// Add permissions from an iterator of string-like types.
117 ///
118 /// # Arguments
119 ///
120 /// * `permissions` - Iterator of items that can be converted to String
121 pub fn add_permissions<I, S>(mut self, permissions: I) -> Self
122 where
123 I: IntoIterator<Item = S>,
124 S: Into<String>,
125 {
126 self.permissions
127 .extend(permissions.into_iter().map(|s| s.into()));
128 self
129 }
130
131 /// Add permissions from a vector of strings.
132 ///
133 /// This is a convenience method for adding permissions that are already
134 /// in String format.
135 ///
136 /// # Arguments
137 ///
138 /// * `permissions` - Vector of permission strings
139 pub fn add_permission_strings(mut self, permissions: Vec<String>) -> Self {
140 self.permissions.extend(permissions);
141 self
142 }
143
144 /// Add a single permission string.
145 ///
146 /// # Arguments
147 ///
148 /// * `permission` - A single permission string to add
149 pub fn add_permission<S: Into<String>>(mut self, permission: S) -> Self {
150 self.permissions.push(permission.into());
151 self
152 }
153
154 /// Validate all permissions and return detailed report.
155 ///
156 /// This method performs validation and logs results automatically.
157 /// It returns a ValidationReport containing all validation details,
158 /// regardless of whether validation passed or failed.
159 ///
160 /// # Returns
161 ///
162 /// * `Ok(ValidationReport)` - Complete validation report
163 /// * `Err(axum_gate::errors::Error)` - Validation process failed
164 pub fn validate(self) -> Result<ValidationReport> {
165 let mut checker = PermissionCollisionChecker::new(self.permissions);
166 let report = checker.validate().map_err(|e| {
167 Error::Permissions(PermissionsError::collision(
168 0,
169 vec![format!("Permission validation process failed: {}", e)],
170 ))
171 })?;
172
173 report.log_results();
174
175 if report.is_valid() {
176 info!("✓ Permission validation completed successfully");
177 }
178
179 Ok(report)
180 }
181
182 /// Returns the current number of permissions to be validated.
183 pub fn permission_count(&self) -> usize {
184 self.permissions.len()
185 }
186}
187
188impl Default for ApplicationValidator {
189 fn default() -> Self {
190 Self::new()
191 }
192}
193
194#[cfg(test)]
195#[allow(clippy::unwrap_used)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn application_validator_basic() {
201 let result = ApplicationValidator::new()
202 .add_permissions(["user:read", "user:write"])
203 .add_permission("admin:delete")
204 .validate();
205
206 assert!(result.is_ok());
207 let report = result.unwrap();
208 assert!(report.is_valid());
209 }
210
211 #[test]
212 fn application_validator_with_duplicates() {
213 let result = ApplicationValidator::new()
214 .add_permissions(["user:read", "user:read"])
215 .validate();
216
217 assert!(result.is_ok());
218 let report = result.unwrap();
219 assert!(!report.is_valid());
220 assert!(!report.duplicates().is_empty());
221 }
222}