synapse_pingora/signals/
auth_coverage.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
5#[serde(rename_all = "snake_case")]
6pub enum ResponseClass {
7 Success, Unauthorized, Forbidden, ClientError, ServerError, }
13
14impl ResponseClass {
15 pub fn from_status(status: u16) -> Self {
16 match status {
17 200..=299 => ResponseClass::Success,
18 401 => ResponseClass::Unauthorized,
19 403 => ResponseClass::Forbidden,
20 400..=499 => ResponseClass::ClientError,
21 _ => ResponseClass::ServerError,
22 }
23 }
24
25 pub fn is_auth_denial(&self) -> bool {
26 matches!(self, ResponseClass::Unauthorized | ResponseClass::Forbidden)
27 }
28}
29
30#[derive(Debug, Default, Clone, Serialize, Deserialize)]
32pub struct EndpointCounts {
33 pub total: u64,
34 pub success: u64,
35 pub unauthorized: u64,
36 pub forbidden: u64,
37 pub other_error: u64,
38 pub with_auth: u64,
39 pub without_auth: u64,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct EndpointSummary {
45 pub endpoint: String,
46 pub counts: EndpointCounts,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct AuthCoverageSummary {
52 pub timestamp: u64,
53 pub sensor_id: String,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub tenant_id: Option<String>,
56 pub endpoints: Vec<EndpointSummary>,
57 #[serde(default)]
58 pub dropped_endpoints: u64,
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn test_response_class_from_status() {
67 assert_eq!(ResponseClass::from_status(200), ResponseClass::Success);
68 assert_eq!(ResponseClass::from_status(201), ResponseClass::Success);
69 assert_eq!(ResponseClass::from_status(401), ResponseClass::Unauthorized);
70 assert_eq!(ResponseClass::from_status(403), ResponseClass::Forbidden);
71 assert_eq!(ResponseClass::from_status(404), ResponseClass::ClientError);
72 assert_eq!(ResponseClass::from_status(500), ResponseClass::ServerError);
73 }
74
75 #[test]
76 fn test_is_auth_denial() {
77 assert!(ResponseClass::Unauthorized.is_auth_denial());
78 assert!(ResponseClass::Forbidden.is_auth_denial());
79 assert!(!ResponseClass::Success.is_auth_denial());
80 assert!(!ResponseClass::ClientError.is_auth_denial());
81 }
82
83 #[test]
84 fn test_summary_serialization() {
85 let summary = AuthCoverageSummary {
86 timestamp: 1234567890,
87 sensor_id: "sensor-1".to_string(),
88 tenant_id: Some("tenant-abc".to_string()),
89 endpoints: vec![EndpointSummary {
90 endpoint: "GET /api/users/{id}".to_string(),
91 counts: EndpointCounts {
92 total: 100,
93 success: 95,
94 unauthorized: 3,
95 forbidden: 2,
96 ..Default::default()
97 },
98 }],
99 dropped_endpoints: 0,
100 };
101
102 let json = serde_json::to_string(&summary).unwrap();
103 assert!(json.contains("sensor-1"));
104 assert!(json.contains("tenant-abc"));
105 }
106}