modo/sse/
last_event_id.rs1use axum::extract::FromRequestParts;
2use http::request::Parts;
3
4#[derive(Debug, Clone)]
17pub struct LastEventId(pub Option<String>);
18
19impl<S> FromRequestParts<S> for LastEventId
20where
21 S: Send + Sync,
22{
23 type Rejection = std::convert::Infallible;
24
25 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
26 let value = parts
27 .headers
28 .get("last-event-id")
29 .and_then(|v| v.to_str().ok())
30 .map(String::from);
31 Ok(LastEventId(value))
32 }
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38 use axum::extract::FromRequestParts;
39 use http::Request;
40
41 #[tokio::test]
42 async fn extracts_last_event_id_header() {
43 let (mut parts, _body) = Request::builder()
44 .header("last-event-id", "evt_42")
45 .body(())
46 .unwrap()
47 .into_parts();
48 let result = LastEventId::from_request_parts(&mut parts, &()).await;
49 let last_id = result.unwrap();
50 assert_eq!(last_id.0, Some("evt_42".to_string()));
51 }
52
53 #[tokio::test]
54 async fn returns_none_when_header_absent() {
55 let (mut parts, _body) = Request::builder().body(()).unwrap().into_parts();
56 let result = LastEventId::from_request_parts(&mut parts, &()).await;
57 let last_id = result.unwrap();
58 assert_eq!(last_id.0, None);
59 }
60
61 #[tokio::test]
62 async fn non_visible_ascii_header_returns_none() {
63 let (mut parts, _body) = Request::builder().body(()).unwrap().into_parts();
64 parts.headers.insert(
65 "last-event-id",
66 http::HeaderValue::from_bytes(&[0x80]).unwrap(),
67 );
68 let result = LastEventId::from_request_parts(&mut parts, &()).await;
69 let last_id = result.unwrap();
70 assert_eq!(last_id.0, None);
71 }
72}