bestool_alertd/http_server/endpoints/
tasks.rs1use std::{collections::BTreeMap, sync::Arc};
2
3use axum::{
4 Json,
5 body::Body,
6 extract::{Path, Query, State},
7 http::{HeaderValue, StatusCode, header::CONTENT_TYPE},
8 response::{IntoResponse, Response},
9};
10use futures::StreamExt;
11use tracing::warn;
12
13use crate::{
14 http_server::state::ServerState,
15 tasks::{TaskContext, TaskEndpointResponse},
16};
17
18pub async fn handle_task_endpoint(
26 State(state): State<Arc<ServerState>>,
27 Path((task, endpoint)): Path<(String, String)>,
28 Query(query): Query<BTreeMap<String, String>>,
29) -> Response {
30 let Some(handler) = state.task_endpoints.get(&(task.clone(), endpoint.clone())) else {
31 return (
32 StatusCode::NOT_FOUND,
33 format!("no endpoint at /tasks/{task}/{endpoint}"),
34 )
35 .into_response();
36 };
37
38 let mut ctx = TaskContext::from_internal(&state.internal_context);
39 ctx.query = query;
40 let response = handler(ctx).await;
41
42 match response {
43 TaskEndpointResponse::Json(value) => Json(value).into_response(),
44 TaskEndpointResponse::JsonLines(stream) => {
45 let body = Body::from_stream(stream.map(|value| {
46 let mut bytes = serde_json::to_vec(&value).unwrap_or_else(|err| {
50 warn!(%err, "could not serialise task endpoint stream value");
51 b"{}".to_vec()
52 });
53 bytes.push(b'\n');
54 Ok::<_, std::convert::Infallible>(bytes)
55 }));
56 let mut response = Response::new(body);
57 response.headers_mut().insert(
58 CONTENT_TYPE,
59 HeaderValue::from_static("application/x-ndjson"),
60 );
61 response
62 }
63 TaskEndpointResponse::Error { status, message } => (
64 StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
65 message,
66 )
67 .into_response(),
68 }
69}