1use std::fmt;
2
3use prost::Message;
4
5use crate::proto_internal::{cloud_sync_service, user_cloud_config};
6
7pub const REQUESTED_KLINE_CONFIG_ID: u32 = 60_000;
11pub const REQUESTED_KLINE_ITEM_ID: u64 = 1;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum UserCloudWireError {
15 DecodeEnvelope {
16 context: &'static str,
17 message: String,
18 },
19 InvalidUpload {
20 field: &'static str,
21 },
22}
23
24impl fmt::Display for UserCloudWireError {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 Self::DecodeEnvelope { context, message } => {
28 write!(f, "{context} protobuf decode failed: {message}")
29 }
30 Self::InvalidUpload { field } => {
31 write!(f, "UserCloud upload has invalid {field}")
32 }
33 }
34 }
35}
36
37impl std::error::Error for UserCloudWireError {}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub struct UserCloudKeyFacts {
41 pub cfg_id: Option<u32>,
42 pub item_id: Option<u64>,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct RequestedKlineWireItem {
47 pub stock_id: u64,
48 pub request_time: i64,
49 pub option_expiry_time_sec: Option<i64>,
50}
51
52pub type RequestedKlineUploadItem = RequestedKlineWireItem;
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum UserCloudDataFacts {
56 Missing,
57 Opaque { len: usize },
58 RequestedKline { items: Vec<RequestedKlineWireItem> },
59 MalformedRequestedKline { message: String },
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct UserCloudConfigItemFacts {
64 pub user_id: Option<u64>,
65 pub key_id: Option<UserCloudKeyFacts>,
66 pub updated_time: Option<u64>,
67 pub data: UserCloudDataFacts,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct UserCloudDownloadFacts {
72 pub ret_code: Option<i32>,
73 pub ret_msg: Option<String>,
74 pub items: Vec<UserCloudConfigItemFacts>,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct UserCloudUploadInput {
79 pub user_id: u64,
80 pub updated_time: u64,
81 pub sequence: String,
82 pub items: Vec<RequestedKlineUploadItem>,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct UserCloudUploadReplyFacts {
87 pub ret_code: Option<i32>,
88 pub ret_msg: Option<String>,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct UserCloudPushFacts {
93 pub items: Vec<UserCloudConfigItemFacts>,
94 pub sequence: Option<String>,
95}
96
97#[must_use]
98pub fn encode_requested_kline_download() -> Vec<u8> {
99 cloud_sync_service::ConfigDownloadReq {
100 key_ids: vec![cloud_sync_service::KeyId {
101 cfg_id: Some(REQUESTED_KLINE_CONFIG_ID),
102 item_id: Some(REQUESTED_KLINE_ITEM_ID),
103 }],
104 }
105 .encode_to_vec()
106}
107
108pub fn decode_requested_kline_download(
109 body: &[u8],
110) -> Result<UserCloudDownloadFacts, UserCloudWireError> {
111 let wire = cloud_sync_service::ConfigDownloadRsp::decode(body)
112 .map_err(|error| decode_error("ConfigDownloadRsp", error))?;
113 Ok(UserCloudDownloadFacts {
114 ret_code: wire.ret_code,
115 ret_msg: wire.ret_msg,
116 items: decode_config_items(wire.cfg_items),
117 })
118}
119
120pub fn encode_requested_kline_upload(
121 input: &UserCloudUploadInput,
122) -> Result<Vec<u8>, UserCloudWireError> {
123 validate_upload(input)?;
124 let nested = user_cloud_config::RequestedKline {
125 requested_items: input
126 .items
127 .iter()
128 .map(|item| user_cloud_config::RequestedKlineItem {
129 sec_id: item.stock_id,
130 time: item.request_time,
131 option_expiry_timestamp_sec: item.option_expiry_time_sec,
132 })
133 .collect(),
134 }
135 .encode_to_vec();
136 Ok(cloud_sync_service::ConfigUploadReq {
137 cfg_items: vec![cloud_sync_service::ConfigItem {
138 user_id: Some(input.user_id),
139 key_id: Some(cloud_sync_service::KeyId {
140 cfg_id: Some(REQUESTED_KLINE_CONFIG_ID),
141 item_id: Some(REQUESTED_KLINE_ITEM_ID),
142 }),
143 updated_time: Some(input.updated_time),
144 data: Some(nested),
145 }],
146 sequence: Some(input.sequence.clone()),
147 }
148 .encode_to_vec())
149}
150
151pub fn decode_requested_kline_upload(
152 body: &[u8],
153) -> Result<UserCloudUploadReplyFacts, UserCloudWireError> {
154 let wire = cloud_sync_service::ConfigUploadRsp::decode(body)
155 .map_err(|error| decode_error("ConfigUploadRsp", error))?;
156 Ok(UserCloudUploadReplyFacts {
157 ret_code: wire.ret_code,
158 ret_msg: wire.ret_msg,
159 })
160}
161
162pub fn decode_user_cloud_update_push(
163 body: &[u8],
164) -> Result<UserCloudPushFacts, UserCloudWireError> {
165 let wire = cloud_sync_service::ConfigUpdatePush::decode(body)
166 .map_err(|error| decode_error("ConfigUpdatePush", error))?;
167 Ok(UserCloudPushFacts {
168 items: decode_config_items(wire.cfg_items),
169 sequence: wire.sequence,
170 })
171}
172
173fn decode_config_items(
174 items: Vec<cloud_sync_service::ConfigItem>,
175) -> Vec<UserCloudConfigItemFacts> {
176 items
177 .into_iter()
178 .map(|item| {
179 let key_id = item.key_id.map(|key| UserCloudKeyFacts {
180 cfg_id: key.cfg_id,
181 item_id: key.item_id,
182 });
183 let requested_kline =
184 key_id.as_ref().and_then(|key| key.cfg_id) == Some(REQUESTED_KLINE_CONFIG_ID);
185 let data = match item.data {
186 None => UserCloudDataFacts::Missing,
187 Some(data) if requested_kline => decode_nested_requested_kline(&data),
188 Some(data) => UserCloudDataFacts::Opaque { len: data.len() },
189 };
190 UserCloudConfigItemFacts {
191 user_id: item.user_id,
192 key_id,
193 updated_time: item.updated_time,
194 data,
195 }
196 })
197 .collect()
198}
199
200fn decode_nested_requested_kline(data: &[u8]) -> UserCloudDataFacts {
201 match user_cloud_config::RequestedKline::decode(data) {
202 Ok(wire) => UserCloudDataFacts::RequestedKline {
203 items: wire
204 .requested_items
205 .into_iter()
206 .map(|item| RequestedKlineWireItem {
207 stock_id: item.sec_id,
208 request_time: item.time,
209 option_expiry_time_sec: item.option_expiry_timestamp_sec,
210 })
211 .collect(),
212 },
213 Err(error) => UserCloudDataFacts::MalformedRequestedKline {
214 message: error.to_string(),
215 },
216 }
217}
218
219fn validate_upload(input: &UserCloudUploadInput) -> Result<(), UserCloudWireError> {
220 if input.user_id == 0 {
221 return Err(UserCloudWireError::InvalidUpload { field: "user_id" });
222 }
223 if input.sequence.trim().is_empty() {
224 return Err(UserCloudWireError::InvalidUpload { field: "sequence" });
225 }
226 if input.items.iter().any(|item| {
227 item.stock_id == 0 || matches!(item.option_expiry_time_sec, Some(expiry) if expiry == 0)
228 }) {
229 return Err(UserCloudWireError::InvalidUpload {
230 field: "requested_kline_item",
231 });
232 }
233 Ok(())
234}
235
236fn decode_error(context: &'static str, error: prost::DecodeError) -> UserCloudWireError {
237 UserCloudWireError::DecodeEnvelope {
238 context,
239 message: error.to_string(),
240 }
241}