0.6 rust重构01
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "storage-convex-bridge"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
core-domain = { path = "../core-domain" }
|
||||
core-protocol = { path = "../core-protocol" }
|
||||
event-log = { path = "../event-log" }
|
||||
@@ -0,0 +1,18 @@
|
||||
# storage-convex-bridge
|
||||
|
||||
阶段 1 的 Convex 桥接 crate。
|
||||
|
||||
当前职责:
|
||||
- 把 Rust 协议对象映射为 Convex 读写请求
|
||||
- 透传 request / actor / workspace / auth 上下文
|
||||
- 生成最小 CommandLog / DomainEvent 骨架
|
||||
|
||||
当前明确不做:
|
||||
- 不复制主事实层
|
||||
- 不维护第二数据库
|
||||
- 不隐藏 tenant / auth / request context
|
||||
- 不承载复杂业务裁决
|
||||
|
||||
相关设计文档:
|
||||
- `../../design/phase1-f-bridge-responsibilities-v0.md`
|
||||
- `../../design/phase1-f-write-read-flow-v0.md`
|
||||
@@ -0,0 +1,18 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BridgeContext {
|
||||
pub deployment_id: Option<String>,
|
||||
pub project_id: Option<String>,
|
||||
pub workspace_id: Option<String>,
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
pub actor_type: String,
|
||||
pub actor_id: String,
|
||||
pub session_id: Option<String>,
|
||||
pub tenant_id: Option<String>,
|
||||
pub auth_token: Option<String>,
|
||||
pub source_channel: String,
|
||||
pub source_client: String,
|
||||
pub idempotency_key: Option<String>,
|
||||
pub validate_only: bool,
|
||||
pub dry_run: bool,
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
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, UpdatePageOptions, UpdatePageStats, UpdatePageTitle},
|
||||
query::GetPage,
|
||||
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_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\""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use crate::context::BridgeContext;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConvexMutationRequest {
|
||||
pub function_name: String,
|
||||
pub deployment_id: Option<String>,
|
||||
pub project_id: Option<String>,
|
||||
pub workspace_id: Option<String>,
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
pub idempotency_key: Option<String>,
|
||||
pub actor_id: String,
|
||||
pub payload_json: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConvexQueryRequest {
|
||||
pub function_name: String,
|
||||
pub deployment_id: Option<String>,
|
||||
pub project_id: Option<String>,
|
||||
pub workspace_id: Option<String>,
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
pub actor_id: String,
|
||||
pub payload_json: String,
|
||||
}
|
||||
|
||||
pub fn map_command_name_to_convex(command_name: &str) -> &'static str {
|
||||
match command_name {
|
||||
"create_workspace" => "workspaces:create",
|
||||
"create_page" => "pages:create",
|
||||
"documents.title.update" => "documents:updateTitle",
|
||||
"documents.stats.update" => "documents:updateStats",
|
||||
"documents.options.update" => "documents:updateOptions",
|
||||
"insert_block" => "blocks:insert",
|
||||
"update_block" => "blocks:update",
|
||||
"move_block" => "blocks:move",
|
||||
"delete_block" => "blocks:delete",
|
||||
_ => "commands:unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_query_name_to_convex(query_name: &str) -> &'static str {
|
||||
match query_name {
|
||||
"get_page" => "pages:get",
|
||||
"list_page_blocks" => "blocks:list_by_page",
|
||||
"search_pages" => "search:pages",
|
||||
"search_blocks" => "search:blocks",
|
||||
_ => "queries:unknown",
|
||||
}
|
||||
}
|
||||
|
||||
fn json_opt(value: &Option<String>) -> String {
|
||||
match value {
|
||||
Some(v) => format!("\"{}\"", v),
|
||||
None => "null".into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn payload_json_for_command(context: &BridgeContext, command_name: &str) -> String {
|
||||
format!(
|
||||
"{{\"kind\":\"command\",\"name\":\"{}\",\"request_id\":\"{}\",\"trace_id\":\"{}\",\"deployment_id\":{},\"project_id\":{},\"workspace_id\":{},\"tenant_id\":{},\"idempotency_key\":{},\"actor\":{{\"type\":\"{}\",\"id\":\"{}\",\"session_id\":{}}},\"source\":{{\"channel\":\"{}\",\"client\":\"{}\"}}}}",
|
||||
command_name,
|
||||
context.request_id,
|
||||
context.trace_id,
|
||||
json_opt(&context.deployment_id),
|
||||
json_opt(&context.project_id),
|
||||
json_opt(&context.workspace_id),
|
||||
json_opt(&context.tenant_id),
|
||||
json_opt(&context.idempotency_key),
|
||||
context.actor_type,
|
||||
context.actor_id,
|
||||
json_opt(&context.session_id),
|
||||
context.source_channel,
|
||||
context.source_client,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn payload_json_for_query(context: &BridgeContext, query_name: &str) -> String {
|
||||
format!(
|
||||
"{{\"kind\":\"query\",\"name\":\"{}\",\"request_id\":\"{}\",\"trace_id\":\"{}\",\"deployment_id\":{},\"project_id\":{},\"workspace_id\":{},\"tenant_id\":{},\"actor_id\":\"{}\",\"source\":{{\"channel\":\"{}\",\"client\":\"{}\"}}}}",
|
||||
query_name,
|
||||
context.request_id,
|
||||
context.trace_id,
|
||||
json_opt(&context.deployment_id),
|
||||
json_opt(&context.project_id),
|
||||
json_opt(&context.workspace_id),
|
||||
json_opt(&context.tenant_id),
|
||||
context.actor_id,
|
||||
context.source_channel,
|
||||
context.source_client,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::context::BridgeContext;
|
||||
use crate::mapping::{map_query_name_to_convex, payload_json_for_query, ConvexQueryRequest};
|
||||
use crate::types::BridgeResult;
|
||||
use crate::validation::validate_query_envelope;
|
||||
use core_protocol::QueryEnvelope;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct QueryReadResult {
|
||||
pub workspace_id: Option<String>,
|
||||
pub request_id: String,
|
||||
pub trace_id: String,
|
||||
}
|
||||
|
||||
pub fn build_query_request<T>(
|
||||
context: &BridgeContext,
|
||||
query: &QueryEnvelope<T>,
|
||||
) -> BridgeResult<ConvexQueryRequest> {
|
||||
validate_query_envelope(context, query)?;
|
||||
Ok(ConvexQueryRequest {
|
||||
function_name: map_query_name_to_convex(&query.name).to_string(),
|
||||
deployment_id: context.deployment_id.clone(),
|
||||
project_id: context.project_id.clone(),
|
||||
workspace_id: context.workspace_id.clone(),
|
||||
request_id: context.request_id.clone(),
|
||||
trace_id: context.trace_id.clone(),
|
||||
actor_id: context.actor_id.clone(),
|
||||
payload_json: payload_json_for_query(context, &query.name),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BridgeErrorKind {
|
||||
Validation,
|
||||
Unauthorized,
|
||||
Conflict,
|
||||
NotFound,
|
||||
Transport,
|
||||
Rejected,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BridgeError {
|
||||
pub kind: BridgeErrorKind,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl BridgeError {
|
||||
pub fn validation(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: BridgeErrorKind::Validation,
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type BridgeResult<T> = Result<T, BridgeError>;
|
||||
@@ -0,0 +1,50 @@
|
||||
use crate::context::BridgeContext;
|
||||
use crate::types::{BridgeError, BridgeResult};
|
||||
use core_protocol::{CommandEnvelope, QueryEnvelope};
|
||||
|
||||
pub fn validate_command_envelope<T>(
|
||||
context: &BridgeContext,
|
||||
command: &CommandEnvelope<T>,
|
||||
) -> BridgeResult<()> {
|
||||
if command.name.trim().is_empty() {
|
||||
return Err(BridgeError::validation("command name 不能为空"));
|
||||
}
|
||||
if command.command_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("command_id 不能为空"));
|
||||
}
|
||||
if context.request_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("request_id 不能为空"));
|
||||
}
|
||||
if context.trace_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("trace_id 不能为空"));
|
||||
}
|
||||
if context.actor_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("actor_id 不能为空"));
|
||||
}
|
||||
if context
|
||||
.workspace_id
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.trim()
|
||||
.is_empty()
|
||||
{
|
||||
return Err(BridgeError::validation("workspace_id 不能为空"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_query_envelope<T>(
|
||||
context: &BridgeContext,
|
||||
query: &QueryEnvelope<T>,
|
||||
) -> BridgeResult<()> {
|
||||
if query.name.trim().is_empty() {
|
||||
return Err(BridgeError::validation("query name 不能为空"));
|
||||
}
|
||||
if context.request_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("request_id 不能为空"));
|
||||
}
|
||||
if context.trace_id.trim().is_empty() {
|
||||
return Err(BridgeError::validation("trace_id 不能为空"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
use crate::context::BridgeContext;
|
||||
use crate::mapping::{map_command_name_to_convex, payload_json_for_command, ConvexMutationRequest};
|
||||
use crate::types::BridgeResult;
|
||||
use crate::validation::validate_command_envelope;
|
||||
use core_domain::{RefLink, Timestamp, WorkspaceId};
|
||||
use core_protocol::{command::CommandResult, AffectedObject, CommandEnvelope};
|
||||
use event_log::{CommandLogRecord, CommandLogStatus, DomainEventRecord, EventStatus};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WritePipelineResult {
|
||||
pub mutation: ConvexMutationRequest,
|
||||
pub command_log: CommandLogRecord,
|
||||
pub domain_event: DomainEventRecord,
|
||||
pub result: CommandResult,
|
||||
}
|
||||
|
||||
pub fn build_write_request<T>(
|
||||
context: &BridgeContext,
|
||||
command: &CommandEnvelope<T>,
|
||||
) -> BridgeResult<ConvexMutationRequest> {
|
||||
validate_command_envelope(context, command)?;
|
||||
let workspace_id = command
|
||||
.target
|
||||
.as_ref()
|
||||
.and_then(|target| target.workspace_id.clone())
|
||||
.or_else(|| context.workspace_id.clone());
|
||||
Ok(ConvexMutationRequest {
|
||||
function_name: map_command_name_to_convex(&command.name).to_string(),
|
||||
deployment_id: context.deployment_id.clone(),
|
||||
project_id: context.project_id.clone(),
|
||||
workspace_id,
|
||||
request_id: context.request_id.clone(),
|
||||
trace_id: context.trace_id.clone(),
|
||||
idempotency_key: command
|
||||
.idempotency_key
|
||||
.clone()
|
||||
.or_else(|| context.idempotency_key.clone()),
|
||||
actor_id: context.actor_id.clone(),
|
||||
payload_json: payload_json_for_command(context, &command.name),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_command_log_record(
|
||||
command_id: &str,
|
||||
command_name: &str,
|
||||
context: &BridgeContext,
|
||||
workspace_id: &str,
|
||||
) -> CommandLogRecord {
|
||||
CommandLogRecord {
|
||||
command_log_id: format!("clog_{command_id}"),
|
||||
command_name: command_name.into(),
|
||||
actor_type: context.actor_type.clone(),
|
||||
actor_id: context.actor_id.clone(),
|
||||
source: context.source_channel.clone(),
|
||||
workspace_id: WorkspaceId::new(workspace_id),
|
||||
target_objects: vec![RefLink::External(format!("workspace:{workspace_id}"))],
|
||||
payload_summary: format!(
|
||||
"command={command_name};request_id={};trace_id={}",
|
||||
context.request_id, context.trace_id
|
||||
),
|
||||
refs_json: format!(
|
||||
"[\"request:{}\",\"trace:{}\"]",
|
||||
context.request_id, context.trace_id
|
||||
),
|
||||
idempotency_key: context.idempotency_key.clone(),
|
||||
status: CommandLogStatus::Pending,
|
||||
created_at: Timestamp::new("2026-04-11T00:00:00Z"),
|
||||
finished_at: None,
|
||||
trace_id: context.trace_id.clone(),
|
||||
request_id: context.request_id.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_domain_event_record(
|
||||
command_id: &str,
|
||||
command_name: &str,
|
||||
context: &BridgeContext,
|
||||
workspace_id: &str,
|
||||
) -> DomainEventRecord {
|
||||
DomainEventRecord {
|
||||
event_id: format!("evt_{command_id}"),
|
||||
workspace_id: workspace_id.into(),
|
||||
aggregate_type: "workspace".into(),
|
||||
aggregate_id: workspace_id.into(),
|
||||
event_type: format!("{command_name}_requested"),
|
||||
event_version: 1,
|
||||
payload_json: format!(
|
||||
"{{\"request_id\":\"{}\",\"trace_id\":\"{}\",\"command_id\":\"{}\",\"command_name\":\"{}\"}}",
|
||||
context.request_id, context.trace_id, command_id, command_name
|
||||
),
|
||||
command_log_id: format!("clog_{command_id}"),
|
||||
actor_type: context.actor_type.clone(),
|
||||
created_at: Timestamp::new("2026-04-11T00:00:00Z"),
|
||||
status: EventStatus::Pending,
|
||||
trace_id: context.trace_id.clone(),
|
||||
request_id: context.request_id.clone(),
|
||||
command_id: command_id.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_write_pipeline<T>(
|
||||
context: &BridgeContext,
|
||||
command: &CommandEnvelope<T>,
|
||||
) -> BridgeResult<WritePipelineResult> {
|
||||
let mutation = build_write_request(context, command)?;
|
||||
let workspace_id = mutation
|
||||
.workspace_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| "workspace_unknown".into());
|
||||
let command_log =
|
||||
build_command_log_record(&command.command_id, &command.name, context, &workspace_id);
|
||||
let domain_event =
|
||||
build_domain_event_record(&command.command_id, &command.name, context, &workspace_id);
|
||||
let result = CommandResult {
|
||||
ok: true,
|
||||
command_id: command.command_id.clone(),
|
||||
event_ids: vec![domain_event.event_id.clone()],
|
||||
affected_objects: vec![AffectedObject {
|
||||
object_type: "workspace".into(),
|
||||
object_id: workspace_id.clone(),
|
||||
}],
|
||||
revision: Some(1),
|
||||
warnings: vec![],
|
||||
};
|
||||
Ok(WritePipelineResult {
|
||||
mutation,
|
||||
command_log,
|
||||
domain_event,
|
||||
result,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user