futu_rest/routes/trd/
account.rs1use std::sync::Arc;
4
5use axum::extract::{Extension, Json, State};
6use serde_json::Value;
7
8use futu_auth::KeyRecord;
9use futu_core::{account_locator, proto_id};
10use futu_proto::trd_get_acc_list;
11
12use crate::adapter::{self, RestState};
13
14use super::ApiResult;
15
16pub async fn get_acc_list(
36 State(state): State<RestState>,
37 rec: Option<Extension<Arc<KeyRecord>>>,
38) -> ApiResult {
39 let ctx =
40 crate::caller_context::CallerContext::from_key_record(rec.as_deref().map(|r| r.as_ref()));
41 let discovery_body = serde_json::json!({
42 "c2s": {
43 "need_general_sec_account": true
49 }
50 });
51
52 let Json(mut rsp) =
53 adapter::proto_request_with_ctx::<trd_get_acc_list::Request, trd_get_acc_list::Response>(
54 &state,
55 proto_id::TRD_GET_ACC_LIST,
56 Some(discovery_body),
57 None,
58 Some(&ctx),
59 )
60 .await?;
61 apply_app_visible_account_projection_json(&mut rsp);
62 Ok(Json(rsp))
63}
64
65fn is_app_visible_account_json(acc: &Value) -> bool {
66 let acc_label = acc.get("acc_label").and_then(Value::as_str);
67 let markets = acc
68 .get("trd_market_auth_list")
69 .and_then(Value::as_array)
70 .map(|items| {
71 items
72 .iter()
73 .filter_map(Value::as_i64)
74 .filter_map(|m| i32::try_from(m).ok())
75 .collect::<Vec<_>>()
76 })
77 .unwrap_or_default();
78 account_locator::is_app_visible_account_parts(&markets, acc_label)
79}
80
81fn apply_app_visible_account_projection_json(rsp: &mut Value) {
82 if let Some(list) = rsp
83 .get_mut("s2c")
84 .and_then(|s2c| s2c.get_mut("acc_list"))
85 .and_then(Value::as_array_mut)
86 {
87 list.retain(is_app_visible_account_json);
88 for acc in list {
89 apply_visible_card_num_json(acc);
90 }
91 }
92}
93
94fn non_empty_str_field(acc: &Value, field: &str) -> Option<String> {
95 acc.get(field)
96 .and_then(Value::as_str)
97 .map(str::trim)
98 .filter(|s| !s.is_empty())
99 .map(ToOwned::to_owned)
100}
101
102fn apply_visible_card_num_json(acc: &mut Value) {
103 let raw_card_num = non_empty_str_field(acc, "card_num");
104 let visible_card_num =
105 non_empty_str_field(acc, "uni_card_num").or_else(|| raw_card_num.clone());
106 let Some(visible_card_num) = visible_card_num else {
107 return;
108 };
109 let Some(obj) = acc.as_object_mut() else {
110 return;
111 };
112
113 if let Some(raw) = raw_card_num
114 && raw != visible_card_num
115 {
116 obj.entry("raw_card_num".to_string())
117 .or_insert_with(|| Value::String(raw));
118 }
119 obj.insert("card_num".to_string(), Value::String(visible_card_num));
120}
121
122#[cfg(test)]
123mod tests;