1use std::sync::Arc;
12use std::time::Duration;
13
14use http::{Method, StatusCode};
15
16use crate::error::ErrorDetail;
17
18#[derive(Clone, Debug)]
22pub(crate) struct RequestInfo(Arc<RequestInfoInner>);
23
24#[derive(Debug)]
25struct RequestInfoInner {
26 method: Method,
27 path: Arc<str>,
28 route: Option<Arc<str>>,
29 request_id: Option<Arc<str>>,
30}
31
32impl RequestInfo {
33 pub(crate) fn new(
35 method: Method,
36 path: Arc<str>,
37 route: Option<Arc<str>>,
38 request_id: Option<Arc<str>>,
39 ) -> Self {
40 Self(Arc::new(RequestInfoInner {
41 method,
42 path,
43 route,
44 request_id,
45 }))
46 }
47
48 pub(crate) fn method(&self) -> &Method {
49 &self.0.method
50 }
51
52 pub(crate) fn path(&self) -> &str {
53 self.0.path.as_ref()
54 }
55
56 pub(crate) fn route(&self) -> Option<&str> {
57 self.0.route.as_deref()
58 }
59
60 pub(crate) fn request_id(&self) -> Option<&str> {
61 self.0.request_id.as_deref()
62 }
63}
64
65macro_rules! shared_accessors {
67 ($t:ty) => {
68 impl $t {
69 pub fn method(&self) -> &Method {
71 &self.info.0.method
72 }
73
74 pub fn path(&self) -> &str {
76 self.info.0.path.as_ref()
77 }
78
79 pub fn route(&self) -> Option<&str> {
82 self.info.0.route.as_deref()
83 }
84
85 pub fn request_id(&self) -> Option<&str> {
87 self.info.0.request_id.as_deref()
88 }
89 }
90 };
91}
92
93pub struct RequestEvent {
95 info: RequestInfo,
96}
97
98impl RequestEvent {
99 pub(crate) fn new(info: RequestInfo) -> Self {
100 Self { info }
101 }
102}
103
104shared_accessors!(RequestEvent);
105
106pub struct ResponseEvent {
108 info: RequestInfo,
109 status: StatusCode,
110 elapsed: Duration,
111}
112
113impl ResponseEvent {
114 pub(crate) fn new(info: RequestInfo, status: StatusCode, elapsed: Duration) -> Self {
115 Self {
116 info,
117 status,
118 elapsed,
119 }
120 }
121
122 pub fn status(&self) -> StatusCode {
124 self.status
125 }
126
127 pub fn elapsed(&self) -> Duration {
129 self.elapsed
130 }
131}
132
133shared_accessors!(ResponseEvent);
134
135pub struct ErrorEvent {
138 info: RequestInfo,
139 status: StatusCode,
140 code: &'static str,
141 message: String,
142}
143
144impl ErrorEvent {
145 pub(crate) fn new(
146 info: RequestInfo,
147 status: StatusCode,
148 code: &'static str,
149 message: String,
150 ) -> Self {
151 Self {
152 info,
153 status,
154 code,
155 message,
156 }
157 }
158
159 pub fn status(&self) -> StatusCode {
161 self.status
162 }
163
164 pub fn code(&self) -> &str {
166 self.code
167 }
168
169 pub fn message(&self) -> &str {
171 &self.message
172 }
173}
174
175shared_accessors!(ErrorEvent);
176
177pub struct ValidationErrorEvent {
180 info: RequestInfo,
181 details: Vec<ErrorDetail>,
182}
183
184impl ValidationErrorEvent {
185 pub(crate) fn new(info: RequestInfo, details: Vec<ErrorDetail>) -> Self {
186 Self { info, details }
187 }
188
189 pub fn details(&self) -> &[ErrorDetail] {
191 &self.details
192 }
193}
194
195shared_accessors!(ValidationErrorEvent);
196
197pub struct PanicEvent {
200 info: RequestInfo,
201 message: String,
202}
203
204impl PanicEvent {
205 pub(crate) fn new(info: RequestInfo, message: String) -> Self {
206 Self { info, message }
207 }
208
209 pub fn message(&self) -> &str {
211 &self.message
212 }
213}
214
215shared_accessors!(PanicEvent);
216
217pub struct ErrorContext {
220 info: RequestInfo,
221}
222
223impl ErrorContext {
224 pub(crate) fn new(info: RequestInfo) -> Self {
225 Self { info }
226 }
227}
228
229shared_accessors!(ErrorContext);
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 fn info() -> RequestInfo {
236 RequestInfo::new(
237 Method::GET,
238 Arc::from("/users/7"),
239 Some(Arc::from("/users/{id}")),
240 Some(Arc::from("req-1")),
241 )
242 }
243
244 #[test]
245 fn shared_accessors_expose_request_metadata() {
246 let event = RequestEvent::new(info());
247 assert_eq!(event.method(), Method::GET);
248 assert_eq!(event.path(), "/users/7");
249 assert_eq!(event.route(), Some("/users/{id}"));
250 assert_eq!(event.request_id(), Some("req-1"));
251 }
252
253 #[test]
254 fn response_event_carries_status_and_elapsed() {
255 let event = ResponseEvent::new(info(), StatusCode::OK, Duration::from_millis(5));
256 assert_eq!(event.status(), StatusCode::OK);
257 assert_eq!(event.elapsed(), Duration::from_millis(5));
258 }
259
260 #[test]
261 fn error_event_carries_status_code_and_message() {
262 let event = ErrorEvent::new(
263 info(),
264 StatusCode::NOT_FOUND,
265 "NOT_FOUND",
266 "missing".to_owned(),
267 );
268 assert_eq!(event.status(), StatusCode::NOT_FOUND);
269 assert_eq!(event.code(), "NOT_FOUND");
270 assert_eq!(event.message(), "missing");
271 }
272
273 #[test]
274 fn validation_event_carries_details() {
275 let event = ValidationErrorEvent::new(
276 info(),
277 vec![ErrorDetail::new("name", "TOO_SHORT", "too short")],
278 );
279 assert_eq!(event.details().len(), 1);
280 assert_eq!(event.details()[0].field, "name");
281 }
282
283 #[test]
284 fn panic_event_carries_message() {
285 let event = PanicEvent::new(info(), "boom".to_owned());
286 assert_eq!(event.message(), "boom");
287 }
288
289 #[test]
290 fn error_context_exposes_route() {
291 let ctx = ErrorContext::new(info());
292 assert_eq!(ctx.route(), Some("/users/{id}"));
293 }
294}