fraiseql_core/security/
profiles.rs1use std::fmt;
39
40use serde::{Deserialize, Serialize};
41
42#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
44#[non_exhaustive]
45pub enum SecurityProfile {
46 #[default]
48 Standard,
49
50 Regulated,
52}
53
54impl SecurityProfile {
55 #[must_use]
57 pub const fn standard() -> Self {
58 Self::Standard
59 }
60
61 #[must_use]
63 pub const fn regulated() -> Self {
64 Self::Regulated
65 }
66
67 #[must_use]
69 pub const fn is_standard(&self) -> bool {
70 matches!(self, Self::Standard)
71 }
72
73 #[must_use]
75 pub const fn is_regulated(&self) -> bool {
76 matches!(self, Self::Regulated)
77 }
78
79 #[must_use]
81 pub const fn name(&self) -> &'static str {
82 match self {
83 Self::Standard => "STANDARD",
84 Self::Regulated => "REGULATED",
85 }
86 }
87
88 #[must_use]
90 pub const fn rate_limit_enabled(&self) -> bool {
91 true
92 }
93
94 #[must_use]
96 pub const fn audit_logging_enabled(&self) -> bool {
97 true
98 }
99
100 #[must_use]
102 pub const fn audit_field_access(&self) -> bool {
103 matches!(self, Self::Regulated)
104 }
105
106 #[must_use]
108 pub const fn sensitive_field_masking(&self) -> bool {
109 matches!(self, Self::Regulated)
110 }
111
112 #[must_use]
114 pub const fn error_detail_reduction(&self) -> bool {
115 matches!(self, Self::Regulated)
116 }
117
118 #[must_use]
120 pub const fn query_logging_for_compliance(&self) -> bool {
121 matches!(self, Self::Regulated)
122 }
123
124 #[must_use]
126 pub const fn response_size_limits(&self) -> bool {
127 matches!(self, Self::Regulated)
128 }
129
130 #[must_use]
132 pub const fn field_filtering_strict(&self) -> bool {
133 matches!(self, Self::Regulated)
134 }
135
136 #[must_use]
138 pub const fn max_response_size_bytes(&self) -> usize {
139 match self {
140 Self::Standard => usize::MAX, Self::Regulated => 1_000_000, }
143 }
144
145 #[must_use]
147 pub const fn max_query_complexity(&self) -> usize {
148 match self {
149 Self::Standard => 100_000,
150 Self::Regulated => 50_000, }
152 }
153
154 #[must_use]
156 pub const fn max_query_depth(&self) -> usize {
157 match self {
158 Self::Standard => 20,
159 Self::Regulated => 10, }
161 }
162
163 #[must_use]
165 pub const fn rate_limit_rps(&self) -> u32 {
166 match self {
167 Self::Standard => 100,
168 Self::Regulated => 10, }
170 }
171
172 #[must_use]
174 pub const fn description(&self) -> &'static str {
175 match self {
176 Self::Standard => "Basic security with rate limiting and audit logging",
177 Self::Regulated => {
178 "Full compliance with field masking, error redaction, and strict limits"
179 },
180 }
181 }
182
183 #[must_use]
185 pub fn enforced_features(&self) -> Vec<&'static str> {
186 let mut features = vec!["Rate Limiting", "Audit Logging"];
187
188 if self.is_regulated() {
189 features.extend(vec![
190 "Field-Level Audit",
191 "Sensitive Field Masking",
192 "Error Detail Reduction",
193 "Query Logging for Compliance",
194 "Response Size Limits",
195 "Strict Field Filtering",
196 ]);
197 }
198
199 features
200 }
201}
202
203impl fmt::Display for SecurityProfile {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 write!(f, "{}", self.name())
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
218 fn test_create_standard_profile() {
219 let profile = SecurityProfile::standard();
220 assert!(profile.is_standard());
221 assert!(!profile.is_regulated());
222 assert_eq!(profile.name(), "STANDARD");
223 }
224
225 #[test]
226 fn test_create_regulated_profile() {
227 let profile = SecurityProfile::regulated();
228 assert!(!profile.is_standard());
229 assert!(profile.is_regulated());
230 assert_eq!(profile.name(), "REGULATED");
231 }
232
233 #[test]
234 fn test_default_profile_is_standard() {
235 let profile = SecurityProfile::default();
236 assert!(profile.is_standard());
237 }
238
239 #[test]
240 fn test_profile_display() {
241 assert_eq!(SecurityProfile::Standard.to_string(), "STANDARD");
242 assert_eq!(SecurityProfile::Regulated.to_string(), "REGULATED");
243 }
244
245 #[test]
250 fn test_standard_has_rate_limiting() {
251 let profile = SecurityProfile::standard();
252 assert!(profile.rate_limit_enabled());
253 }
254
255 #[test]
256 fn test_regulated_has_rate_limiting() {
257 let profile = SecurityProfile::regulated();
258 assert!(profile.rate_limit_enabled());
259 }
260
261 #[test]
262 fn test_standard_has_audit_logging() {
263 let profile = SecurityProfile::standard();
264 assert!(profile.audit_logging_enabled());
265 }
266
267 #[test]
268 fn test_regulated_has_audit_logging() {
269 let profile = SecurityProfile::regulated();
270 assert!(profile.audit_logging_enabled());
271 }
272
273 #[test]
278 fn test_standard_no_field_audit() {
279 let profile = SecurityProfile::standard();
280 assert!(!profile.audit_field_access());
281 }
282
283 #[test]
284 fn test_standard_no_field_masking() {
285 let profile = SecurityProfile::standard();
286 assert!(!profile.sensitive_field_masking());
287 }
288
289 #[test]
290 fn test_standard_no_error_redaction() {
291 let profile = SecurityProfile::standard();
292 assert!(!profile.error_detail_reduction());
293 }
294
295 #[test]
296 fn test_standard_no_query_logging_compliance() {
297 let profile = SecurityProfile::standard();
298 assert!(!profile.query_logging_for_compliance());
299 }
300
301 #[test]
302 fn test_standard_no_response_limits() {
303 let profile = SecurityProfile::standard();
304 assert!(!profile.response_size_limits());
305 }
306
307 #[test]
308 fn test_standard_no_strict_filtering() {
309 let profile = SecurityProfile::standard();
310 assert!(!profile.field_filtering_strict());
311 }
312
313 #[test]
314 fn test_standard_unlimited_response_size() {
315 let profile = SecurityProfile::standard();
316 assert_eq!(profile.max_response_size_bytes(), usize::MAX);
317 }
318
319 #[test]
320 fn test_standard_rate_limit_rps() {
321 let profile = SecurityProfile::standard();
322 assert_eq!(profile.rate_limit_rps(), 100);
323 }
324
325 #[test]
330 fn test_regulated_has_field_audit() {
331 let profile = SecurityProfile::regulated();
332 assert!(profile.audit_field_access());
333 }
334
335 #[test]
336 fn test_regulated_has_field_masking() {
337 let profile = SecurityProfile::regulated();
338 assert!(profile.sensitive_field_masking());
339 }
340
341 #[test]
342 fn test_regulated_has_error_redaction() {
343 let profile = SecurityProfile::regulated();
344 assert!(profile.error_detail_reduction());
345 }
346
347 #[test]
348 fn test_regulated_has_query_logging_compliance() {
349 let profile = SecurityProfile::regulated();
350 assert!(profile.query_logging_for_compliance());
351 }
352
353 #[test]
354 fn test_regulated_has_response_limits() {
355 let profile = SecurityProfile::regulated();
356 assert!(profile.response_size_limits());
357 }
358
359 #[test]
360 fn test_regulated_has_strict_filtering() {
361 let profile = SecurityProfile::regulated();
362 assert!(profile.field_filtering_strict());
363 }
364
365 #[test]
366 fn test_regulated_response_size_limit() {
367 let profile = SecurityProfile::regulated();
368 assert_eq!(profile.max_response_size_bytes(), 1_000_000);
369 }
370
371 #[test]
372 fn test_regulated_rate_limit_stricter() {
373 let standard = SecurityProfile::standard();
374 let regulated = SecurityProfile::regulated();
375 assert!(regulated.rate_limit_rps() < standard.rate_limit_rps());
376 }
377
378 #[test]
379 fn test_regulated_query_complexity_stricter() {
380 let standard = SecurityProfile::standard();
381 let regulated = SecurityProfile::regulated();
382 assert!(regulated.max_query_complexity() < standard.max_query_complexity());
383 }
384
385 #[test]
386 fn test_regulated_query_depth_stricter() {
387 let standard = SecurityProfile::standard();
388 let regulated = SecurityProfile::regulated();
389 assert!(regulated.max_query_depth() < standard.max_query_depth());
390 }
391
392 #[test]
397 fn test_standard_query_limits() {
398 let profile = SecurityProfile::standard();
399 assert!(profile.max_query_complexity() > 0);
400 assert!(profile.max_query_depth() > 0);
401 }
402
403 #[test]
404 fn test_regulated_query_limits() {
405 let profile = SecurityProfile::regulated();
406 assert!(profile.max_query_complexity() > 0);
407 assert!(profile.max_query_depth() > 0);
408 }
409
410 #[test]
411 fn test_response_size_reasonable() {
412 let profile = SecurityProfile::regulated();
413 assert!(profile.max_response_size_bytes() > 100_000); assert!(profile.max_response_size_bytes() < 100_000_000); }
416
417 #[test]
422 fn test_standard_description() {
423 let profile = SecurityProfile::standard();
424 let desc = profile.description();
425 assert!(!desc.is_empty());
426 assert!(desc.contains("rate limiting"));
427 }
428
429 #[test]
430 fn test_regulated_description() {
431 let profile = SecurityProfile::regulated();
432 let desc = profile.description();
433 assert!(!desc.is_empty());
434 assert!(desc.contains("compliance"));
435 }
436
437 #[test]
438 fn test_standard_enforced_features() {
439 let profile = SecurityProfile::standard();
440 let features = profile.enforced_features();
441 assert!(features.contains(&"Rate Limiting"));
442 assert!(features.contains(&"Audit Logging"));
443 assert_eq!(features.len(), 2);
444 }
445
446 #[test]
447 fn test_regulated_enforced_features() {
448 let profile = SecurityProfile::regulated();
449 let features = profile.enforced_features();
450 assert!(features.len() > 2);
451 assert!(features.contains(&"Rate Limiting"));
452 assert!(features.contains(&"Audit Logging"));
453 assert!(features.contains(&"Sensitive Field Masking"));
454 assert!(features.contains(&"Error Detail Reduction"));
455 }
456
457 #[test]
462 fn test_profile_equality() {
463 let standard1 = SecurityProfile::standard();
464 let standard2 = SecurityProfile::standard();
465 let regulated = SecurityProfile::regulated();
466
467 assert_eq!(standard1, standard2);
468 assert_ne!(standard1, regulated);
469 }
470
471 #[test]
472 fn test_profile_clone() {
473 let original = SecurityProfile::regulated();
474 let cloned = original;
475 assert_eq!(original, cloned);
476 }
477
478 #[test]
483 fn test_profile_features_are_superset() {
484 let standard = SecurityProfile::standard();
486 let regulated = SecurityProfile::regulated();
487
488 assert_eq!(standard.rate_limit_enabled(), regulated.rate_limit_enabled());
489 assert_eq!(standard.audit_logging_enabled(), regulated.audit_logging_enabled());
490 assert!(regulated.audit_field_access() || !standard.audit_field_access());
492 }
493
494 #[test]
495 fn test_all_features_documented() {
496 let profile = SecurityProfile::standard();
497 assert!(!profile.name().is_empty());
498 assert!(!profile.description().is_empty());
499
500 let profile = SecurityProfile::regulated();
501 assert!(!profile.name().is_empty());
502 assert!(!profile.description().is_empty());
503 }
504
505 #[test]
506 fn test_rate_limits_are_positive() {
507 for profile in &[SecurityProfile::Standard, SecurityProfile::Regulated] {
508 assert!(
509 profile.rate_limit_rps() > 0,
510 "Profile {profile} should have positive rate limit"
511 );
512 }
513 }
514}