// file: kb_app/src/demo_http.rs //! Tauri commands for the HTTP demo window. //! //! This module exposes a small manual test surface over the HTTP endpoint pool. use tauri::Manager; /// Request payload for one demo HTTP execution. #[derive(Clone, Debug, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct KbDemoHttpRequest { /// Logical role used to select one endpoint from the pool. pub role: std::string::String, /// JSON-RPC HTTP method name. pub method: std::string::String, /// Optional first string argument, used by methods such as /// `getBalance`, `getAccountInfo`, `getProgramAccounts`, /// `getSignaturesForAddress`, `getTransaction`, `sendTransaction`. pub first_arg: std::option::Option, /// Optional JSON config payload encoded as a string. pub config_json: std::option::Option, } /// Response payload for one demo HTTP execution. #[derive(Clone, Debug, serde::Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct KbDemoHttpExecutionPayload { /// Selected endpoint name. pub endpoint_name: std::string::String, /// Selected endpoint provider. pub provider: std::string::String, /// Selected endpoint URL. pub endpoint_url: std::string::String, /// Requested role. pub role: std::string::String, /// Executed method name. pub method: std::string::String, /// Classified method family. pub method_class: std::string::String, /// Pretty-printed JSON response payload. pub response_json: std::string::String, } /// Opens the dedicated HTTP demo window. #[tauri::command] pub(crate) fn open_demo_http_window( app_handle: tauri::AppHandle, ) -> Result<(), std::string::String> { let existing_window_option = app_handle.get_webview_window("demo_http"); let demo_window = match existing_window_option { Some(demo_window) => demo_window, None => { let builder = tauri::WebviewWindowBuilder::new( &app_handle, "demo_http", tauri::WebviewUrl::App("demo_http.html".into()), ) .title("Demo Http") .inner_size(1400.0, 768.0) .min_inner_size(800.0, 600.0) .center() .visible(true) .transparent(false) .decorations(true); let build_result = builder.build(); match build_result { Ok(window) => window, Err(error) => { return Err(format!("cannot create demo_http window: {error:?}")); } } } }; let show_result = demo_window.show(); if let Err(error) = show_result { return Err(format!("cannot show demo_http window: {error:?}")); } let focus_result = demo_window.set_focus(); if let Err(error) = focus_result { return Err(format!("cannot focus demo_http window: {error:?}")); } Ok(()) } /// Returns a fresh snapshot of the HTTP endpoint pool. #[tauri::command] pub(crate) async fn demo_http_list_pool_clients( state: tauri::State<'_, crate::KbAppState>, ) -> Result, std::string::String> { Ok(state.http_pool.snapshot().await) } /// Executes one manual HTTP request through the endpoint pool. #[tauri::command] pub(crate) async fn demo_http_execute_request( state: tauri::State<'_, crate::KbAppState>, request: KbDemoHttpRequest, ) -> Result { let role = request.role.trim().to_string(); if role.is_empty() { return Err("demo http role must not be empty".to_string()); } let method = request.method.trim().to_string(); if method.is_empty() { return Err("demo http method must not be empty".to_string()); } let config_json_value_result = kb_parse_optional_demo_http_json(request.config_json); let config_json_value = match config_json_value_result { Ok(config_json_value) => config_json_value, Err(error) => return Err(error), }; let params_result = kb_build_demo_http_params(&method, request.first_arg.as_deref(), config_json_value); let params = match params_result { Ok(params) => params, Err(error) => return Err(error), }; let selected_client_result = state .http_pool .select_client_for_role_and_method(&role, &method) .await; let selected_client = match selected_client_result { Ok(selected_client) => selected_client, Err(error) => return Err(error.to_string()), }; let method_class = kb_lib::HttpClient::classify_method(&method); let method_class_text = kb_demo_http_method_class_to_string(method_class); tracing::info!( endpoint_name = %selected_client.endpoint_name(), endpoint_url = %selected_client.endpoint_url(), role = %role, method = %method, method_class = %method_class_text, "executing demo http request" ); let response_value_result = selected_client .execute_json_rpc_result_raw(method.clone(), params) .await; let response_value = match response_value_result { Ok(response_value) => response_value, Err(error) => return Err(error.to_string()), }; let response_json_result = serde_json::to_string_pretty(&response_value); let response_json = match response_json_result { Ok(response_json) => response_json, Err(error) => { return Err(format!( "cannot pretty-print demo http response for method '{}': {}", method, error )); } }; Ok(KbDemoHttpExecutionPayload { endpoint_name: selected_client.endpoint_name().to_string(), provider: selected_client.endpoint_config().provider.clone(), endpoint_url: selected_client.endpoint_url().to_string(), role, method, method_class: method_class_text.to_string(), response_json, }) } fn kb_parse_optional_demo_http_json( config_json: std::option::Option, ) -> Result, std::string::String> { let config_json = match config_json { Some(config_json) => config_json.trim().to_string(), None => { return Ok(None); } }; if config_json.is_empty() { return Ok(None); } let value_result = serde_json::from_str::(&config_json); match value_result { Ok(value) => Ok(Some(value)), Err(error) => Err(format!("invalid configJson: {}", error)), } } fn kb_build_demo_http_params( method: &str, first_arg: std::option::Option<&str>, config_json: std::option::Option, ) -> Result, std::string::String> { let needs_first_arg = matches!( method, "getBalance" | "getAccountInfo" | "getProgramAccounts" | "getSignaturesForAddress" | "getTransaction" | "sendTransaction" ); if needs_first_arg { let first_arg = match first_arg { Some(first_arg) => first_arg.trim(), None => "", }; if first_arg.is_empty() { return Err(format!("method '{}' requires firstArg", method)); } let mut params = vec![serde_json::Value::String(first_arg.to_string())]; if let Some(config_json) = config_json { params.push(config_json); } return Ok(params); } let mut params = std::vec::Vec::new(); if let Some(config_json) = config_json { params.push(config_json); } Ok(params) } fn kb_demo_http_method_class_to_string(method_class: kb_lib::KbHttpMethodClass) -> &'static str { match method_class { kb_lib::KbHttpMethodClass::GeneralRpc => "GeneralRpc", kb_lib::KbHttpMethodClass::SendTransaction => "SendTransaction", kb_lib::KbHttpMethodClass::HeavyRead => "HeavyRead", } }