endpoint_validator/tui/
app.rs1use crate::parser::EndpointMetadata;
2use crate::parser::services::ConvertValue;
3use crate::tui::state::{AppState, EndpointField, JsonViewMode};
4use crate::tui::ui::draw_ui;
5use anyhow::{Context, Result};
6use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode};
7use crossterm::execute;
8use crossterm::terminal::{
9 EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
10};
11use ratatui::Terminal;
12use ratatui::backend::CrosstermBackend;
13use std::collections::HashMap;
14use std::io;
15use std::sync::Arc;
16use tokio::sync::Mutex;
17use tokio::time::{self, Duration};
18
19pub async fn run(
20 endpoint_names: Vec<String>,
21 endpoint_data: HashMap<String, EndpointMetadata>,
22 param_defaults: HashMap<String, HashMap<String, String>>,
23) -> Result<()> {
24 enable_raw_mode()?;
26 let mut stdout = io::stdout();
27 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
28 let backend = CrosstermBackend::new(stdout);
29 let terminal = Arc::new(Mutex::new(Terminal::new(backend)?));
30
31 let app_state = Arc::new(Mutex::new(AppState::new(
33 endpoint_names,
34 endpoint_data,
35 param_defaults,
36 )));
37
38 let terminal_clone = Arc::clone(&terminal);
40 let app_state_clone = Arc::clone(&app_state);
41 tokio::spawn(async move {
42 let mut ticker = time::interval(Duration::from_millis(500));
43 loop {
44 ticker.tick().await; let mut app_state_guard = app_state_clone.lock().await;
46 let mut terminal_guard = terminal_clone.lock().await;
47 if let Err(e) = terminal_guard.draw(|f| draw_ui(f, &mut *app_state_guard)) {
48 eprintln!("Error drawing UI: {}", e);
49 break; }
51 }
52 });
53
54 let terminal_clone = Arc::clone(&terminal);
56 let handle_event_task = tokio::spawn(async move {
57 loop {
58 if let Err(e) = handle_event(&app_state, &terminal_clone).await {
59 eprintln!("Error handling event: {}", e);
60 break;
61 }
62 }
63 });
64
65 handle_event_task.await?;
67
68 {
70 let mut terminal = terminal.lock().await;
71 disable_raw_mode()?;
72 execute!(
73 terminal.backend_mut(),
74 LeaveAlternateScreen,
75 DisableMouseCapture
76 )?;
77 terminal.show_cursor()?;
78 }
79
80 Ok(())
81}
82
83async fn handle_event(
84 app_state: &Arc<Mutex<AppState>>,
85 terminal: &Arc<Mutex<Terminal<CrosstermBackend<std::io::Stdout>>>>,
86) -> Result<()> {
87 if event::poll(Duration::from_millis(100))? {
89 if let Event::Key(key) = event::read()? {
90 let mut needs_redraw = false;
91
92 match key.code {
93 KeyCode::Char(c) => {
94 let mut app_state_guard = app_state.lock().await;
95 app_state_guard.update_input(c);
96 needs_redraw = true;
97 }
98 KeyCode::Backspace => {
99 let mut app_state_guard = app_state.lock().await;
100 app_state_guard.delete_last_char();
101 needs_redraw = true;
102 }
103 KeyCode::Tab => {
104 let mut app_state_guard = app_state.lock().await;
105 app_state_guard.switch_block();
106 needs_redraw = true;
107 }
108 KeyCode::Left => {
109 let mut app_state_guard = app_state.lock().await;
110 app_state_guard.scroll_response_left();
111 needs_redraw = true;
112 }
113 KeyCode::Right => {
114 let mut app_state_guard = app_state.lock().await;
115 app_state_guard.scroll_response_right();
116 needs_redraw = true;
117 }
118 KeyCode::Down => {
119 let mut app_state_guard = app_state.lock().await;
120 app_state_guard.next_field();
121 needs_redraw = true;
122 }
123 KeyCode::Up => {
124 let mut app_state_guard = app_state.lock().await;
125 app_state_guard.previous_field();
126 needs_redraw = true;
127 }
128 KeyCode::Enter => {
129 let app_state_clone = Arc::clone(&app_state);
130
131 let focused_field = {
133 let state = app_state_clone.lock().await;
134 state.focused_endpoint_field.clone()
135 };
136
137 if let Some(EndpointField::ConnectButton) = focused_field {
139 if let Err(e) = connect_and_listen(app_state_clone).await {
140 eprintln!("Error connecting and listening: {}", e);
141 }
142 } else {
143 let mut app_state_guard = app_state.lock().await;
144 if let Err(err) = app_state_guard.handle_enter().await {
145 app_state_guard.json_data = Some(format!("Error: {}", err));
146 }
147 }
148 needs_redraw = true;
149 }
150 KeyCode::Esc => {
151 return Err(anyhow::anyhow!("Esc pressed, exiting program"));
153 }
154 _ => {}
155 }
156
157 if needs_redraw {
159 let mut app_state_guard = app_state.lock().await;
160 let mut terminal_guard = terminal.lock().await;
161 if let Err(e) = terminal_guard.draw(|f| draw_ui(f, &mut *app_state_guard)) {
162 eprintln!("Error drawing UI: {}", e);
163 }
164 }
165 }
166 }
167
168 Ok(())
169}
170
171async fn connect_and_listen(app_state: Arc<Mutex<AppState>>) -> Result<()> {
172 tokio::spawn(async move {
173 let (method_id, converted_params, is_stream) = {
174 let state = app_state.lock().await;
175
176 let method_id = state
178 .method_id
179 .ok_or_else(|| anyhow::anyhow!("Method ID is missing"))?;
180 let is_stream = state.is_stream;
181
182 let converted_params = state
183 .params
184 .iter()
185 .zip(state.param_values.iter())
186 .map(|(param, value)| {
187 param.ty.convert_value(value).context(format!(
188 "Failed to convert value for parameter: {}",
189 param.name
190 ))
191 })
192 .collect::<Result<Vec<_>>>()?;
193
194 (method_id, converted_params, is_stream)
195 };
196
197 {
198 let mut state = app_state.lock().await;
200 let client = state
201 .client
202 .as_mut()
203 .context("WebSocket client is not connected")?;
204 client
205 .send_req(method_id, converted_params)
206 .await
207 .context("Failed to send request to WebSocket")?;
208 }
209
210 loop {
212 let raw_response_result = {
213 let mut state = app_state.lock().await;
214 let client = state
215 .client
216 .as_mut()
217 .context("WebSocket client is not connected")?;
218
219 client.recv_raw().await
220 };
221
222 match raw_response_result {
223 Ok(raw_response) => {
224 let resp = {
225 let state = app_state.lock().await;
226 match state.json_view_mode {
227 JsonViewMode::Pretty => serde_json::to_string_pretty(&raw_response)
228 .context("Failed to format JSON as pretty"),
229 JsonViewMode::Raw => serde_json::to_string(&raw_response)
230 .context("Failed to format JSON as raw"),
231 }
232 };
233
234 let mut state = app_state.lock().await;
235 match resp {
236 Ok(formatted_json) => {
237 state.json_data = Some(formatted_json);
238 state.endpoint_connected = true;
239 }
240 Err(err) => {
241 state.json_data = Some(format!("Error: {}", err));
242 }
243 }
244 }
245 Err(e) => {
246 let mut state = app_state.lock().await;
247 state.json_data = Some(format!("Error receiving data: {:?}", e));
248 break;
249 }
250 }
251
252 if !is_stream {
253 break;
255 }
256
257 tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
259 }
260
261 Ok::<(), anyhow::Error>(())
262 });
263
264 Ok(())
265}