use std::convert::Infallible;
use std::sync::Arc;
use ::http::request::Parts;
use ::http::{Method, StatusCode};
use async_graphql::Executor;
use autumn_web::AppState;
use axum::response::sse::{Event, KeepAlive, Sse};
use axum::response::{IntoResponse, Response};
use futures_util::stream::{self, StreamExt};
use super::http::Incoming;
use super::{Endpoint, Format, drained, rejection_response};
use crate::context::Transport;
use crate::error::codes;
use crate::limits;
use crate::pipeline::Rejection;
use async_graphql::parser::types::OperationType;
pub async fn serve<E: Executor>(
endpoint: Arc<Endpoint<E>>,
state: AppState,
mut parts: Parts,
incoming: Incoming,
) -> Response {
let Incoming {
mut request,
document_id,
} = incoming;
if let Err(rejection) = endpoint
.attach(&mut request, &mut parts, &state, Transport::Sse)
.await
{
return rejection_response(rejection, Format::Json);
}
if let Some(id) = document_id
&& let Err(rejection) = endpoint
.pipeline
.prepare(&mut request, Some(&id), Transport::Sse)
.await
{
return rejection_response(rejection, Format::Json);
}
let operation_name = request.operation_name.clone();
if parts.method == Method::GET
&& let Ok(document) = request.parsed_query()
&& limits::operation_type(document, operation_name.as_deref())
== Some(OperationType::Mutation)
{
return rejection_response(
Rejection::new(
codes::METHOD_NOT_ALLOWED,
"mutation operations are not allowed over GET; use POST",
StatusCode::METHOD_NOT_ALLOWED,
),
Format::Json,
);
}
let Some(slot) = endpoint.try_stream_slot(Transport::Sse) else {
return rejection_response(
Rejection::new(
codes::TOO_MANY_CONNECTIONS,
"too many concurrent streams; retry later",
StatusCode::SERVICE_UNAVAILABLE,
),
Format::Json,
);
};
let stopped = drained(endpoint.shutdown.subscribe());
let results = endpoint
.pipeline
.execute_stream(&endpoint.executor, request, None, Transport::Sse)
.map(|response| {
Ok::<_, Infallible>(
Event::default()
.event("next")
.json_data(&response)
.unwrap_or_else(|_| Event::default().event("next").data("{}")),
)
});
let complete = stream::once(async move {
drop(slot);
Ok::<_, Infallible>(Event::default().event("complete").data(""))
});
let events = results.take_until(Box::pin(stopped)).chain(complete);
Sse::new(events)
.keep_alive(KeepAlive::new().interval(endpoint.keepalive))
.into_response()
}