2026-04-16 22:01:51 +08:00
|
|
|
use crate::app::AppConfig;
|
2026-04-17 00:25:28 +08:00
|
|
|
use crate::context::RequestContext;
|
2026-04-16 22:01:51 +08:00
|
|
|
use crate::error::WebError;
|
2026-04-26 19:35:52 +08:00
|
|
|
use bridge_runtime::{
|
2026-05-28 22:01:44 +08:00
|
|
|
RuntimeCommandArtifactPlan, RuntimeCommandExecutionPlan, RuntimeQueryExecutionPlan,
|
2026-04-26 19:35:52 +08:00
|
|
|
};
|
2026-05-28 22:01:44 +08:00
|
|
|
use serde_json::Value;
|
2026-04-16 22:01:51 +08:00
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct RetiredCloudCommandExecution {
|
|
|
|
|
pub result: Value,
|
|
|
|
|
pub artifacts: Option<RuntimeCommandArtifactPlan>,
|
|
|
|
|
pub artifact_error: Option<String>,
|
2026-05-13 22:43:16 +08:00
|
|
|
}
|
2026-04-17 00:25:28 +08:00
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
fn retired_error(context: &RequestContext, phase: &'static str) -> WebError {
|
|
|
|
|
WebError::service_unavailable_code(
|
|
|
|
|
"convex_retired",
|
|
|
|
|
"Convex 运行时已退役;请使用 local-first Rust/SQLite control-plane 路径",
|
|
|
|
|
)
|
|
|
|
|
.with_context(context)
|
|
|
|
|
.with_header("x-error-phase", phase)
|
|
|
|
|
.with_header("x-upstream-service", "convex-retired")
|
2026-04-16 22:01:51 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 00:25:28 +08:00
|
|
|
fn load_query_fixture(
|
2026-04-16 22:01:51 +08:00
|
|
|
config: &AppConfig,
|
2026-04-17 00:25:28 +08:00
|
|
|
context: &RequestContext,
|
|
|
|
|
plan: &RuntimeQueryExecutionPlan,
|
|
|
|
|
) -> Result<Option<Value>, WebError> {
|
|
|
|
|
if !config.allow_dev_fixtures {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 23:36:24 +08:00
|
|
|
let Some(raw) = config.query_fixtures_json.as_deref() else {
|
2026-04-17 00:25:28 +08:00
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
let trimmed = raw.trim();
|
|
|
|
|
if trimmed.is_empty() {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let fixtures: Value = serde_json::from_str(trimmed).map_err(|error| {
|
|
|
|
|
WebError::internal(format!("MNOTE_WEB_QUERY_FIXTURES_JSON 非法: {error}"))
|
|
|
|
|
.with_context(context)
|
|
|
|
|
.with_header("x-error-phase", "fixture_parse")
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
Ok(fixtures
|
|
|
|
|
.as_object()
|
|
|
|
|
.and_then(|map| map.get(plan.function_name.as_str()))
|
|
|
|
|
.cloned())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 19:35:52 +08:00
|
|
|
fn load_mutation_fixture_by_name(
|
|
|
|
|
config: &AppConfig,
|
|
|
|
|
context: &RequestContext,
|
|
|
|
|
function_name: &str,
|
2026-04-17 23:36:24 +08:00
|
|
|
) -> Result<Option<Value>, WebError> {
|
|
|
|
|
if !config.allow_dev_fixtures {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let Some(raw) = config.mutation_fixtures_json.as_deref() else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
let trimmed = raw.trim();
|
|
|
|
|
if trimmed.is_empty() {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let fixtures: Value = serde_json::from_str(trimmed).map_err(|error| {
|
|
|
|
|
WebError::internal(format!("MNOTE_WEB_MUTATION_FIXTURES_JSON 非法: {error}"))
|
|
|
|
|
.with_context(context)
|
|
|
|
|
.with_header("x-error-phase", "fixture_parse")
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
Ok(fixtures
|
|
|
|
|
.as_object()
|
2026-04-26 19:35:52 +08:00
|
|
|
.and_then(|map| map.get(function_name))
|
2026-04-17 23:36:24 +08:00
|
|
|
.cloned())
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
fn load_mutation_fixture(
|
|
|
|
|
config: &AppConfig,
|
|
|
|
|
context: &RequestContext,
|
|
|
|
|
plan: &RuntimeCommandExecutionPlan,
|
|
|
|
|
) -> Result<Option<Value>, WebError> {
|
|
|
|
|
load_mutation_fixture_by_name(config, context, plan.function_name.as_str())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn execute_retired_query_plan(
|
2026-04-17 00:25:28 +08:00
|
|
|
config: &AppConfig,
|
|
|
|
|
context: &RequestContext,
|
2026-04-16 22:01:51 +08:00
|
|
|
plan: &RuntimeQueryExecutionPlan,
|
|
|
|
|
) -> Result<Value, WebError> {
|
2026-04-17 00:25:28 +08:00
|
|
|
if plan.function_name.trim().is_empty() || plan.function_name.ends_with(":unknown") {
|
|
|
|
|
return Err(WebError::bad_request_code(
|
|
|
|
|
"transport_query_unsupported",
|
|
|
|
|
format!("mnote-web transport 暂不支持 query: {}", plan.function_name),
|
|
|
|
|
)
|
|
|
|
|
.with_context(context)
|
|
|
|
|
.with_header("x-error-phase", "plan_validation"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(fixture) = load_query_fixture(config, context, plan)? {
|
|
|
|
|
return Ok(fixture);
|
2026-04-16 22:01:51 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
Err(retired_error(context, "convex_query_retired"))
|
2026-04-16 22:01:51 +08:00
|
|
|
}
|
2026-04-17 00:25:28 +08:00
|
|
|
|
2026-04-17 23:36:24 +08:00
|
|
|
pub async fn execute_sidebar_dataset_query(
|
2026-04-17 00:25:28 +08:00
|
|
|
config: &AppConfig,
|
|
|
|
|
context: &RequestContext,
|
|
|
|
|
plan: &RuntimeQueryExecutionPlan,
|
|
|
|
|
) -> Result<Value, WebError> {
|
2026-05-28 22:01:44 +08:00
|
|
|
execute_retired_query_plan(config, context, plan).await
|
2026-04-17 23:36:24 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
pub async fn execute_retired_query_by_name(
|
2026-05-11 08:27:52 +08:00
|
|
|
config: &AppConfig,
|
|
|
|
|
context: &RequestContext,
|
|
|
|
|
function_name: &str,
|
|
|
|
|
args: Value,
|
|
|
|
|
workspace_id: Option<&str>,
|
|
|
|
|
error_phase: &'static str,
|
|
|
|
|
) -> Result<Value, WebError> {
|
|
|
|
|
let plan = RuntimeQueryExecutionPlan {
|
|
|
|
|
query_name: function_name.to_string(),
|
|
|
|
|
function_name: function_name.to_string(),
|
|
|
|
|
workspace_id: workspace_id.map(ToOwned::to_owned),
|
|
|
|
|
request_id: context.trace.request_id.clone(),
|
|
|
|
|
trace_id: context.trace.trace_id.clone(),
|
|
|
|
|
actor_id: context.auth.actor_id.clone(),
|
|
|
|
|
payload_json: args.to_string(),
|
|
|
|
|
args_json: args,
|
|
|
|
|
};
|
2026-05-28 22:01:44 +08:00
|
|
|
execute_retired_query_plan(config, context, &plan)
|
2026-05-11 08:27:52 +08:00
|
|
|
.await
|
|
|
|
|
.map_err(|error| error.with_header("x-error-phase", error_phase))
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
pub async fn execute_retired_command_plan(
|
2026-04-17 23:36:24 +08:00
|
|
|
config: &AppConfig,
|
|
|
|
|
context: &RequestContext,
|
|
|
|
|
plan: &RuntimeCommandExecutionPlan,
|
|
|
|
|
) -> Result<Value, WebError> {
|
|
|
|
|
if plan.function_name.trim().is_empty() || plan.function_name.ends_with(":unknown") {
|
|
|
|
|
return Err(WebError::bad_request_code(
|
|
|
|
|
"transport_command_unsupported",
|
2026-04-18 09:38:16 +08:00
|
|
|
format!(
|
|
|
|
|
"mnote-web transport 暂不支持 command: {}",
|
|
|
|
|
plan.function_name
|
|
|
|
|
),
|
2026-04-17 23:36:24 +08:00
|
|
|
)
|
|
|
|
|
.with_context(context)
|
|
|
|
|
.with_header("x-error-phase", "plan_validation"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(fixture) = load_mutation_fixture(config, context, plan)? {
|
|
|
|
|
return Ok(fixture);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
Err(retired_error(context, "convex_mutation_retired"))
|
2026-04-17 00:25:28 +08:00
|
|
|
}
|
2026-04-18 09:38:16 +08:00
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
pub async fn execute_retired_mutation_by_name(
|
2026-04-26 19:35:52 +08:00
|
|
|
config: &AppConfig,
|
|
|
|
|
context: &RequestContext,
|
|
|
|
|
function_name: &str,
|
|
|
|
|
args: Value,
|
2026-05-28 22:01:44 +08:00
|
|
|
_workspace_id: Option<&str>,
|
|
|
|
|
_idempotency_key: Option<&str>,
|
2026-04-26 19:35:52 +08:00
|
|
|
error_phase: &'static str,
|
|
|
|
|
) -> Result<Value, WebError> {
|
|
|
|
|
if let Some(fixture) = load_mutation_fixture_by_name(config, context, function_name)? {
|
|
|
|
|
return Ok(fixture);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
let _ = args;
|
|
|
|
|
Err(retired_error(context, error_phase))
|
2026-04-26 19:35:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn persist_runtime_command_artifacts(
|
2026-05-28 22:01:44 +08:00
|
|
|
_config: &AppConfig,
|
2026-04-26 19:35:52 +08:00
|
|
|
context: &RequestContext,
|
2026-05-28 22:01:44 +08:00
|
|
|
_artifacts: &RuntimeCommandArtifactPlan,
|
2026-04-26 19:35:52 +08:00
|
|
|
) -> Result<(), WebError> {
|
2026-05-28 22:01:44 +08:00
|
|
|
Err(retired_error(context, "convex_artifacts_retired"))
|
2026-04-26 19:35:52 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 22:01:44 +08:00
|
|
|
pub async fn execute_retired_command_plan_with_artifacts(
|
2026-04-26 19:35:52 +08:00
|
|
|
config: &AppConfig,
|
|
|
|
|
context: &RequestContext,
|
2026-05-28 22:01:44 +08:00
|
|
|
_runtime_context: &bridge_runtime::RuntimeBridgeContextWire,
|
|
|
|
|
_command: &bridge_runtime::RuntimeCommandEnvelopeWire,
|
2026-04-26 19:35:52 +08:00
|
|
|
plan: &RuntimeCommandExecutionPlan,
|
2026-05-28 22:01:44 +08:00
|
|
|
) -> Result<RetiredCloudCommandExecution, WebError> {
|
|
|
|
|
let result = execute_retired_command_plan(config, context, plan).await?;
|
|
|
|
|
Ok(RetiredCloudCommandExecution {
|
2026-04-26 19:35:52 +08:00
|
|
|
result,
|
2026-05-28 22:01:44 +08:00
|
|
|
artifacts: None,
|
2026-04-26 19:35:52 +08:00
|
|
|
artifact_error: None,
|
|
|
|
|
})
|
|
|
|
|
}
|