1use crate::common::interface::StoreTrait;
2use crate::common::model::Response;
3use crate::common::model::meta::MetaData;
4#[cfg(feature = "polars")]
5use polars::io::SerWriter;
6#[cfg(feature = "polars")]
7use polars::io::ipc::IpcWriter;
8#[cfg(feature = "polars")]
9use polars::prelude::*;
10use std::fmt::Debug;
11use uuid::Uuid;
12#[derive(Clone, Debug, Default)]
14pub struct StoreContext {
15 pub request_id: Uuid,
17 pub platform: String,
19 pub account: String,
21 pub module: String,
23 pub meta: MetaData,
25 pub data_middleware: Vec<String>,
27}
28impl StoreContext {
29 pub fn task_id(&self) -> String {
31 format!("{}-{}", self.account, self.platform)
32 }
33 pub fn module_id(&self) -> String {
35 format!("{}-{}-{}", self.account, self.platform, self.module)
36 }
37}
38
39#[derive(Clone, Debug, Default)]
41pub struct FileStore {
42 pub ctx: StoreContext,
44 pub file_name: String,
46 pub file_path: String,
48 pub content: Vec<u8>,
50}
51
52impl StoreTrait for FileStore {
53 fn build(&self) -> DataEvent {
54 DataEvent {
56 request_id: self.ctx.request_id,
57 platform: self.ctx.platform.clone(),
58 account: self.ctx.account.clone(),
59 module: self.ctx.module.clone(),
60 meta: self.ctx.meta.clone(),
61 data: DataType::File(self.clone()),
62 data_middleware: self.ctx.data_middleware.clone(),
63 }
64 }
65}
66
67impl From<FileStore> for DataEvent {
68 fn from(value: FileStore) -> Self {
69 let ctx_clone = value.ctx.clone();
70 DataEvent {
71 request_id: ctx_clone.request_id,
72 platform: ctx_clone.platform.clone(),
73 account: ctx_clone.account.clone(),
74 module: ctx_clone.module.clone(),
75 meta: ctx_clone.meta.clone(),
76 data: DataType::File(
77 FileStore::default()
78 .with_ctx(ctx_clone.clone())
79 .with_content(value.content)
80 .with_name(value.file_name)
81 .with_path(value.file_path),
82 ),
83 data_middleware: ctx_clone.data_middleware.clone(),
84 }
85 }
86}
87impl From<DataEvent> for FileStore {
88 fn from(value: DataEvent) -> Self {
89 match value.data {
90 DataType::File(f) => f,
91 _ => FileStore::default(),
92 }
93 }
94}
95
96impl FileStore {
97 pub fn with_content(mut self, content: Vec<u8>) -> Self {
99 self.content = content;
100 self
101 }
102 pub fn with_ctx(mut self, ctx: StoreContext) -> Self {
104 self.ctx = ctx;
105 self
106 }
107 pub fn with_name(mut self, file_name: impl AsRef<str>) -> Self {
109 self.file_name = file_name.as_ref().to_string();
110 self
111 }
112 pub fn with_path(mut self, file_path: impl AsRef<str>) -> Self {
114 self.file_path = file_path.as_ref().to_string();
115 self
116 }
117 pub fn with_file_name(self, file_name: impl AsRef<str>) -> Self {
119 self.with_name(file_name)
120 }
121 pub fn with_file_path(self, file_path: impl AsRef<str>) -> Self {
123 self.with_path(file_path)
124 }
125}
126
127#[cfg(feature = "polars")]
128#[derive(Clone, Debug)]
129pub enum DataframeStoreData {
130 Bytes(Vec<u8>),
131 DataFrame(DataFrame),
132}
133
134#[cfg(feature = "polars")]
136#[derive(Clone, Debug)]
137pub struct DataFrameStore {
138 ctx: StoreContext,
140 pub data: DataframeStoreData,
142 pub schema: String,
144 pub table: String,
146}
147#[cfg(feature = "polars")]
148impl Default for DataFrameStore {
149 fn default() -> Self {
150 Self {
151 ctx: StoreContext::default(),
152 data: DataframeStoreData::Bytes(vec![]),
153 schema: String::new(),
154 table: String::new(),
155 }
156 }
157}
158
159#[cfg(feature = "polars")]
160impl StoreTrait for DataFrameStore {
161 fn build(&self) -> DataEvent {
162 DataEvent {
164 request_id: self.ctx.request_id,
165 platform: self.ctx.platform.clone(),
166 account: self.ctx.account.clone(),
167 module: self.ctx.module.clone(),
168 meta: self.ctx.meta.clone(),
169 data: DataType::DataFrame(self.clone()),
170 data_middleware: self.ctx.data_middleware.clone(),
171 }
172 }
173}
174#[cfg(feature = "polars")]
175impl From<DataFrameStore> for DataEvent {
176 fn from(value: DataFrameStore) -> Self {
177 DataEvent {
178 request_id: value.ctx.request_id,
179 platform: value.ctx.platform.clone(),
180 account: value.ctx.account.clone(),
181 module: value.ctx.module.clone(),
182 meta: value.ctx.meta.clone(),
183 data_middleware: value.ctx.data_middleware.clone(),
184 data: DataType::DataFrame(value),
185 }
186 }
187}
188
189#[cfg(feature = "polars")]
190impl DataFrameStore {
191 pub fn with_data(mut self, data: DataFrame) -> Self {
193 let mut buffer = Vec::new();
194 let mut df = data;
195 let mut writer = IpcWriter::new(&mut buffer); writer.finish(&mut df).expect("serialize DataFrame to IPC");
197 self.data = DataframeStoreData::Bytes(buffer);
198 self
199 }
200 pub fn with_schema(mut self, schema: impl AsRef<str>) -> Self {
202 self.schema = schema.as_ref().to_string();
203 self
204 }
205 pub fn with_table(mut self, table: impl AsRef<str>) -> Self {
207 self.table = table.as_ref().to_string();
208 self
209 }
210 pub fn get_data(&self) -> Option<DataFrame> {
211 match &self.data {
212 DataframeStoreData::Bytes(bytes) => {
213 let cursor = std::io::Cursor::new(bytes);
214 let reader = polars::io::ipc::IpcReader::new(cursor);
215 reader.finish().ok()
216 }
217 DataframeStoreData::DataFrame(df) => Some(df.clone()),
218 }
219 }
220}
221
222#[allow(clippy::large_enum_variant)]
228#[derive(Debug, Clone, Default)]
229pub enum DataType {
230 #[default]
233 Empty,
234 #[cfg(feature = "polars")]
236 DataFrame(DataFrameStore),
237 File(FileStore),
239}
240
241#[derive(Debug, Clone)]
243pub struct DataEvent {
244 pub request_id: Uuid,
246 pub platform: String,
248 pub account: String,
250 pub module: String,
252 pub meta: MetaData,
254 pub data: DataType,
256 pub data_middleware: Vec<String>,
258}
259
260impl Default for DataEvent {
261 fn default() -> Self {
262 Self {
263 request_id: Default::default(),
264 platform: "".to_string(),
265 account: "".to_string(),
266 module: "".to_string(),
267 meta: Default::default(),
268 data: DataType::Empty,
269 data_middleware: vec![],
270 }
271 }
272}
273impl DataEvent {
274 pub fn from(response: &Response) -> Self {
276 DataEvent {
277 request_id: response.id,
278 platform: response.platform.clone(),
279 account: response.account.clone(),
280 module: response.module.clone(),
281 meta: response.metadata.clone(),
282 data: DataType::Empty,
283 data_middleware: response.data_middleware.clone(),
284 }
285 }
286 pub fn with_middlewares(mut self, middleware: Vec<String>) -> Self {
288 self.data_middleware = middleware;
289 self
290 }
291 pub fn with_middleware(mut self, middleware: impl AsRef<str>) -> Self {
293 self.data_middleware.push(middleware.as_ref().into());
294 self
295 }
296 pub fn task_id(&self) -> String {
298 format!("{}-{}", self.account, self.platform)
299 }
300 pub fn module_id(&self) -> String {
302 format!("{}-{}-{}", self.account, self.platform, self.module)
303 }
304 #[cfg(feature = "polars")]
306 pub fn with_df(self, data: DataFrame) -> DataFrameStore {
307 DataFrameStore::default().with_data(data)
308 }
309 pub fn with_file(self, data: Vec<u8>) -> FileStore {
311 FileStore {
313 ctx: StoreContext {
314 request_id: self.request_id,
315 platform: self.platform,
316 account: self.account,
317 module: self.module,
318 meta: self.meta,
319 data_middleware: self.data_middleware,
320 },
321 file_name: String::new(),
322 file_path: String::new(),
323 content: data,
324 }
325 }
326 pub fn size(&self) -> usize {
328 match &self.data {
329 DataType::Empty => 0,
330 #[cfg(feature = "polars")]
331 DataType::DataFrame(df_store) => match &df_store.data {
332 DataframeStoreData::Bytes(bytes) => bytes.len(),
333 DataframeStoreData::DataFrame(df) => df.height() * df.width(), },
335 DataType::File(file_store) => file_store.content.len(),
336 }
337 }
338}
339#[cfg(feature = "polars")]
340impl From<(DataFrame, &Response)> for DataEvent {
341 fn from(value: (DataFrame, &Response)) -> Self {
342 let (data, response) = value;
343 DataEvent {
344 request_id: response.id,
345 platform: response.platform.clone(),
346 account: response.account.clone(),
347 module: response.module.clone(),
348 meta: response.metadata.clone(),
349 data: DataType::DataFrame(DataFrameStore::default().with_data(data)),
350 data_middleware: response.data_middleware.clone(),
351 }
352 }
353}
354impl StoreTrait for DataEvent {
355 fn build(&self) -> DataEvent {
356 self.clone()
357 }
358}
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use serde::{Deserialize, Serialize};
363
364 #[test]
365 fn test_file_store_builder() {
366 let store = FileStore::default()
367 .with_name("test.txt")
368 .with_path("/tmp/test.txt")
369 .with_content(vec![1, 2, 3]);
370
371 assert_eq!(store.file_name, "test.txt");
372 assert_eq!(store.file_path, "/tmp/test.txt");
373 assert_eq!(store.content, vec![1, 2, 3]);
374 }
375
376 #[test]
377 fn test_serde_examples() {
378 #[derive(Serialize, Deserialize, Debug)]
379 struct Item {
380 key: String,
381 value: serde_json::Value,
382 }
383
384 #[derive(Serialize, Deserialize, Debug)]
386 struct RespPartial {
387 data: Vec<Item>,
388 }
390
391 #[derive(Serialize, Deserialize, Debug)]
393 struct RespWithOptional {
394 data: Vec<Item>,
395 is_success: Option<bool>, #[serde(default)] extra_field: String,
398 }
399
400 let complex_json = r#"
402 {
403 "data": [
404 {"key": "name", "value": "Alice"},
405 {"key": "age", "value": "30"},
406 {"key": "city", "value": "New York"}
407 ],
408 "is_success": true,
409 "timestamp": "2023-01-01T00:00:00Z",
410 "metadata": {
411 "total_count": 100,
412 "page": 1,
413 "limit": 10
414 },
415 "debug_info": "some debug data",
416 "version": "1.0.0"
417 }"#;
418
419 let resp_partial: RespPartial = serde_json::from_str(complex_json).unwrap();
421 assert_eq!(resp_partial.data.len(), 3);
422
423 let resp_optional: RespWithOptional = serde_json::from_str(complex_json).unwrap();
425 assert_eq!(resp_optional.is_success, Some(true));
426 assert_eq!(resp_optional.extra_field, "");
427
428 let value: serde_json::Value = serde_json::from_str(complex_json).unwrap();
430 if let Some(data) = value.get("data") {
431 let items: Vec<Item> = serde_json::from_value(data.clone()).unwrap();
432 assert_eq!(items.len(), 3);
433 }
434
435 let is_success = value
437 .get("is_success")
438 .and_then(|v| v.as_bool())
439 .unwrap_or(false);
440 let version = value
441 .get("version")
442 .and_then(|v| v.as_str())
443 .unwrap_or("unknown");
444
445 assert!(is_success);
446 assert_eq!(version, "1.0.0");
447 }
448
449 #[cfg(feature = "polars")]
450 #[test]
451 fn test_polars_dataframe() {
452 use chrono::NaiveDate;
453 let df: DataFrame = df!(
454 "name" => ["Alice Archer", "Ben Brown", "Chloe Cooper", "Daniel Donovan"],
455 "birthdate" => [
456 NaiveDate::from_ymd_opt(1997, 1, 10).unwrap(),
457 NaiveDate::from_ymd_opt(1985, 2, 15).unwrap(),
458 NaiveDate::from_ymd_opt(1983, 3, 22).unwrap(),
459 NaiveDate::from_ymd_opt(1981, 4, 30).unwrap(),
460 ],
461 "weight" => [57.9, 72.5, 53.6, 83.1], "height" => [1.56, 1.77, 1.65, 1.75], )
464 .unwrap();
465
466 assert_eq!(df.height(), 4);
467 assert_eq!(df.width(), 4);
468 }
469}