Skip to main content

relay_server/endpoints/
attachments.rs

1use 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}