Files
mnote/rust/crates/mnote-web/src/routes/command_support.rs
T

141 lines
4.8 KiB
Rust
Raw Normal View History

use crate::app::AppConfig;
use crate::context::RequestContext;
use crate::error::WebError;
2026-04-26 19:35:52 +08:00
use crate::transport::convex::{
2026-04-29 12:24:44 +08:00
execute_convex_command_plan, execute_convex_command_plan_with_artifacts, ConvexCommandExecution,
2026-04-26 19:35:52 +08:00
};
use bridge_runtime::{
2026-04-29 12:24:44 +08:00
execute_runtime_input, RuntimeActorWire, RuntimeBridgeContextWire, RuntimeCommandEnvelopeWire,
RuntimeCommandExecutionPlan, RuntimeExecutionPlan, RuntimeInput, RuntimeSourceWire,
2026-04-29 12:24:44 +08:00
RuntimeTargetWire,
};
use serde_json::Value;
pub fn runtime_context(
context: &RequestContext,
effective_workspace_id: Option<&str>,
) -> RuntimeBridgeContextWire {
RuntimeBridgeContextWire {
deployment_id: context.workspace.deployment_id.clone(),
project_id: context.workspace.project_id.clone(),
workspace_id: effective_workspace_id
.map(ToOwned::to_owned)
.or_else(|| context.workspace.workspace_id.clone()),
request_id: context.trace.request_id.clone(),
trace_id: context.trace.trace_id.clone(),
actor: RuntimeActorWire {
actor_type: context.auth.actor_type.clone(),
actor_id: context.auth.actor_id.clone(),
session_id: context.auth.session_id.clone(),
},
source: RuntimeSourceWire {
channel: context.source.channel.clone(),
client: context.source.client.clone(),
2026-05-08 00:41:03 +08:00
source_kind: None,
root_uri: None,
workspace_id: None,
capabilities: Vec::new(),
},
tenant_id: context.workspace.tenant_id.clone(),
auth_token: context.auth.authorization.clone(),
idempotency_key: context.source.idempotency_key.clone(),
validate_only: false,
dry_run: false,
}
}
pub fn build_runtime_command_plan(
context: &RequestContext,
effective_workspace_id: Option<&str>,
command: RuntimeCommandEnvelopeWire,
) -> Result<RuntimeCommandExecutionPlan, WebError> {
let runtime_input = RuntimeInput::Command {
context: runtime_context(context, effective_workspace_id),
command,
};
let RuntimeExecutionPlan::Command(plan) = execute_runtime_input(runtime_input)
.map_err(|error| WebError::bad_request(error.message).with_context(context))?
else {
return Err(WebError::internal("runtime command 未返回 command plan").with_context(context));
};
Ok(plan)
}
pub async fn execute_runtime_command_via_convex(
config: &AppConfig,
context: &RequestContext,
effective_workspace_id: Option<&str>,
command: RuntimeCommandEnvelopeWire,
) -> Result<Value, WebError> {
let plan = build_runtime_command_plan(context, effective_workspace_id, command)?;
execute_convex_command_plan(config, context, &plan).await
}
2026-04-26 19:35:52 +08:00
pub async fn execute_runtime_command_via_convex_with_artifacts(
config: &AppConfig,
context: &RequestContext,
effective_workspace_id: Option<&str>,
command: RuntimeCommandEnvelopeWire,
) -> Result<ConvexCommandExecution, WebError> {
let runtime_context = runtime_context(context, effective_workspace_id);
let runtime_input = RuntimeInput::Command {
context: runtime_context.clone(),
command: command.clone(),
};
let RuntimeExecutionPlan::Command(plan) = execute_runtime_input(runtime_input)
.map_err(|error| WebError::bad_request(error.message).with_context(context))?
else {
return Err(WebError::internal("runtime command 未返回 command plan").with_context(context));
};
execute_convex_command_plan_with_artifacts(config, context, &runtime_context, &command, &plan)
.await
}
pub fn build_tree_target(
workspace_id: &str,
page_id: Option<&str>,
block_id: Option<&str>,
) -> RuntimeTargetWire {
RuntimeTargetWire {
workspace_id: Some(workspace_id.to_string()),
page_id: page_id.map(ToOwned::to_owned),
block_id: block_id.map(ToOwned::to_owned),
}
}
pub fn ensure_non_empty(
value: &str,
field: &'static str,
context: &RequestContext,
) -> Result<String, WebError> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(WebError::bad_request_code(
"tree_command_validation",
format!("{field} 不能为空"),
)
.with_context(context)
.with_header("x-error-phase", "tree_command_validate"));
}
Ok(trimmed.to_string())
}
pub fn read_optional_non_empty(value: Option<String>) -> Option<String> {
value
.map(|item| item.trim().to_string())
.filter(|item| !item.is_empty())
}
pub fn ensure_sort_order(sort_order: i64, context: &RequestContext) -> Result<i64, WebError> {
if sort_order < 0 {
return Err(
WebError::bad_request_code("tree_command_validation", "sortOrder 不能小于 0")
.with_context(context)
.with_header("x-error-phase", "tree_command_validate"),
);
}
Ok(sort_order)
}