relay_server/endpoints/
attachments.rs1use axum::extract::{DefaultBodyLimit, Path, Request};
2use axum::http::StatusCode;
3use axum::response::IntoResponse;
4use axum::routing::{MethodRouter, post};
5use multer::{Field, Multipart};
6use relay_config::Config;
7use relay_event_schema::protocol::EventId;
8use relay_quotas::DataCategory;
9use serde::Deserialize;
10use tower_http::limit::RequestBodyLimitLayer;
11
12use crate::endpoints::common::{self, BadStoreRequest};
13use crate::envelope::{AttachmentType, Envelope, Item};
14use crate::extractors::RequestMeta;
15use crate::managed::Managed;
16use crate::service::ServiceState;
17use crate::utils::{self, AttachmentStrategy, read_bytes_into_item};
18
19#[derive(Debug, Deserialize)]
20pub struct AttachmentPath {
21 event_id: EventId,
22}
23
24struct AttachmentsAttachmentStrategy;
25
26impl AttachmentStrategy for AttachmentsAttachmentStrategy {
27 fn infer_type(&self, _: &Field) -> AttachmentType {
28 AttachmentType::default()
29 }
30
31 async fn add_to_item(
32 &self,
33 field: Field<'static>,
34 item: Managed<Item>,
35 config: &Config,
36 ) -> Result<Option<Managed<Item>>, BadStoreRequest> {
37 Ok(Some(read_bytes_into_item(field, item, config).await?))
38 }
39}
40
41async fn multipart_to_envelope(
42 meta: RequestMeta,
43 path: AttachmentPath,
44 multipart: Multipart<'static>,
45 state: &ServiceState,
46) -> Result<Managed<Box<Envelope>>, BadStoreRequest> {
47 let items = utils::multipart_items(
48 multipart,
49 state.config(),
50 AttachmentsAttachmentStrategy,
51 &meta,
52 state.outcome_aggregator(),
53 )
54 .await?;
55
56 let envelope = items.map(|items, records| {
57 if items.iter().any(|i| i.creates_event()) {
58 records.modify_by(DataCategory::Error, 1);
59 }
60 Box::new(Envelope::from_request(Some(path.event_id), meta).with_items(items))
61 });
62 Ok(envelope)
63}
64
65pub async fn handle(
66 state: ServiceState,
67 meta: RequestMeta,
68 Path(path): Path<AttachmentPath>,
69 request: Request,
70) -> axum::response::Result<impl IntoResponse> {
71 let multipart = utils::multipart_from_request(request)?;
72 let envelope = multipart_to_envelope(meta, path, multipart, &state).await?;
73 common::handle_managed_envelope(&state, envelope)
74 .await?
75 .check_rate_limits()?;
76 Ok(StatusCode::CREATED)
77}
78
79pub fn route(config: &Config) -> MethodRouter<ServiceState> {
80 post(handle)
81 .route_layer(RequestBodyLimitLayer::new(config.max_attachments_size()))
82 .route_layer(DefaultBodyLimit::disable())
83}