futu_backend/msg_header.rs
1//! 集中 `MsgHeader` builder (v1.4.110 P0-1)
2//!
3//! 取代 v1.4.110 之前散落 15+ 处直接 `odr_sys_cmn::MsgHeader { ... }` /
4//! `sim_odr_sys_cmn::MsgHeader { ... }` / `trade_cmn::CryptoMsgHeader { ... }`
5//! 构造模式. `req_id` 一律走 [`crate::trade_query::create_backend_req_id`]
6//! 派生, 防 v1.4.109 P0 类 dedupe bug (req_id 固定 → backend 幂等 cache →
7//! 误返 stale order echo). 见
8//! `essentials/2026-05-16-0725-v1.4.109-place-order-msgheader-req-id-not-unique-root-cause-codex-handoff-zh.md`.
9//!
10//! 配套 pre-push Check 6 (`.githooks/pre-push`, v1.4.110 P0-4) 用 grep 拦截
11//! 任何新加的直接构造, 强制走本 module 的 builder.
12//!
13//! # 三个 shape 差异
14//!
15//! - [`build_real`] → `odr_sys_cmn::MsgHeader` (real account, 7 fields,
16//! 含 `sub_account_id`)
17//! - [`build_sim`] → `sim_odr_sys_cmn::MsgHeader` (sim account, 7 fields,
18//! 含 `market` 替代 `sub_account_id`)
19//! - [`build_crypto`] → `trade_cmn::CryptoMsgHeader` (crypto, 仅 3 fields)
20
21#[cfg(test)]
22mod tests;
23
24use crate::proto_internal::{odr_sys_cmn, sim_odr_sys_cmn, trade_cmn};
25use crate::trade_query::create_backend_req_id;
26
27/// 构造 real-account `odr_sys_cmn::MsgHeader`.
28///
29/// # Args
30///
31/// - `acc_id`: 账户 id (填 `account_id` 字段; 同时作为 [`create_backend_req_id`]
32/// 的派生种子).
33/// - `cipher`: trade cipher.
34/// - `Some(vec![])` = cache-only query (wire 上 field-present 但为空 bytes).
35/// - 非空 `Some` = 持 unlock token 的写路径 (PlaceOrder / ModifyOrder /
36/// CancelOrder / OrderFillInfoReq w/ security_type).
37/// - `None` = 无 trade 上下文 (如 sim_acc_list / login-time queries).
38/// - `security_type`: PlaceOrder / ModifyOrder / OrderFillInfoReq 等需要;
39/// 纯 list / asset 查询填 `None`.
40/// - `input_source`: **仅 PlaceOrder 填** (C++ 行为, see
41/// `crates/futu-gateway-trd/src/handlers/trd/translate.rs` 的
42/// `PLACE_ORDER_MSG_INPUT_SOURCE`); modify / cancel / query 都 `None`.
43pub fn build_real(
44 acc_id: u64,
45 cipher: Option<Vec<u8>>,
46 security_type: Option<u32>,
47 input_source: Option<u32>,
48) -> odr_sys_cmn::MsgHeader {
49 odr_sys_cmn::MsgHeader {
50 req_id: Some(create_backend_req_id(acc_id)),
51 account_id: Some(acc_id),
52 cipher,
53 security_type,
54 exchange_code: None,
55 input_source,
56 sub_account_id: None,
57 }
58}
59
60/// 构造 sim-account `sim_odr_sys_cmn::MsgHeader`.
61///
62/// Sim shape 与 real shape 差异:
63/// - 多 `market` 字段 (sim 账户跨市场必填, see `trd_market_to_sim_market`).
64/// - 无 `sub_account_id`.
65///
66/// # Args
67///
68/// - `market`: sim 账户 market enum (`SimMarket_CN` / `SimMarket_HK` /
69/// `SimMarket_US` etc.). `None` 仅用于跨市场 list query.
70pub fn build_sim(
71 acc_id: u64,
72 cipher: Option<Vec<u8>>,
73 market: Option<u32>,
74 security_type: Option<u32>,
75) -> sim_odr_sys_cmn::MsgHeader {
76 sim_odr_sys_cmn::MsgHeader {
77 req_id: Some(create_backend_req_id(acc_id)),
78 account_id: Some(acc_id),
79 cipher,
80 security_type,
81 exchange_code: None,
82 input_source: None,
83 market,
84 }
85}
86
87/// 构造 crypto `trade_cmn::CryptoMsgHeader` (仅 3 字段).
88///
89/// 空 cipher 处理为 `None` 而非 `Some(vec![])` (对齐 v1.4.110 之前
90/// `crypto_trade.rs::build_crypto_msg_header` 原行为, C++ 参照见
91/// `NNProto_Trd_OrderOpCrypto.cpp:40-48`).
92pub fn build_crypto(acc_id: u64, cipher: Vec<u8>) -> trade_cmn::CryptoMsgHeader {
93 trade_cmn::CryptoMsgHeader {
94 req_id: Some(create_backend_req_id(acc_id)),
95 account_id: Some(acc_id),
96 cipher: if cipher.is_empty() {
97 None
98 } else {
99 Some(cipher)
100 },
101 }
102}