1use crate::parser::services::ConvertValue;
2use crate::parser::{EndpointMetadata, ParameterMetadata};
3use crate::ws::WsClient;
4use anyhow::{Context, Result, bail};
5use std::collections::HashMap;
6
7#[derive(PartialEq)]
8pub enum SettingsField {
9 Url,
10 Username,
11 Password,
12 ConnectButton,
13 DisconnectButton,
14}
15
16#[derive(PartialEq, Clone)]
17pub enum EndpointField {
18 Param(usize),
19 ConnectButton,
20 DisconnectButton,
21 JsonToggleButton,
22}
23
24#[derive(PartialEq)]
25pub enum AppBlock {
26 Settings,
27 EndpointList,
28 EndpointsReq,
29 EndpointsRes,
30}
31
32#[derive(PartialEq)]
33pub enum JsonViewMode {
34 Pretty,
35 Raw,
36}
37
38pub struct AppState {
39 pub client: Option<WsClient>,
40 pub current_block: AppBlock,
41 pub focused_settings_field: Option<SettingsField>,
42 pub focused_endpoint_field: Option<EndpointField>,
43 pub connected: bool,
44 pub endpoint_connected: bool,
45 pub url: String,
46 pub username: String,
47 pub password: String,
48 pub method_id: Option<u32>,
49 pub service_name: Option<String>,
50 pub params: Vec<ParameterMetadata>,
51 pub param_values: Vec<String>,
52 pub param_defaults: HashMap<String, HashMap<String, String>>,
53 pub json_view_mode: JsonViewMode,
54 pub json_data: Option<String>,
55 pub endpoints: Vec<String>,
56 pub selected_endpoint: usize,
57 pub endpoint_data: HashMap<String, EndpointMetadata>,
58 pub response_scroll: (u16, u16),
59 pub is_stream: bool,
60}
61
62impl AppState {
63 pub fn new(
64 endpoint_names: Vec<String>,
65 endpoint_data: HashMap<String, EndpointMetadata>,
66 param_defaults: HashMap<String, HashMap<String, String>>,
67 ) -> Self {
68 Self {
69 client: None,
70 current_block: AppBlock::Settings,
71 focused_settings_field: Some(SettingsField::Url),
72 focused_endpoint_field: Some(EndpointField::Param(0)),
73 connected: false,
74 endpoint_connected: false,
75 url: "ws://localhost:8443".to_string(),
76 username: String::new(),
77 password: String::new(),
78 method_id: None,
79 service_name: None,
80 params: Vec::new(),
81 param_values: Vec::new(),
82 param_defaults: param_defaults,
83 json_view_mode: JsonViewMode::Pretty,
84 json_data: None,
85 endpoints: endpoint_names,
86 selected_endpoint: 0,
87 endpoint_data: endpoint_data,
88 response_scroll: (0, 0),
89 is_stream: false,
90 }
91 }
92
93 pub fn scroll_response_down(&mut self) {
95 self.response_scroll.0 += 1;
96 }
97
98 pub fn scroll_response_up(&mut self) {
99 if self.response_scroll.0 > 0 {
100 self.response_scroll.0 -= 1;
101 }
102 }
103
104 pub fn scroll_response_right(&mut self) {
105 if self.current_block == AppBlock::EndpointsRes {
106 self.response_scroll.1 += 1;
107 }
108 }
109
110 pub fn scroll_response_left(&mut self) {
111 if self.current_block == AppBlock::EndpointsRes {
112 if self.response_scroll.1 > 0 {
113 self.response_scroll.1 -= 1;
114 }
115 }
116 }
117
118 pub fn update_input(&mut self, c: char) {
120 match self.current_block {
121 AppBlock::Settings => self.update_settings_input(c),
122 AppBlock::EndpointsReq => self.update_endpoint_input(c),
123 _ => {}
124 }
125 }
126
127 fn update_settings_input(&mut self, c: char) {
128 match self.focused_settings_field {
129 Some(SettingsField::Url) => self.url.push(c),
130 Some(SettingsField::Username) => self.username.push(c),
131 Some(SettingsField::Password) => self.password.push(c),
132 _ => {}
133 }
134 }
135
136 fn update_endpoint_input(&mut self, c: char) {
137 if let Some(EndpointField::Param(index)) = self.focused_endpoint_field {
138 if let Some(param) = self.param_values.get_mut(index) {
139 param.push(c);
140 }
141 }
142 }
143
144 pub fn delete_last_char(&mut self) {
145 match self.current_block {
146 AppBlock::Settings => self.delete_last_char_from_settings(),
147 AppBlock::EndpointsReq => self.delete_last_char_from_endpoint(),
148 _ => {}
149 }
150 }
151
152 fn delete_last_char_from_settings(&mut self) {
153 match self.focused_settings_field {
154 Some(SettingsField::Url) => {
155 self.url.pop();
156 }
157 Some(SettingsField::Username) => {
158 self.username.pop();
159 }
160 Some(SettingsField::Password) => {
161 self.password.pop();
162 }
163 _ => {}
164 }
165 }
166
167 fn delete_last_char_from_endpoint(&mut self) {
168 if let Some(EndpointField::Param(index)) = self.focused_endpoint_field {
169 if let Some(param) = self.param_values.get_mut(index) {
170 param.pop();
171 }
172 }
173 }
174
175 pub fn next_field(&mut self) {
177 match self.current_block {
178 AppBlock::Settings => self.focused_settings_field = self.next_settings_field(),
179 AppBlock::EndpointList => self.select_next_endpoint(),
180 AppBlock::EndpointsReq => self.focused_endpoint_field = self.next_endpoint_field(),
181 AppBlock::EndpointsRes => self.scroll_response_down(),
182 }
183 }
184
185 pub fn previous_field(&mut self) {
186 match self.current_block {
187 AppBlock::Settings => self.focused_settings_field = self.previous_settings_field(),
188 AppBlock::EndpointList => self.select_previous_endpoint(),
189 AppBlock::EndpointsReq => self.focused_endpoint_field = self.previous_endpoint_field(),
190 AppBlock::EndpointsRes => self.scroll_response_up(),
191 }
192 }
193
194 fn next_settings_field(&self) -> Option<SettingsField> {
195 match self.focused_settings_field {
196 Some(SettingsField::Url) => Some(SettingsField::Username),
197 Some(SettingsField::Username) => Some(SettingsField::Password),
198 Some(SettingsField::Password) => Some(SettingsField::ConnectButton),
199 Some(SettingsField::ConnectButton) => Some(SettingsField::DisconnectButton),
200 Some(SettingsField::DisconnectButton) => Some(SettingsField::Url),
201 None => Some(SettingsField::Url),
202 }
203 }
204
205 fn previous_settings_field(&self) -> Option<SettingsField> {
206 match self.focused_settings_field {
207 Some(SettingsField::Url) => Some(SettingsField::DisconnectButton),
208 Some(SettingsField::Username) => Some(SettingsField::Url),
209 Some(SettingsField::Password) => Some(SettingsField::Username),
210 Some(SettingsField::ConnectButton) => Some(SettingsField::Password),
211 Some(SettingsField::DisconnectButton) => Some(SettingsField::ConnectButton),
212 None => Some(SettingsField::Url),
213 }
214 }
215
216 fn next_endpoint_field(&self) -> Option<EndpointField> {
217 match self.focused_endpoint_field {
218 Some(EndpointField::Param(index)) if index + 1 < self.params.len() => {
219 Some(EndpointField::Param(index + 1))
220 }
221 Some(EndpointField::Param(_)) => Some(EndpointField::ConnectButton),
222 Some(EndpointField::ConnectButton) => Some(EndpointField::DisconnectButton),
223 Some(EndpointField::DisconnectButton) => Some(EndpointField::JsonToggleButton),
224 Some(EndpointField::JsonToggleButton) | None => {
225 if self.params.is_empty() {
226 Some(EndpointField::ConnectButton)
227 } else {
228 Some(EndpointField::Param(0))
229 }
230 }
231 }
232 }
233
234 fn previous_endpoint_field(&self) -> Option<EndpointField> {
235 match self.focused_endpoint_field {
236 Some(EndpointField::Param(index)) if index > 0 => Some(EndpointField::Param(index - 1)),
237 Some(EndpointField::Param(_)) => Some(EndpointField::JsonToggleButton),
238 Some(EndpointField::JsonToggleButton) => Some(EndpointField::DisconnectButton),
239 Some(EndpointField::DisconnectButton) => Some(EndpointField::ConnectButton),
240 Some(EndpointField::ConnectButton) | None => {
241 if self.params.is_empty() {
242 Some(EndpointField::JsonToggleButton)
243 } else {
244 Some(EndpointField::Param(self.params.len() - 1))
245 }
246 }
247 }
248 }
249
250 pub fn switch_block(&mut self) {
252 if self.connected {
253 self.current_block = match self.current_block {
254 AppBlock::Settings => {
255 self.update_selected_endpoint_data();
256 AppBlock::EndpointList
257 }
258 AppBlock::EndpointList => {
259 self.update_selected_endpoint_data();
260 AppBlock::EndpointsReq
261 }
262 AppBlock::EndpointsReq => AppBlock::EndpointsRes,
263 AppBlock::EndpointsRes => AppBlock::Settings,
264 };
265 }
266 }
267
268 pub fn select_next_endpoint(&mut self) {
270 if self.connected {
271 if self.selected_endpoint < self.endpoints.len() - 1 {
272 self.selected_endpoint += 1;
273 self.update_selected_endpoint_data();
274 }
275 }
276 }
277
278 pub fn select_previous_endpoint(&mut self) {
279 if self.connected {
280 if self.selected_endpoint > 0 {
281 self.selected_endpoint -= 1;
282 self.update_selected_endpoint_data();
283 }
284 }
285 }
286
287 fn update_selected_endpoint_data(&mut self) {
288 if let Some(endpoint) = self.endpoints.get(self.selected_endpoint) {
289 if let Some(metadata) = self.endpoint_data.get(endpoint) {
290 self.method_id = Some(metadata.method_id);
291 self.service_name = Some(metadata.service_name.clone());
292
293 self.params = metadata.params.clone();
295
296 if let Some((_, defaults)) = self
298 .param_defaults
299 .iter()
300 .find(|(id, _)| **id == metadata.method_id.to_string())
301 {
302 let default_map: HashMap<_, _> = defaults.clone();
304
305 self.param_values = self
307 .params
308 .iter()
309 .map(|param| {
310 default_map
311 .get(¶m.name)
312 .cloned()
313 .unwrap_or_else(|| "".to_string())
314 })
315 .collect();
316 } else {
317 self.param_values = vec!["".to_string(); self.params.len()];
319 }
320
321 self.is_stream = metadata.is_stream;
322 }
323 }
324 }
325
326 pub fn toggle_json_view_mode(&mut self) {
327 if let Some(raw_json) = self.json_data.as_ref() {
328 self.json_view_mode = match self.json_view_mode {
329 JsonViewMode::Pretty => {
330 let raw_json = serde_json::to_string(
331 &serde_json::from_str::<serde_json::Value>(raw_json).unwrap_or_default(),
332 )
333 .unwrap_or_else(|_| raw_json.clone());
334 self.json_data = Some(raw_json);
335 JsonViewMode::Raw
336 }
337 JsonViewMode::Raw => {
338 let pretty_json = serde_json::to_string_pretty(
339 &serde_json::from_str::<serde_json::Value>(raw_json).unwrap_or_default(),
340 )
341 .unwrap_or_else(|_| raw_json.clone());
342 self.json_data = Some(pretty_json);
343 JsonViewMode::Pretty
344 }
345 };
346 }
347 }
348
349 pub async fn handle_enter(&mut self) -> Result<()> {
351 if self.current_block == AppBlock::Settings {
352 match self.focused_settings_field {
353 Some(SettingsField::ConnectButton) => {
354 if let Err(_err) = self.handle_connect().await {
355 self.connected = false;
356 self.json_data = Some(_err.to_string());
357 }
358 }
359 Some(SettingsField::DisconnectButton) => {
360 if let Err(_err) = self.handle_disconnect().await {
361 self.connected = true;
362 self.json_data = Some(_err.to_string());
363 }
364 }
365 _ => {}
366 }
367 } else if self.current_block == AppBlock::EndpointsReq {
368 match self.focused_endpoint_field {
369 Some(EndpointField::ConnectButton) => {
370 if let Err(_err) = self.handle_endpoint_connect().await {
371 self.endpoint_connected = false;
372 self.json_data = Some(_err.to_string());
373 }
374 }
375 Some(EndpointField::DisconnectButton) => {
376 if let Err(_err) = self.handle_endpoint_disconnect().await {
377 self.endpoint_connected = true;
378 self.json_data = Some(_err.to_string());
379 }
380 }
381 Some(EndpointField::JsonToggleButton) => self.toggle_json_view_mode(),
382 _ => {}
383 }
384 }
385
386 Ok(())
387 }
388
389 pub async fn handle_connect(&mut self) -> Result<()> {
390 let mut client = WsClient::new(&self.url, &format!("0loginstep1, 1{}", self.username))
391 .await
392 .context("Failed to connect to WebSocket")?;
393
394 let response = client
395 .recv_raw()
396 .await
397 .context("Failed to receive response from WebSocket for step 1")?;
398
399 let Ok(token) = response
400 .get("params")
401 .context("Missing params in response")
402 .and_then(|v| {
403 v.get("accessToken")
404 .and_then(|token| token.as_str())
405 .context("Missing accessToken in response")
406 })
407 else {
408 let response = self.format_json(&response)?;
409 bail!("Failed to get access token from response\n{response}");
410 };
411
412 let _ = client.close().await;
413 let mut client = WsClient::new(
414 &self.url,
415 &format!("0loginstep2, 1{token}, 2{}", self.password),
416 )
417 .await
418 .context("Failed to connect to WebSocket")?;
419
420 let response = client
421 .recv_raw()
422 .await
423 .context("Failed to receive response from WebSocket for step 2")?;
424 let response = self.format_json(&response)?;
425
426 self.json_data = Some(format!("Connected to {}\n{}", self.url, response));
427 self.client = Some(client);
428 self.connected = true;
429
430 Ok(())
431 }
432
433 pub async fn handle_disconnect(&mut self) -> Result<()> {
434 if let Some(client) = self.client.take() {
435 client.close().await?;
436 }
437 let resp = format!("Disconnected from to {}", self.url);
438 self.json_data = Some(resp);
439 self.connected = false;
440 Ok(())
441 }
442
443 pub async fn handle_endpoint_connect(&mut self) -> Result<()> {
444 let client = self
445 .client
446 .as_mut()
447 .context("WebSocket client is not connected")?;
448
449 let mut converted_params = Vec::new();
450 for (param, value) in self.params.iter().zip(self.param_values.iter()) {
451 let converted_value = param.ty.convert_value(value).context(format!(
452 "Failed to convert value for parameter: {}",
453 param.name
454 ))?;
455 converted_params.push(converted_value);
456 }
457
458 client
459 .send_req(self.method_id.unwrap(), converted_params)
460 .await
461 .context("Failed to send request to WebSocket")?;
462 let raw_response = client
463 .recv_raw()
464 .await
465 .context("Failed to receive response from WebSocket")?;
466
467 let resp = self.format_json(&raw_response)?;
468
469 self.endpoint_connected = true;
470 self.json_data = Some(resp);
471 Ok(())
472 }
473
474 pub async fn handle_endpoint_disconnect(&mut self) -> Result<()> {
475 self.endpoint_connected = false;
476 self.json_data = None;
477 Ok(())
478 }
479
480 fn format_json(&self, value: &serde_json::Value) -> Result<String> {
481 Ok(match self.json_view_mode {
482 JsonViewMode::Pretty => {
483 serde_json::to_string_pretty(value).context("Failed to format JSON as pretty")?
484 }
485 JsonViewMode::Raw => {
486 serde_json::to_string(value).context("Failed to format JSON as raw")?
487 }
488 })
489 }
490}