futucli/cmd/trade_ext/
parsers.rs1use anyhow::{Context, Result, bail};
2use futu_core::trade_parsing;
3use futu_trd::types::{ModifyOrderOp, OrderType, TrdSide};
4
5use crate::trd_sdk_adapter;
6
7pub(crate) fn parse_trd_side(s: &str) -> Result<TrdSide> {
8 let side = trade_parsing::parse_trd_side_id(s).ok_or_else(|| {
9 anyhow::anyhow!(
10 "unknown trd side {:?} ({})",
11 s.trim().to_ascii_uppercase(),
12 trade_parsing::TRD_SIDE_PARSE_CHOICES
13 )
14 })?;
15 trd_sdk_adapter::trd_side_from_id(side)
16 .ok_or_else(|| anyhow::anyhow!("unknown trd side id {side} after core parser accepted it"))
17}
18
19pub(crate) fn parse_order_type(s: &str) -> Result<OrderType> {
20 let order_type = trade_parsing::parse_order_type_id(s).ok_or_else(|| {
21 anyhow::anyhow!(
22 "unknown order type {:?} ({})",
23 s.trim().to_ascii_uppercase(),
24 trade_parsing::ORDER_TYPE_PARSE_CHOICES
25 )
26 })?;
27 trd_sdk_adapter::order_type_from_id(order_type).ok_or_else(|| {
28 anyhow::anyhow!("unknown order type id {order_type} after core parser accepted it")
29 })
30}
31
32pub(crate) fn parse_modify_op(s: &str) -> Result<ModifyOrderOp> {
33 let modify_op = trade_parsing::parse_modify_op_id(s).ok_or_else(|| {
34 anyhow::anyhow!(
35 "unknown modify op {:?} ({})",
36 s.trim().to_ascii_uppercase(),
37 trade_parsing::MODIFY_OP_PARSE_CHOICES
38 )
39 })?;
40 trd_sdk_adapter::modify_order_op_from_id(modify_op).ok_or_else(|| {
41 anyhow::anyhow!("unknown modify op id {modify_op} after core parser accepted it")
42 })
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub(crate) struct ResolvedOrderIdArg {
47 pub(crate) order_id: u64,
48 pub(crate) order_id_ex: Option<String>,
49 pub(crate) idempotency_component: String,
50}
51
52pub(crate) fn resolve_order_id_arg(raw: &str) -> Result<ResolvedOrderIdArg> {
53 let trimmed = raw.trim();
54 if trimmed.is_empty() {
55 bail!("--order-id must not be empty");
56 }
57
58 if trimmed.bytes().all(|b| b.is_ascii_digit()) {
62 let order_id = trimmed
63 .parse::<u64>()
64 .with_context(|| format!("invalid numeric --order-id {trimmed:?}"))?;
65 return Ok(ResolvedOrderIdArg {
66 order_id,
67 order_id_ex: None,
68 idempotency_component: trimmed.to_string(),
69 });
70 }
71
72 Ok(ResolvedOrderIdArg {
73 order_id: 0,
74 order_id_ex: Some(trimmed.to_string()),
75 idempotency_component: trimmed.to_string(),
76 })
77}
78
79pub(crate) fn parse_numeric_order_id_arg(raw: &str, field: &str) -> Result<u64> {
80 let trimmed = raw.trim();
81 if trimmed.is_empty() {
82 bail!("{field} must not be empty");
83 }
84 if !trimmed.bytes().all(|b| b.is_ascii_digit()) {
85 bail!(
86 "{field} for reconfirm-order must be numeric FTAPI order_id; \
87 orderIDEx is not supported by Trd_ReconfirmOrder"
88 );
89 }
90 trimmed
91 .parse::<u64>()
92 .with_context(|| format!("invalid {field} {trimmed:?}"))
93}