relay_server/endpoints/
nel.rs1use axum::extract::{DefaultBodyLimit, FromRequest};
7use axum::http::StatusCode;
8use axum::response::IntoResponse;
9use axum::routing::{MethodRouter, post};
10use bytes::Bytes;
11use relay_config::Config;
12use relay_event_schema::protocol::EventId;
13use serde_json::value::RawValue;
14
15use crate::endpoints::common::{self, BadStoreRequest};
16use crate::envelope::{ContentType, Envelope, Item, ItemType};
17use crate::extractors::{Mime, RequestMeta};
18use crate::service::ServiceState;
19
20#[derive(Debug, FromRequest)]
21#[from_request(state(ServiceState))]
22struct NelReportParams {
23 meta: RequestMeta,
24 body: Bytes,
25}
26
27fn is_nel_mime(mime: Mime) -> bool {
28 let ty = mime.type_().as_str();
29 let subty = mime.subtype().as_str();
30 let suffix = mime.suffix().map(|suffix| suffix.as_str());
31
32 matches!(
33 (ty, subty, suffix),
34 ("application", "json", None) | ("application", "reports", Some("json"))
35 )
36}
37
38async fn handle(
40 state: ServiceState,
41 mime: Mime,
42 params: NelReportParams,
43) -> Result<impl IntoResponse, BadStoreRequest> {
44 if !is_nel_mime(mime) {
45 return Ok(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response());
46 }
47
48 let items: Vec<&RawValue> =
49 serde_json::from_slice(¶ms.body).map_err(BadStoreRequest::InvalidJson)?;
50
51 let mut envelope = Envelope::from_request(Some(EventId::new()), params.meta.clone());
52 for item in items {
53 let mut report_item = Item::new(ItemType::Nel);
54 report_item.set_payload(ContentType::Json, item.to_owned().to_string());
55 envelope.add_item(report_item);
56 }
57
58 common::handle_envelope(&state, envelope).await?;
59 Ok(().into_response())
60}
61
62pub fn route(config: &Config) -> MethodRouter<ServiceState> {
63 post(handle).route_layer(DefaultBodyLimit::max(config.max_event_size()))
64}