use crate::app::AppConfig; use crate::context::RequestContext; use crate::error::WebError; use crate::transport::convex::{ execute_convex_command_plan, execute_convex_command_plan_with_artifacts, ConvexCommandExecution, }; use bridge_runtime::{ execute_runtime_input, RuntimeActorWire, RuntimeBridgeContextWire, RuntimeCommandEnvelopeWire, RuntimeCommandExecutionPlan, RuntimeExecutionPlan, RuntimeInput, RuntimeSourceWire, 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(), 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 { 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 { let plan = build_runtime_command_plan(context, effective_workspace_id, command)?; execute_convex_command_plan(config, context, &plan).await } pub async fn execute_runtime_command_via_convex_with_artifacts( config: &AppConfig, context: &RequestContext, effective_workspace_id: Option<&str>, command: RuntimeCommandEnvelopeWire, ) -> Result { 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 { 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) -> Option { value .map(|item| item.trim().to_string()) .filter(|item| !item.is_empty()) } pub fn ensure_sort_order(sort_order: i64, context: &RequestContext) -> Result { 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) }