Skip to main content

futu_qot/
symbol_list.rs

1//! Compatibility adapter for list-style QOT input contracts.
2//!
3//! The fail-closed list validation/resolution contract lives in
4//! `futu-core::qot_symbol_list`. This module preserves the historical
5//! `futu_qot::symbol_list` API for proto `Security` callers.
6
7use futu_proto::qot_common::Security;
8
9/// 已通过基本契约校验的 security 列表 — 至少 1 项,且每项 market / code 都非空。
10///
11/// 所有 list-type handler 必须用此类型(而不是裸 `Vec<Security>`)作为输入承载,
12/// 防止 silent-fallback / partial-success 反模式。
13#[derive(Debug, Clone)]
14pub struct ParsedSymbolList {
15    /// 已校验的 security 列表(保留原顺序,便于 handler 按序构造响应)。
16    pub securities: Vec<Security>,
17}
18
19impl ParsedSymbolList {
20    /// 列表长度(保证 ≥1,因为只有通过 [`parse_required_symbol_list`] 才能构造)。
21    pub fn len(&self) -> usize {
22        self.securities.len()
23    }
24
25    /// 永远 false(构造函数保证非空),仅为 clippy `len_without_is_empty` 提供配套。
26    pub fn is_empty(&self) -> bool {
27        self.securities.is_empty()
28    }
29
30    /// 借用底层切片。
31    pub fn as_slice(&self) -> &[Security] {
32        &self.securities
33    }
34}
35
36pub fn parse_required_symbol_list(securities: &[Security]) -> Result<ParsedSymbolList, String> {
37    let facts = securities_to_core_facts(securities);
38    futu_core::qot_symbol_list::parse_required_symbol_list(&facts)?;
39    Ok(ParsedSymbolList {
40        securities: securities.to_vec(),
41    })
42}
43
44pub fn resolve_required_stock_ids<F>(
45    parsed: &ParsedSymbolList,
46    mut resolver: F,
47) -> Result<Vec<(Security, u64)>, String>
48where
49    F: FnMut(&Security) -> Option<u64>,
50{
51    let facts = securities_to_core_facts(&parsed.securities);
52    let core_parsed = futu_core::qot_symbol_list::parse_required_symbol_list(&facts)?;
53    futu_core::qot_symbol_list::resolve_required_stock_ids(&core_parsed, |sec| {
54        resolver(&Security {
55            market: sec.market,
56            code: sec.code.clone(),
57        })
58    })
59    .map(|resolved| {
60        resolved
61            .into_iter()
62            .map(|(sec, stock_id)| {
63                (
64                    Security {
65                        market: sec.market,
66                        code: sec.code,
67                    },
68                    stock_id,
69                )
70            })
71            .collect()
72    })
73}
74
75fn securities_to_core_facts(
76    securities: &[Security],
77) -> Vec<futu_core::qot_symbol_list::QotSymbolListSecurity> {
78    securities
79        .iter()
80        .map(|sec| futu_core::qot_symbol_list::QotSymbolListSecurity {
81            market: sec.market,
82            code: sec.code.clone(),
83        })
84        .collect()
85}
86
87#[cfg(test)]
88mod tests;