relay_server/endpoints/
monitor.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
use crate::constants::DEFAULT_CHECK_IN_CLIENT;
use axum::extract::{DefaultBodyLimit, Path, Query, Request};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::routing::{on, MethodFilter, MethodRouter};
use axum::{Json, RequestExt};
use relay_config::Config;
use relay_event_schema::protocol::EventId;
use relay_monitors::{CheckIn, CheckInStatus};
use serde::Deserialize;
use uuid::Uuid;

use crate::endpoints::common::{self, BadStoreRequest};
use crate::envelope::{ContentType, Envelope, Item, ItemType};
use crate::extractors::{RawContentType, RequestMeta};
use crate::service::ServiceState;

#[derive(Debug, Deserialize)]
struct MonitorPath {
    monitor_slug: String,
}

#[derive(Debug, Deserialize)]
struct MonitorQuery {
    status: CheckInStatus,
    check_in_id: Option<Uuid>,
    environment: Option<String>,
    duration: Option<f64>,
}

async fn handle(
    state: ServiceState,
    content_type: RawContentType,
    mut meta: RequestMeta,
    Path(path): Path<MonitorPath>,
    request: Request,
) -> axum::response::Result<impl IntoResponse> {
    let check_in = if content_type.as_ref().starts_with("application/json") {
        let Json(mut check_in): Json<CheckIn> = request.extract().await?;
        check_in.monitor_slug = path.monitor_slug;
        check_in
    } else {
        let Query(query): Query<MonitorQuery> = request.extract().await?;
        CheckIn {
            check_in_id: query.check_in_id.unwrap_or_default(),
            monitor_slug: path.monitor_slug,
            status: query.status,
            environment: query.environment,
            duration: query.duration,
            monitor_config: None,
            contexts: None,
        }
    };

    let json = serde_json::to_vec(&check_in).map_err(BadStoreRequest::InvalidJson)?;

    // In case the `client` was not specified in the `RequestMeta` we mark the client as the Relay
    // HTTP endpoint since we don't know from which client the request came from.
    if meta.client().is_none() {
        meta.set_client(DEFAULT_CHECK_IN_CLIENT.to_owned());
    }

    let mut envelope = Envelope::from_request(Some(EventId::new()), meta);
    let mut item = Item::new(ItemType::CheckIn);
    item.set_payload(ContentType::Json, json);
    envelope.add_item(item);

    // Never respond with a 429
    match common::handle_envelope(&state, envelope).await {
        Ok(_) | Err(BadStoreRequest::RateLimited(_)) => (),
        Err(error) => return Err(error.into()),
    };

    // Event will be processed by Sentry, respond with a 202
    Ok(StatusCode::ACCEPTED)
}

pub fn route(config: &Config) -> MethodRouter<ServiceState> {
    on(MethodFilter::GET.or(MethodFilter::POST), handle)
        .route_layer(DefaultBodyLimit::max(config.max_event_size()))
}