webgates_core/permissions/application_validator.rs
1use crate::errors_core::Result;
2use crate::permissions::collision_checker::PermissionCollisionChecker;
3use crate::permissions::errors::PermissionsError;
4use crate::permissions::validation_report::ValidationReport;
5use tracing::info;
6
7/// High-level builder for validating your application's permission list.
8///
9/// `ApplicationValidator` collects permission strings from one or more sources,
10/// validates them for duplicates and hash collisions, and returns a
11/// [`ValidationReport`].
12///
13/// This is the easiest API to use at startup or in tests when you want to say:
14/// "here is the full permission surface of my application; make sure it is safe."
15///
16/// If you need reusable post-validation inspection helpers, use
17/// [`PermissionCollisionChecker`] directly.
18///
19/// # Examples
20///
21/// ```
22/// use webgates_core::permissions::application_validator::ApplicationValidator;///
23/// # fn load_config_permissions() -> Vec<String> { vec!["user:read".to_string()] }
24/// # async fn load_db_permissions() -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> { Ok(vec!["admin:write".to_string()]) }
25/// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
26/// let config_permissions = load_config_permissions();
27/// let db_permissions = load_db_permissions().await?;
28///
29/// let report = ApplicationValidator::new()
30/// .add_permissions(config_permissions)
31/// .add_permissions(db_permissions)
32/// .add_permission("system:health")
33/// .validate()?;
34///
35/// assert!(report.is_valid());
36/// # Ok(())
37/// # }
38/// ```
39///
40/// ```
41/// use webgates_core::permissions::application_validator::ApplicationValidator;
42/// use webgates_core::permissions::collision_checker::PermissionCollisionChecker;
43///
44/// let permissions = vec!["user:read".to_string(), "user:write".to_string()];
45///
46/// let report = ApplicationValidator::new()
47/// .add_permissions(permissions.clone())
48/// .validate()
49/// .map_err(|error| error.to_string())?;
50///
51/// let mut checker = PermissionCollisionChecker::new(permissions);
52/// let inspected = checker.validate().map_err(|error| error.to_string())?;
53///
54/// assert_eq!(report.is_valid(), inspected.is_valid());
55/// # Ok::<(), String>(())
56/// ```
57pub struct ApplicationValidator {
58 permissions: Vec<String>,
59}
60
61impl ApplicationValidator {
62 /// Creates a new empty validator.
63 pub fn new() -> Self {
64 Self {
65 permissions: Vec::new(),
66 }
67 }
68
69 /// Adds permissions from an iterator of string-like values.
70 ///
71 /// # Arguments
72 ///
73 /// * `permissions` - Iterator of items that can be converted to String
74 pub fn add_permissions<I, S>(mut self, permissions: I) -> Self
75 where
76 I: IntoIterator<Item = S>,
77 S: Into<String>,
78 {
79 self.permissions
80 .extend(permissions.into_iter().map(|s| s.into()));
81 self
82 }
83
84 /// Adds permissions from a vector of owned strings.
85 ///
86 /// This is a convenience method for callers that already have `Vec<String>`.
87 ///
88 /// # Arguments
89 ///
90 /// * `permissions` - Vector of permission strings
91 pub fn add_permission_strings(mut self, permissions: Vec<String>) -> Self {
92 self.permissions.extend(permissions);
93 self
94 }
95
96 /// Adds one permission string.
97 ///
98 /// # Arguments
99 ///
100 /// * `permission` - A single permission string to add
101 pub fn add_permission<S: Into<String>>(mut self, permission: S) -> Self {
102 self.permissions.push(permission.into());
103 self
104 }
105
106 /// Validates all collected permissions and returns a detailed report.
107 ///
108 /// This method performs validation and logs results automatically. It
109 /// returns a [`ValidationReport`] whether validation succeeds or fails,
110 /// unless the validation process itself encounters an unexpected error.
111 ///
112 /// # Returns
113 ///
114 /// * `Ok(ValidationReport)` - Complete validation report
115 /// * `Err(webgates_core::errors::Error)` - Validation process failed
116 pub fn validate(self) -> Result<ValidationReport, PermissionsError> {
117 let mut checker = PermissionCollisionChecker::new(self.permissions);
118 let report = checker.validate().map_err(|e| {
119 PermissionsError::collision(
120 0,
121 vec![format!("Permission validation process failed: {}", e)],
122 )
123 })?;
124
125 report.log_results();
126
127 if report.is_valid() {
128 info!("✓ Permission validation completed successfully");
129 }
130
131 Ok(report)
132 }
133
134 /// Returns how many permission strings are currently queued for validation.
135 pub fn permission_count(&self) -> usize {
136 self.permissions.len()
137 }
138}
139
140impl Default for ApplicationValidator {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn application_validator_basic() {
152 let result = ApplicationValidator::new()
153 .add_permissions(["user:read", "user:write"])
154 .add_permission("admin:delete")
155 .validate();
156
157 match result {
158 Ok(report) => assert!(report.is_valid()),
159 Err(error) => panic!("validation unexpectedly failed: {error}"),
160 }
161 }
162
163 #[test]
164 fn application_validator_with_duplicates() {
165 let result = ApplicationValidator::new()
166 .add_permissions(["user:read", "user:read"])
167 .validate();
168
169 match result {
170 Ok(report) => {
171 assert!(!report.is_valid());
172 assert!(!report.duplicates().is_empty());
173 }
174 Err(error) => panic!("validation unexpectedly failed: {error}"),
175 }
176 }
177}