use axum::{ extract::FromRequestParts, http::{request::Parts, HeaderName, HeaderValue}, }; use axum_extra::typed_header::TypedHeader; /// A typed header. When used as a bare extractor, reads from the /// `Last-Event-Id` HTTP header. pub struct LastEventId(pub String); static LAST_EVENT_ID: HeaderName = HeaderName::from_static("last-event-id"); impl headers::Header for LastEventId { fn name() -> &'static HeaderName { &LAST_EVENT_ID } fn decode<'i, I>(values: &mut I) -> Result where I: Iterator, { let value = values.next().ok_or_else(headers::Error::invalid)?; if let Ok(value) = value.to_str() { Ok(Self(value.into())) } else { Err(headers::Error::invalid()) } } fn encode(&self, values: &mut E) where E: Extend, { let Self(value) = self; // Must panic or suppress; the trait provides no other options. let value = HeaderValue::from_str(value).expect("LastEventId is a valid header value"); values.extend(std::iter::once(value)); } } #[async_trait::async_trait] impl FromRequestParts for LastEventId where S: Send + Sync, { type Rejection = as FromRequestParts>::Rejection; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { // This is purely for ergonomics: it allows `RequestedAt` to be extracted // without having to wrap it in `Extension<>`. Callers _can_ still do that, // but they aren't forced to. let TypedHeader(requested_at) = TypedHeader::::from_request_parts(parts, state).await?; Ok(requested_at) } } impl From for LastEventId { fn from(header: String) -> Self { Self(header) } } impl std::ops::Deref for LastEventId { type Target = str; fn deref(&self) -> &Self::Target { let Self(header) = self; header } }