GUI plugins #9
1 changed files with 138 additions and 0 deletions
pinakes-ui: add plugin action executor
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ic6dc2c6e3ef58dacad4829037226b0cf6a6a6964
commit
307375a348
138
crates/pinakes-ui/src/plugin_ui/actions.rs
Normal file
138
crates/pinakes-ui/src/plugin_ui/actions.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
//! Action execution system for plugin UI pages
|
||||||
|
//!
|
||||||
|
//! This module provides the action execution system that handles
|
||||||
|
//! user interactions with plugin UI elements.
|
||||||
|
|
||||||
|
use pinakes_plugin_api::{ActionDefinition, ActionRef, HttpMethod};
|
||||||
|
|
||||||
|
use crate::client::ApiClient;
|
||||||
|
|
||||||
|
/// Result of an action execution
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ActionResult {
|
||||||
|
/// Action completed successfully
|
||||||
|
Success(serde_json::Value),
|
||||||
|
/// Action failed
|
||||||
|
Error(String),
|
||||||
|
/// Navigation action
|
||||||
|
Navigate(String),
|
||||||
|
/// No meaningful result (e.g. 204 No Content)
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute an action defined in the UI schema
|
||||||
|
pub async fn execute_action(
|
||||||
|
client: &ApiClient,
|
||||||
|
action_ref: &ActionRef,
|
||||||
|
form_data: Option<&serde_json::Value>,
|
||||||
|
) -> Result<ActionResult, String> {
|
||||||
|
match action_ref {
|
||||||
|
ActionRef::Name(name) => {
|
||||||
|
tracing::warn!("Named action '{}' not implemented yet", name);
|
||||||
|
Ok(ActionResult::None)
|
||||||
|
},
|
||||||
|
ActionRef::Inline(action) => {
|
||||||
|
execute_inline_action(client, action, form_data).await
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute an inline action definition
|
||||||
|
async fn execute_inline_action(
|
||||||
|
client: &ApiClient,
|
||||||
|
action: &ActionDefinition,
|
||||||
|
form_data: Option<&serde_json::Value>,
|
||||||
|
) -> Result<ActionResult, String> {
|
||||||
|
// Build URL from path
|
||||||
|
let url = action.path.clone();
|
||||||
|
|
||||||
|
// Merge action params with form data into query string for GET, body for
|
||||||
|
// others
|
||||||
|
let method = match action.method {
|
||||||
|
HttpMethod::Get => reqwest::Method::GET,
|
||||||
|
HttpMethod::Post => reqwest::Method::POST,
|
||||||
|
HttpMethod::Put => reqwest::Method::PUT,
|
||||||
|
HttpMethod::Patch => reqwest::Method::PATCH,
|
||||||
|
HttpMethod::Delete => reqwest::Method::DELETE,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut request = client.raw_request(method.clone(), &url);
|
||||||
|
|
||||||
|
// For GET, merge params into query string; for mutating methods, send as
|
||||||
|
// JSON body
|
||||||
|
if method == reqwest::Method::GET {
|
||||||
|
let query_pairs: Vec<(String, String)> = action
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
let val = match v {
|
||||||
|
serde_json::Value::String(s) => s.clone(),
|
||||||
|
other => other.to_string(),
|
||||||
|
};
|
||||||
|
(k.clone(), val)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if !query_pairs.is_empty() {
|
||||||
|
request = request.query(&query_pairs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Build body: merge action.params with form_data
|
||||||
|
let mut merged: serde_json::Map<String, serde_json::Value> = action
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
|
.collect();
|
||||||
|
if let Some(fd) = form_data {
|
||||||
|
if let Some(obj) = fd.as_object() {
|
||||||
|
for (k, v) in obj {
|
||||||
|
merged.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !merged.is_empty() {
|
||||||
|
request = request.json(&merged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = request.send().await.map_err(|e| e.to_string())?;
|
||||||
|
let status = response.status();
|
||||||
|
|
||||||
|
if !status.is_success() {
|
||||||
|
let error_text = response.text().await.unwrap_or_default();
|
||||||
|
return Ok(ActionResult::Error(format!(
|
||||||
|
"Action failed: {} - {}",
|
||||||
|
status.as_u16(),
|
||||||
|
error_text
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.as_u16() == 204 {
|
||||||
|
// Navigate on success if configured
|
||||||
|
if let Some(route) = &action.navigate_to {
|
||||||
|
return Ok(ActionResult::Navigate(route.clone()));
|
||||||
|
}
|
||||||
|
return Ok(ActionResult::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let value: serde_json::Value =
|
||||||
|
response.json().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if let Some(route) = &action.navigate_to {
|
||||||
|
return Ok(ActionResult::Navigate(route.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ActionResult::Success(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_action_result_variants() {
|
||||||
|
let _ = ActionResult::None;
|
||||||
|
let _ = ActionResult::Success(serde_json::json!({"ok": true}));
|
||||||
|
let _ = ActionResult::Error("error".to_string());
|
||||||
|
let _ = ActionResult::Navigate("/page".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue