Files
khadhroony-bobobot/kb_app/src/demo_http.rs
2026-04-22 19:04:22 +02:00

227 lines
7.9 KiB
Rust

// 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<std::string::String>,
/// Optional JSON config payload encoded as a string.
pub config_json: std::option::Option<std::string::String>,
}
/// 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::vec::Vec<kb_lib::KbHttpPoolClientSnapshot>, 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<KbDemoHttpExecutionPayload, std::string::String> {
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<std::string::String>,
) -> Result<std::option::Option<serde_json::Value>, 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::<serde_json::Value>(&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<serde_json::Value>,
) -> Result<std::vec::Vec<serde_json::Value>, 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",
}
}