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