1use rmcp::schemars;
4use serde::Deserialize;
5
6use futu_core::qot_endpoint_market;
7
8use crate::tool_enums;
9
10use super::*;
11
12mod basic;
13mod f10;
14mod misc;
15mod price_reminder;
16mod reference;
17mod screen_unusual;
18mod shareholders;
19
20pub use basic::{
21 KLineReq, MarketStateReq, OptionExerciseProbabilityReq, OptionVolatilityReq, OrderBookReq,
22 PlateListReq, PlateStocksReq, ShortInfoReq, SymbolListReq, SymbolReq, TickerReq,
23 TopTenBuySellBrokersReq,
24};
25pub use f10::{
26 CompanyExecutiveBackgroundReq, CompanyOperationalEfficiencyReq, FinancialCalendarReq,
27 FinancialsEarningsPriceHistoryReq, FinancialsEarningsPriceMoveReq,
28 FinancialsRevenueBreakdownReq, FinancialsStatementsReq, ResearchAnalystConsensusReq,
29 ResearchMorningstarReportReq, ResearchRatingSummaryReq, TargetFinancialCalendarReq,
30 ValuationDetailReq, ValuationPlateStockListReq,
31};
32pub use misc::{
33 BizGroupReq, BondSymbolReq, CodeChangeReq, HistoryKlQuotaReq, HoldingChangeReq,
34 ModifyUserSecurityReq, QuoteRightsReq, StockFilterReq, SuspendReq, TickerStatisticDetailReq,
35 TickerStatisticReq, TradingDaysReq, UserSecurityGroupReq, UserSecurityReq,
36};
37pub use price_reminder::{GetPriceReminderReq, OptionExpirationDateReq, SetPriceReminderReq};
38pub use reference::{
39 FutureInfoReq, HistoryKLineReq, IpoCalendarReq, IpoListReq, OptionChainReq, ReferenceReq,
40 WarrantReq,
41};
42pub use screen_unusual::{
43 DerivativeUnusualReq, FinancialUnusualReq, OptionScreenReq, StockScreenReq,
44 TechnicalUnusualReq, WarrantScreenReq,
45};
46pub use shareholders::{
47 InsiderHolderListReq, InsiderTradeListReq, ShareholdersHolderDetailReq,
48 ShareholdersHoldingChangesReq, ShareholdersInstitutionalReq, ShareholdersOverviewReq,
49};
50
51fn validate_optional_i32_range(
52 tool: &str,
53 field: &str,
54 value: Option<i32>,
55 min: i32,
56 max: i32,
57) -> Result<(), String> {
58 if let Some(value) = value
59 && !(min..=max).contains(&value)
60 {
61 return Err(format!(
62 "{field} must be in {min}..={max} for {tool}, got {value}"
63 ));
64 }
65 Ok(())
66}
67
68fn validate_optional_f64_min_max(
69 tool: &str,
70 field: &str,
71 min: Option<f64>,
72 max: Option<f64>,
73) -> Result<(), String> {
74 if let (Some(min), Some(max)) = (min, max)
75 && min > max
76 {
77 return Err(format!("{field}_min must be <= {field}_max for {tool}"));
78 }
79 Ok(())
80}
81
82fn deser_stock_filter_market_as_i32<'de, D>(deserializer: D) -> Result<i32, D::Error>
83where
84 D: serde::Deserializer<'de>,
85{
86 deser_endpoint_market_as_i32(
87 deserializer,
88 qot_endpoint_market::QOT_STOCK_FILTER_MARKET_VALID_VALUES,
89 qot_endpoint_market::stock_filter_market_from_str_alias,
90 )
91}
92
93fn deser_ipo_market_as_i32<'de, D>(deserializer: D) -> Result<i32, D::Error>
94where
95 D: serde::Deserializer<'de>,
96{
97 deser_endpoint_market_as_i32(
98 deserializer,
99 qot_endpoint_market::QOT_IPO_MARKET_VALID_VALUES,
100 qot_endpoint_market::ipo_market_from_str_alias,
101 )
102}
103
104fn deser_endpoint_market_as_i32<'de, D>(
105 deserializer: D,
106 valid_values: &str,
107 parse_alias: impl Fn(&str) -> Option<i32>,
108) -> Result<i32, D::Error>
109where
110 D: serde::Deserializer<'de>,
111{
112 #[derive(Deserialize)]
113 #[serde(untagged)]
114 enum IntOrStr {
115 Int(i32),
116 Str(String),
117 }
118
119 match IntOrStr::deserialize(deserializer)? {
120 IntOrStr::Int(value) => Ok(value),
121 IntOrStr::Str(value) => {
122 if let Some(value) = parse_alias(&value) {
123 return Ok(value);
124 }
125 Err(serde::de::Error::custom(format!(
126 "unknown market {value:?}: valid = {valid_values}"
127 )))
128 }
129 }
130}