use axum::{
Json,
extract::{Path as AxumPath, Query, State},
http::HeaderMap,
response::Response,
};
use futures::stream;
use crate::{
application::{
commands::{CreateStreamCommand, GenerateFramesCommand, StartStreamCommand},
dto::PriorityDto,
handlers::{
CommandHandlerGat, QueryHandlerGat, command_handlers::SessionCommandHandler,
query_handlers::StreamQueryHandler,
},
queries::{FramesResponse, GetStreamFramesQuery, GetStreamQuery, StreamResponse},
},
domain::{
entities::Frame,
ports::{EventPublisherGat, StreamRepositoryGat, StreamStoreGat},
value_objects::{Priority, StreamId},
},
infrastructure::{
adapters::InMemoryFrameStore,
http::{
axum_adapter::{
FrameQueryParams, GenerateFramesRequest, GenerateFramesResponse, PjsAppState,
PjsError, StartStreamRequest, parse_session_and_stream_id, parse_session_id,
},
streaming::{
BatchFrameStream, StreamFormat, create_streaming_response,
create_streaming_response_with_content_type,
},
},
},
};
pub(crate) async fn create_stream<R, P, S>(
State(state): State<PjsAppState<R, P, S>>,
AxumPath(session_id): AxumPath<String>,
Json(request): Json<StartStreamRequest>,
) -> Result<Json<serde_json::Value>, PjsError>
where
R: StreamRepositoryGat + Send + Sync + 'static,
P: EventPublisherGat + Send + Sync + 'static,
S: StreamStoreGat + Send + Sync + 'static,
{
let session_id = parse_session_id(session_id)?;
let command = CreateStreamCommand {
session_id: session_id.into(),
source_data: request.data,
config: None,
};
let stream_id: StreamId = CommandHandlerGat::handle(&*state.command_handler, command)
.await
.map_err(PjsError::Application)?;
Ok(Json(serde_json::json!({
"stream_id": stream_id.to_string(),
"status": "created"
})))
}
pub(crate) async fn start_stream<R, P, S>(
State(state): State<PjsAppState<R, P, S>>,
AxumPath((session_id, stream_id)): AxumPath<(String, String)>,
) -> Result<Json<serde_json::Value>, PjsError>
where
R: StreamRepositoryGat + Send + Sync + 'static,
P: EventPublisherGat + Send + Sync + 'static,
S: StreamStoreGat + Send + Sync + 'static,
{
let (session_id, stream_id) = parse_session_and_stream_id(session_id, stream_id)?;
let command = StartStreamCommand {
session_id: session_id.into(),
stream_id: stream_id.into(),
};
<SessionCommandHandler<R, P> as CommandHandlerGat<StartStreamCommand>>::handle(
&*state.command_handler,
command,
)
.await
.map_err(PjsError::Application)?;
Ok(Json(serde_json::json!({
"stream_id": stream_id.to_string(),
"status": "started"
})))
}
pub(crate) async fn generate_frames<R, P, S>(
State(state): State<PjsAppState<R, P, S>>,
AxumPath((session_id, stream_id)): AxumPath<(String, String)>,
request: Option<Json<GenerateFramesRequest>>,
) -> Result<Json<GenerateFramesResponse>, PjsError>
where
R: StreamRepositoryGat + Send + Sync + 'static,
P: EventPublisherGat + Send + Sync + 'static,
S: StreamStoreGat + Send + Sync + 'static,
{
let (session_id, stream_id) = parse_session_and_stream_id(session_id, stream_id)?;
let Json(request) = request.unwrap_or_default();
let priority_value = request
.priority_threshold
.unwrap_or(Priority::BACKGROUND.value());
let priority_threshold =
PriorityDto::new(priority_value).map_err(|e| PjsError::InvalidPriority(e.to_string()))?;
let max_frames = request.max_frames.unwrap_or(16);
let command = GenerateFramesCommand {
session_id: session_id.into(),
stream_id: stream_id.into(),
priority_threshold,
max_frames,
};
let frames: Vec<Frame> = <SessionCommandHandler<R, P> as CommandHandlerGat<
GenerateFramesCommand,
>>::handle(&*state.command_handler, command)
.await
.map_err(PjsError::Application)?;
let frame_count = frames.len();
Ok(Json(GenerateFramesResponse {
frames,
frame_count,
}))
}
pub(crate) async fn get_stream<R, P, S>(
State(state): State<PjsAppState<R, P, S>>,
AxumPath((session_id, stream_id)): AxumPath<(String, String)>,
) -> Result<Json<StreamResponse>, PjsError>
where
R: StreamRepositoryGat + Send + Sync + 'static,
P: EventPublisherGat + Send + Sync + 'static,
S: StreamStoreGat + Send + Sync + 'static,
{
let (session_id, stream_id) = parse_session_and_stream_id(session_id, stream_id)?;
let query = GetStreamQuery {
session_id: session_id.into(),
stream_id: stream_id.into(),
};
let response = <StreamQueryHandler<R, S, InMemoryFrameStore> as QueryHandlerGat<
GetStreamQuery,
>>::handle(&*state.stream_query_handler, query)
.await
.map_err(PjsError::Application)?;
Ok(Json(response))
}
pub(crate) async fn get_stream_frames<R, P, S>(
State(state): State<PjsAppState<R, P, S>>,
AxumPath((session_id, stream_id)): AxumPath<(String, String)>,
Query(params): Query<FrameQueryParams>,
) -> Result<Json<FramesResponse>, PjsError>
where
R: StreamRepositoryGat + Send + Sync + 'static,
P: EventPublisherGat + Send + Sync + 'static,
S: StreamStoreGat + Send + Sync + 'static,
{
let (session_id, stream_id) = parse_session_and_stream_id(session_id, stream_id)?;
let priority_filter = params
.priority
.map(|p| Priority::new(p).map(Into::into))
.transpose()
.map_err(|e: crate::domain::DomainError| PjsError::InvalidPriority(e.to_string()))?;
let query = GetStreamFramesQuery {
session_id: session_id.into(),
stream_id: stream_id.into(),
since_sequence: params.since_sequence,
priority_filter,
limit: params.limit,
};
let response = <StreamQueryHandler<R, S, InMemoryFrameStore> as QueryHandlerGat<
GetStreamFramesQuery,
>>::handle(&*state.stream_query_handler, query)
.await
.map_err(PjsError::Application)?;
Ok(Json(response))
}
pub(crate) async fn stream_stream_frames<R, P, S>(
State(state): State<PjsAppState<R, P, S>>,
AxumPath((session_id, stream_id)): AxumPath<(String, String)>,
Query(params): Query<FrameQueryParams>,
headers: HeaderMap,
) -> Result<Response, PjsError>
where
R: StreamRepositoryGat + Send + Sync + 'static,
P: EventPublisherGat + Send + Sync + 'static,
S: StreamStoreGat + Send + Sync + 'static,
{
let (session_id, stream_id) = parse_session_and_stream_id(session_id, stream_id)?;
let priority_filter = params
.priority
.map(|p| Priority::new(p).map(Into::into))
.transpose()
.map_err(|e: crate::domain::DomainError| PjsError::InvalidPriority(e.to_string()))?;
let query = GetStreamFramesQuery {
session_id: session_id.into(),
stream_id: stream_id.into(),
since_sequence: params.since_sequence,
priority_filter,
limit: params.limit,
};
let response = <StreamQueryHandler<R, S, InMemoryFrameStore> as QueryHandlerGat<
GetStreamFramesQuery,
>>::handle(&*state.stream_query_handler, query)
.await
.map_err(PjsError::Application)?;
let total_count = response.total_count;
let format = match StreamFormat::from_accept_header(&headers) {
StreamFormat::Binary => StreamFormat::Json,
other => other,
};
let batch = BatchFrameStream::new(stream::iter(response.frames), format, 1);
let content_type = batch.content_type();
let mut http_response = match format {
StreamFormat::Json => {
create_streaming_response_with_content_type(batch.into_stream(), content_type)
}
_ => create_streaming_response(batch.into_stream(), format),
}
.map_err(|e| {
tracing::error!(error = %e, "failed to build streaming response");
PjsError::HttpError("failed to build streaming response".into())
})?;
http_response
.headers_mut()
.insert("X-Total-Count", axum::http::HeaderValue::from(total_count));
Ok(http_response)
}