use anyhow::Result;
use axum::{
extract::{Query, Request, State},
handler::Handler,
http::StatusCode,
response::IntoResponse,
};
use serde::Serialize;
use std::collections::BTreeMap;
use std::path;
use std::sync::Arc;
use tokio_listener::Listener;
use crate::{
morgue::{Decay, StateOfDecay, shutdown_signal},
sex_dungeon::{self, Language, Request as SDRequest, SharedRequest},
tenx_programmer::TenXProgrammer,
};
#[derive(Clone)]
pub struct Bruce {
pub state: StateOfDecay,
}
impl Bruce {
pub fn new<P: AsRef<path::Path>, C: AsRef<path::Path>, S: Serialize>(
language: Language,
compiler: Option<C>,
path: &Option<P>,
initial_seed: &str,
metrics: &TenXProgrammer,
config: Option<S>,
) -> Result<Self> {
let request_handler = Arc::new(sex_dungeon::create(
language,
compiler,
path.as_ref(),
initial_seed,
metrics,
config,
)?);
if !request_handler.can_output() {
let path = path
.as_ref()
.map_or_else(|| "(default)".into(), |p| p.as_ref().display().to_string());
tracing::error!({ path }, "At least an output() function is required");
anyhow::bail!("Requires at least output()");
}
let state = Decay {
metrics: metrics.clone(),
request_handler,
}
.into();
Ok(Self { state })
}
#[allow(clippy::significant_drop_tightening)]
pub fn run_tests(self) -> anyhow::Result<()> {
let mut state = self.state.write().unwrap();
let handler = Arc::get_mut(&mut state.request_handler).unwrap();
handler.run_tests()
}
pub async fn serve(self, listener: Listener) -> Result<()> {
let app = handler
.layer(tower_http::trace::TraceLayer::new_for_http())
.with_state(self.state);
Ok(axum::serve(listener, app.into_make_service())
.with_graceful_shutdown(shutdown_signal(None))
.await?)
}
}
async fn handler(
State(state): State<StateOfDecay>,
Query(params): Query<BTreeMap<String, String>>,
request: Request,
) -> impl IntoResponse {
let request: SharedRequest = SDRequest {
method: request.method().to_string(),
path: request.uri().path().to_owned(),
headers: request.headers().clone(),
params,
}
.into();
let state = state.read().unwrap();
let decision = if state.request_handler.can_decide() {
match state.request_handler.decide(request.clone()) {
Ok(v) => Some(v),
Err(e) => {
tracing::error!("Error running decide(): {e}");
return server_error().into_response();
}
}
} else {
None
};
state.request_handler.output(request, decision).map_or_else(
|e| {
tracing::error!("Error running output(): {e}");
server_error().into_response()
},
IntoResponse::into_response,
)
}
fn server_error() -> impl IntoResponse {
(StatusCode::INTERNAL_SERVER_ERROR, "")
}