0.4.4
This commit is contained in:
226
kb_app/src/demo_http.rs
Normal file
226
kb_app/src/demo_http.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
// 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",
|
||||
}
|
||||
}
|
||||
@@ -383,7 +383,6 @@ pub(crate) async fn demo_ws_subscribe(
|
||||
return Err("demo websocket client is not connected".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
kb_execute_demo_ws_subscribe(&client, &request).await
|
||||
}
|
||||
|
||||
@@ -445,7 +444,6 @@ async fn kb_execute_demo_ws_subscribe(
|
||||
let filter = kb_parse_required_json_typed::<
|
||||
solana_rpc_client_api::config::RpcBlockSubscribeFilter,
|
||||
>(&request.filter_json, "block typed filter")?;
|
||||
|
||||
let config = kb_parse_optional_json_typed::<
|
||||
solana_rpc_client_api::config::RpcBlockSubscribeConfig,
|
||||
>(&request.config_json, "block typed config")?;
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
#![deny(unreachable_pub)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod splash;
|
||||
mod demo_http;
|
||||
mod demo_ws;
|
||||
mod splash;
|
||||
|
||||
pub use crate::splash::SplashOrder;
|
||||
use tauri::Emitter;
|
||||
@@ -36,6 +37,7 @@ struct KbAppState {
|
||||
config: kb_lib::KbConfig,
|
||||
ws_runtime: tokio::sync::Mutex<KbWsRuntimeState>,
|
||||
demo_ws_runtime: std::sync::Arc<tokio::sync::Mutex<crate::demo_ws::KbDemoWsRuntimeState>>,
|
||||
http_pool: kb_lib::HttpEndpointPool,
|
||||
}
|
||||
|
||||
/// Runs the desktop application.
|
||||
@@ -72,12 +74,21 @@ pub fn run() {
|
||||
environment = %config.app.environment,
|
||||
"starting desktop application"
|
||||
);
|
||||
let http_pool_result = kb_lib::HttpEndpointPool::from_config(&config);
|
||||
let http_pool = match http_pool_result {
|
||||
Ok(http_pool) => http_pool,
|
||||
Err(error) => {
|
||||
tracing::error!("cannot create http endpoint pool: {}", error);
|
||||
panic!("cannot create http endpoint pool: {}", error);
|
||||
}
|
||||
};
|
||||
let app_state = KbAppState {
|
||||
config: config.clone(),
|
||||
ws_runtime: tokio::sync::Mutex::new(KbWsRuntimeState::new()),
|
||||
demo_ws_runtime: std::sync::Arc::new(tokio::sync::Mutex::new(
|
||||
crate::demo_ws::KbDemoWsRuntimeState::new(),
|
||||
)),
|
||||
http_pool,
|
||||
};
|
||||
let tracing_builder = tauri_plugin_tracing::Builder::new();
|
||||
let mut tauri_builder = tauri::Builder::default();
|
||||
@@ -91,7 +102,10 @@ pub fn run() {
|
||||
crate::demo_ws::demo_ws_connect,
|
||||
crate::demo_ws::demo_ws_disconnect,
|
||||
crate::demo_ws::demo_ws_subscribe,
|
||||
crate::demo_ws::demo_ws_unsubscribe_current
|
||||
crate::demo_ws::demo_ws_unsubscribe_current,
|
||||
crate::demo_http::open_demo_http_window,
|
||||
crate::demo_http::demo_http_list_pool_clients,
|
||||
crate::demo_http::demo_http_execute_request,
|
||||
]);
|
||||
tauri_builder = tauri_builder.plugin(tracing_builder.build::<tauri::Wry>());
|
||||
tauri_builder = tauri_builder.setup(|app| {
|
||||
@@ -147,7 +161,7 @@ pub fn run() {
|
||||
emit_splash_order(&splash_window, "fadeout", None, None);
|
||||
tracing::debug!("end splash fadeout");
|
||||
tokio::time::sleep(std::time::Duration::from_millis(3100)).await;
|
||||
let close_result = splash_window.close();
|
||||
let close_result = splash_window.destroy();
|
||||
if let Err(error) = close_result {
|
||||
tracing::error!("error closing splash window: {error:?}");
|
||||
}
|
||||
@@ -351,13 +365,13 @@ fn kb_format_ws_event(event: &kb_lib::WsEvent) -> std::string::String {
|
||||
endpoint_url,
|
||||
} => {
|
||||
format!("[ws:{endpoint_name}] connected to {endpoint_url}")
|
||||
},
|
||||
}
|
||||
kb_lib::WsEvent::TextMessage {
|
||||
endpoint_name,
|
||||
text,
|
||||
} => {
|
||||
format!("[ws:{endpoint_name}] text: {text}")
|
||||
},
|
||||
}
|
||||
kb_lib::WsEvent::JsonRpcMessage {
|
||||
endpoint_name,
|
||||
message,
|
||||
|
||||
Reference in New Issue
Block a user