1use std::{
2 sync::{
3 Arc, Mutex,
4 mpsc::{Receiver, RecvTimeoutError},
5 },
6 time::Duration,
7};
8
9use axum::{Json, Router, routing::get};
10use dada_ir_ast::DebugEvent;
11use serde::{Deserialize, Serialize};
12
13pub fn main(port: u32, debug_rx: Receiver<DebugEvent>) -> anyhow::Result<()> {
14 tokio::runtime::Builder::new_current_thread()
15 .enable_all()
16 .build()?
17 .block_on(main_async(port, debug_rx))?;
18 Ok(())
19}
20
21async fn main_async(port: u32, debug_rx: Receiver<DebugEvent>) -> anyhow::Result<()> {
22 tracing_subscriber::fmt::init();
24
25 let state = Arc::new(State {
26 debug_events: Default::default(),
27 shutdown: Default::default(),
28 });
29
30 std::thread::spawn({
31 let state = state.clone();
32 move || record_events(debug_rx, state)
33 });
34
35 let app = Router::new()
37 .route("/", get(root))
39 .route("/view/{event_index}", get(view))
40 .route("/assets/{file}", get(assets))
41 .route("/source/{*path}", get(source))
42 .route("/events", get(events))
43 .route("/events/{event_index}", get(event_data))
44 .with_state(state.clone());
45
46 let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
48 .await
49 .unwrap();
50
51 axum::serve(listener, app).await?;
52
53 *state.shutdown.lock().unwrap() = true;
54
55 Ok(())
56}
57
58fn respond_ok_or_500<B: Into<String>>(body: anyhow::Result<B>) -> axum::http::Response<String> {
59 match body {
60 Ok(body) => axum::http::Response::builder()
61 .status(200)
62 .body(body.into())
63 .unwrap(),
64 Err(err) => axum::http::Response::builder()
65 .status(500)
66 .body(crate::error::error(err))
67 .unwrap(),
68 }
69}
70
71fn respond_json_or_500<T: Serialize>(result: anyhow::Result<T>) -> axum::response::Result<Json<T>> {
72 match result {
73 Ok(data) => Ok(Json(data)),
74 Err(err) => Err(axum::response::Response::builder()
75 .status(500)
76 .body(crate::error::error(err))
77 .unwrap()
78 .into()),
79 }
80}
81
82async fn root(
83 axum::extract::State(state): axum::extract::State<Arc<State>>,
84) -> axum::http::Response<String> {
85 respond_ok_or_500(crate::root::root(&state).await)
86}
87
88async fn view(
89 axum::extract::Path(event_index): axum::extract::Path<usize>,
90 axum::extract::State(state): axum::extract::State<Arc<State>>,
91) -> axum::http::Response<String> {
92 respond_ok_or_500(crate::view::try_view(event_index, &state).await)
93}
94
95async fn assets(
96 axum::extract::Path(file): axum::extract::Path<String>,
97) -> axum::http::Response<String> {
98 respond_ok_or_500(crate::assets::try_asset(&file))
99}
100
101async fn events(
102 headers: axum::http::header::HeaderMap,
103 axum::extract::State(state): axum::extract::State<Arc<State>>,
104) -> axum::response::Result<Json<Vec<crate::root::RootEvent>>> {
105 respond_json_or_500(crate::events::events(&headers, &state).await)
106}
107
108async fn event_data(
109 headers: axum::http::header::HeaderMap,
110 axum::extract::Path(event_index): axum::extract::Path<usize>,
111 axum::extract::State(state): axum::extract::State<Arc<State>>,
112) -> axum::response::Result<Json<serde_json::Value>> {
113 respond_json_or_500(crate::events::try_event_data(&headers, event_index, &state).await)
114}
115
116#[derive(Deserialize, Debug)]
117struct SourceQueryArgs {
118 line: u32,
119 column: u32,
120}
121
122async fn source(
123 axum::extract::Path(path): axum::extract::Path<String>,
124 axum::extract::Query(line_col): axum::extract::Query<SourceQueryArgs>,
125) -> axum::http::Response<String> {
126 respond_ok_or_500(crate::source::try_source(
127 &path,
128 line_col.line,
129 line_col.column,
130 ))
131}
132
133pub struct State {
134 pub debug_events: Mutex<Vec<Arc<DebugEvent>>>,
135 pub shutdown: Mutex<bool>,
136}
137
138fn record_events(debug_rx: Receiver<DebugEvent>, state: Arc<State>) {
139 while !*state.shutdown.lock().unwrap() {
140 match debug_rx.recv_timeout(Duration::from_secs(1)) {
141 Ok(event) => {
142 state.debug_events.lock().unwrap().push(Arc::new(event));
143 }
144 Err(RecvTimeoutError::Disconnected) => return,
145 Err(RecvTimeoutError::Timeout) => (),
146 }
147 }
148}