Files
mnote/rust/crates/storage-convex-bridge/src/lib.rs
T

432 lines
17 KiB
Rust
Raw Normal View History

2026-04-14 13:22:29 +08:00
pub mod context;
pub mod mapping;
pub mod read_path;
pub mod types;
pub mod validation;
pub mod write_path;
pub use context::BridgeContext;
pub use mapping::{
map_command_name_to_convex, map_query_name_to_convex, ConvexMutationRequest, ConvexQueryRequest,
};
pub use read_path::{build_query_request, QueryReadResult};
pub use types::{BridgeError, BridgeErrorKind, BridgeResult};
pub use validation::{validate_command_envelope, validate_query_envelope};
pub use write_path::{
build_command_log_record, build_domain_event_record, build_write_pipeline, build_write_request,
WritePipelineResult,
};
#[cfg(test)]
mod tests {
use super::*;
use core_protocol::{
command::{
CreatePage, CreateWorkspace, PatchPageBlock, ReplaceMediaAssetStorage,
SavePageContent, UpdatePageOptions, UpdatePageStats, UpdatePageTitle,
},
query::{GetPage, GetPageContent, GetPageMeta, ListSidebarDataset},
2026-04-14 13:22:29 +08:00
ActorPayload, CommandEnvelope, QueryEnvelope, SourcePayload, TargetRef,
};
fn demo_context() -> BridgeContext {
BridgeContext {
deployment_id: Some("dep_1".into()),
project_id: Some("proj_1".into()),
request_id: "req_1".into(),
trace_id: "trace_1".into(),
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
workspace_id: Some("ws_1".into()),
tenant_id: Some("tenant_1".into()),
auth_token: Some("token_1".into()),
source_channel: "cli".into(),
source_client: "mnote-cli".into(),
idempotency_key: Some("idem_ctx".into()),
validate_only: false,
dry_run: false,
}
}
#[test]
fn build_write_request_keeps_workspace_scope() {
let command = CommandEnvelope {
name: "create_page".into(),
command_id: "cmd_1".into(),
idempotency_key: Some("idem_cmd".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "cli".into(),
client: "mnote-cli".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: None,
block_id: None,
}),
payload: CreatePage {
title: "第一页".into(),
parent_page_id: None,
position: None,
},
reason: Some("初始化页面".into()),
refs: vec!["spec:phase1-f".into()],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "pages:create");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert_eq!(request.deployment_id.as_deref(), Some("dep_1"));
assert_eq!(request.project_id.as_deref(), Some("proj_1"));
assert_eq!(request.idempotency_key.as_deref(), Some("idem_cmd"));
assert!(request.payload_json.contains("\"kind\":\"command\""));
assert!(request.payload_json.contains("\"name\":\"create_page\""));
}
#[test]
fn build_query_request_includes_trace_and_workspace() {
let query = QueryEnvelope {
name: "get_page".into(),
payload: GetPage {
page_id: "page_1".into(),
},
};
let request = build_query_request(&demo_context(), &query).expect("query request should build");
assert_eq!(request.function_name, "pages:get");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert_eq!(request.request_id, "req_1");
assert_eq!(request.trace_id, "trace_1");
assert!(request.payload_json.contains("\"kind\":\"query\""));
assert!(request.payload_json.contains("\"name\":\"get_page\""));
}
#[test]
fn build_write_pipeline_sets_trace_fields() {
let command = CommandEnvelope {
name: "create_workspace".into(),
command_id: "cmd_2".into(),
idempotency_key: None,
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "cli".into(),
client: "mnote-cli".into(),
},
target: None,
payload: CreateWorkspace {
name: "demo".into(),
slug: Some("demo".into()),
},
reason: Some("初始化工作区".into()),
refs: vec![],
dry_run: false,
validate_only: false,
};
let pipeline = build_write_pipeline(&demo_context(), &command).expect("pipeline should build");
assert_eq!(pipeline.command_log.command_name, "create_workspace");
assert!(pipeline.command_log.payload_summary.contains("request_id=req_1"));
assert_eq!(pipeline.command_log.trace_id, "trace_1");
assert_eq!(pipeline.command_log.request_id, "req_1");
assert!(pipeline.domain_event.payload_json.contains("\"trace_id\":\"trace_1\""));
assert_eq!(pipeline.result.event_ids, vec!["evt_cmd_2".to_string()]);
}
#[test]
fn document_title_command_maps_to_documents_update_title() {
let command = CommandEnvelope {
name: "documents.title.update".into(),
command_id: "cmd_title_1".into(),
idempotency_key: Some("idem_title".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "next-route".into(),
client: "wolai-frontend".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: Some("page_1".into()),
block_id: None,
}),
payload: UpdatePageTitle {
page_id: "page_1".into(),
title: "新标题".into(),
},
reason: None,
refs: vec![],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "documents:updateTitle");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"documents.title.update\""));
}
#[test]
fn document_save_command_maps_to_documents_update_content() {
let command = CommandEnvelope {
name: "documents.save".into(),
command_id: "cmd_save_1".into(),
idempotency_key: Some("idem_save".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "next-route".into(),
client: "wolai-frontend".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: Some("page_1".into()),
block_id: None,
}),
payload: SavePageContent {
page_id: "page_1".into(),
workspace_id: Some("ws_1".into()),
revision: Some(42),
content_json: "{\"type\":\"doc\"}".into(),
conflict_detection_key: Some("rev:42".into()),
},
reason: Some("保存正文".into()),
refs: vec!["checklist:c3".into()],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "documents:updateContent");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"documents.save\""));
}
#[test]
fn block_patch_command_maps_to_documents_update_content() {
let command = CommandEnvelope {
name: "blocks.patch".into(),
command_id: "cmd_patch_1".into(),
idempotency_key: Some("idem_patch".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "next-route".into(),
client: "wolai-frontend".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: Some("page_1".into()),
block_id: Some("block_1".into()),
}),
payload: PatchPageBlock {
page_id: "page_1".into(),
block_id: "block_1".into(),
workspace_id: Some("ws_1".into()),
revision: Some(7),
block_snapshot_json: "{\"id\":\"block_1\",\"type\":\"paragraph\"}".into(),
conflict_detection_key: Some("page:1:block:1".into()),
},
reason: Some("替换块快照".into()),
refs: vec!["checklist:c3".into()],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "documents:updateContent");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"blocks.patch\""));
}
2026-04-14 13:22:29 +08:00
#[test]
fn document_stats_command_maps_to_documents_update_stats() {
let command = CommandEnvelope {
name: "documents.stats.update".into(),
command_id: "cmd_stats_1".into(),
idempotency_key: Some("idem_stats".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "next-route".into(),
client: "wolai-frontend".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: Some("page_1".into()),
block_id: None,
}),
payload: UpdatePageStats {
page_id: "page_1".into(),
word_count: 10,
character_count: 20,
block_count: 3,
todo_total: 4,
todo_done: 1,
},
reason: None,
refs: vec![],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "documents:updateStats");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"documents.stats.update\""));
}
#[test]
fn document_options_command_maps_to_documents_update_options() {
let command = CommandEnvelope {
name: "documents.options.update".into(),
command_id: "cmd_options_1".into(),
idempotency_key: Some("idem_options".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "next-route".into(),
client: "wolai-frontend".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: Some("page_1".into()),
block_id: None,
}),
payload: UpdatePageOptions {
page_id: "page_1".into(),
wide_layout: None,
small_text: None,
show_heading_numbers: None,
show_toc: Some(true),
show_structure: None,
protect_editing: None,
show_word_count: None,
collapse_backlinks: None,
page_font: None,
layout_density: Some("compact".into()),
hide_child_pages: None,
show_block_ref_count: None,
embed_default_block_id: None,
},
reason: None,
refs: vec![],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "documents:updateOptions");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"documents.options.update\""));
}
#[test]
fn media_asset_writeback_command_maps_to_replace_storage_from_upload() {
let command = CommandEnvelope {
name: "media.assets.replace_storage".into(),
command_id: "cmd_asset_1".into(),
idempotency_key: Some("idem_asset".into()),
actor: ActorPayload {
actor_type: "service".into(),
actor_id: "onlyoffice-callback".into(),
session_id: Some("onlyoffice".into()),
},
source: SourcePayload {
channel: "onlyoffice-callback".into(),
client: "wolai-frontend".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: Some("page_1".into()),
block_id: None,
}),
payload: ReplaceMediaAssetStorage {
asset_id: "asset_1".into(),
storage_id: "storage_1".into(),
},
reason: Some("ONLYOFFICE 回调写回附件".into()),
refs: vec!["checklist:d".into()],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "mediaAssets:replaceStorageFromUpload");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"media.assets.replace_storage\""));
}
#[test]
fn document_meta_query_maps_to_documents_get_meta() {
let query = QueryEnvelope {
name: "documents.meta.get".into(),
payload: GetPageMeta {
page_id: "page_1".into(),
workspace_id: Some("ws_1".into()),
},
};
let request = build_query_request(&demo_context(), &query).expect("query request should build");
assert_eq!(request.function_name, "documents:getMeta");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"documents.meta.get\""));
}
#[test]
fn document_content_query_maps_to_documents_get_content() {
let query = QueryEnvelope {
name: "documents.content.get".into(),
payload: GetPageContent {
page_id: "page_1".into(),
workspace_id: Some("ws_1".into()),
},
};
let request = build_query_request(&demo_context(), &query).expect("query request should build");
assert_eq!(request.function_name, "documents:getContent");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"documents.content.get\""));
}
#[test]
fn sidebar_dataset_query_maps_to_sidebar_dataset_list() {
let query = QueryEnvelope {
name: "sidebar.dataset.list".into(),
payload: ListSidebarDataset {
workspace_id: "ws_1".into(),
},
};
let request = build_query_request(&demo_context(), &query).expect("query request should build");
assert_eq!(request.function_name, "sidebar:datasetList");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"sidebar.dataset.list\""));
}
2026-04-14 13:22:29 +08:00
}