Skip to main content

wp_model_core/model/data/
record.rs

1use crate::model::Maker;
2use crate::model::format::LevelFormatAble;
3use crate::model::{DataType, FNameStr, FValueStr, Value};
4use crate::traits::AsValueRef;
5use serde_derive::{Deserialize, Serialize};
6use std::convert::TryFrom;
7use std::fmt::{Display, Formatter};
8use std::net::{IpAddr, Ipv4Addr};
9use std::sync::Arc;
10
11use super::field::Field;
12use super::storage::FieldStorage;
13pub const WP_EVENT_ID: &str = "wp_event_id";
14/// 记录中每一项需要暴露的行为
15pub trait RecordItem {
16    fn get_name(&self) -> &str;
17    fn get_meta(&self) -> &DataType;
18    fn get_value(&self) -> &Value;
19    fn get_value_mut(&mut self) -> &mut Value;
20}
21
22/// 为 Record 生成字段所需的工厂方法
23pub trait RecordItemFactory {
24    fn from_digit<S: Into<FNameStr>>(name: S, val: i64) -> Self;
25    fn from_ip<S: Into<FNameStr>>(name: S, ip: IpAddr) -> Self;
26    fn from_chars<N: Into<FNameStr>, Val: Into<FValueStr>>(name: N, val: Val) -> Self;
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
30pub struct Record<T> {
31    pub id: u64,
32    pub items: Vec<T>,
33}
34
35impl<T> Default for Record<T> {
36    fn default() -> Self {
37        Self {
38            id: 0,
39            items: Vec::with_capacity(10),
40        }
41    }
42}
43
44impl<T> From<Vec<T>> for Record<T> {
45    fn from(value: Vec<T>) -> Self {
46        Self {
47            id: 0,
48            items: value,
49        }
50    }
51}
52
53impl<T> Display for Record<T>
54where
55    T: RecordItem + LevelFormatAble,
56{
57    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
58        writeln!(f)?;
59        for (i, o) in self.items.iter().enumerate() {
60            if *o.get_meta() != DataType::Ignore {
61                write!(f, "NO:{:<5}", i + 1)?;
62                o.level_fmt(f, 1)?;
63            }
64        }
65        Ok(())
66    }
67}
68
69impl<T> Record<T>
70where
71    T: RecordItem + RecordItemFactory,
72{
73    pub fn set_id(&mut self, id: u64) {
74        // 设置 id 字段
75        self.id = id;
76
77        // 如果已存在 wp_event_id 字段,避免重复追加
78        if self.items.iter().any(|f| f.get_name() == WP_EVENT_ID) {
79            return;
80        }
81        let Ok(id_i64) = i64::try_from(id) else {
82            // 事件 ID 超出 i64 无法表示,保持记录原状
83            return;
84        };
85        self.items.insert(0, T::from_digit(WP_EVENT_ID, id_i64));
86    }
87    pub fn test_value() -> Self {
88        let data = vec![
89            T::from_ip("ip", IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
90            T::from_chars("chars", "test"),
91        ];
92        Self { id: 0, items: data }
93    }
94}
95
96impl<T> Record<T>
97where
98    T: RecordItem,
99{
100    pub fn get_value(&self, key: &str) -> Option<&Value> {
101        self.items
102            .iter()
103            .find(|x| x.get_name() == key)
104            .map(|x| x.get_value())
105    }
106}
107
108impl<T> Record<T> {
109    pub fn append(&mut self, data: T) {
110        self.items.push(data);
111    }
112    pub fn merge(&mut self, mut other: Self) {
113        self.items.append(&mut other.items);
114    }
115}
116
117impl<T> Record<T>
118where
119    T: RecordItem,
120{
121    // 存在同名字段时取第一个字段返回值
122    pub fn field(&self, key: &str) -> Option<&T> {
123        self.items.iter().find(|item| item.get_name() == key)
124    }
125
126    pub fn get2(&self, name: &str) -> Option<&T> {
127        self.items.iter().find(|x| x.get_name() == name)
128    }
129    pub fn get_value_mut(&mut self, name: &str) -> Option<&mut T> {
130        self.items.iter_mut().find(|x| x.get_name() == name)
131    }
132    pub fn remove_field(&mut self, name: &str) -> bool {
133        let pos = self.items.iter().position(|x| x.get_name() == name);
134        if let Some(pos) = pos {
135            self.items.remove(pos);
136            true
137        } else {
138            false
139        }
140    }
141}
142
143impl<V> RecordItem for Field<V>
144where
145    V: AsValueRef<Value>,
146{
147    fn get_name(&self) -> &str {
148        Field::get_name(self)
149    }
150
151    fn get_meta(&self) -> &DataType {
152        Field::get_meta(self)
153    }
154
155    fn get_value(&self) -> &Value {
156        Field::get_value(self)
157    }
158
159    fn get_value_mut(&mut self) -> &mut Value {
160        Field::get_value_mut(self)
161    }
162}
163
164impl<V> RecordItemFactory for Field<V>
165where
166    V: Maker<i64> + Maker<FValueStr> + Maker<IpAddr>,
167{
168    fn from_digit<S: Into<FNameStr>>(name: S, val: i64) -> Self {
169        Field::from_digit(name, val)
170    }
171
172    fn from_ip<S: Into<FNameStr>>(name: S, ip: IpAddr) -> Self {
173        Field::from_ip(name, ip)
174    }
175
176    fn from_chars<N: Into<FNameStr>, Val: Into<FValueStr>>(name: N, val: Val) -> Self {
177        Field::from_chars(name, val)
178    }
179}
180
181// Convenience methods for Record<FieldStorage> (DataRecord with mixed storage)
182impl Record<FieldStorage> {
183    /// Push a shared field (Arc-wrapped) to the record
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// use std::sync::Arc;
189    /// use wp_model_core::model::{DataRecord, Field, Value, DataType, FieldStorage};
190    ///
191    /// let mut record = DataRecord::default();
192    /// let field = Arc::new(Field::new(DataType::Chars, "app_name", Value::from("myapp")));
193    /// record.push_shared(field);
194    /// ```
195    pub fn push_shared(&mut self, field: Arc<Field<Value>>) {
196        self.items.push(FieldStorage::Shared(field));
197    }
198
199    /// Push an owned field to the record
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// use wp_model_core::model::{DataRecord, Field, Value, DataType};
205    ///
206    /// let mut record = DataRecord::default();
207    /// let field = Field::new(DataType::Digit, "count", Value::from(42));
208    /// record.push_owned(field);
209    /// ```
210    pub fn push_owned(&mut self, field: Field<Value>) {
211        self.items.push(FieldStorage::Owned(field));
212    }
213
214    /// Get a reference to the underlying field by index
215    ///
216    /// # Examples
217    ///
218    /// ```
219    /// use wp_model_core::model::{DataRecord, Field, Value, DataType};
220    ///
221    /// let mut record = DataRecord::default();
222    /// let field = Field::new(DataType::Digit, "x", Value::from(10));
223    /// record.push_owned(field);
224    ///
225    /// let retrieved = record.get_field(0);
226    /// assert!(retrieved.is_some());
227    /// assert_eq!(retrieved.unwrap().get_name(), "x");
228    /// ```
229    pub fn get_field(&self, index: usize) -> Option<&Field<Value>> {
230        self.items.get(index).map(|s| s.as_field())
231    }
232
233    /// Get statistics about storage types (shared vs owned)
234    ///
235    /// Returns (shared_count, owned_count)
236    ///
237    /// # Examples
238    ///
239    /// ```
240    /// use std::sync::Arc;
241    /// use wp_model_core::model::{DataRecord, Field, Value, DataType};
242    ///
243    /// let mut record = DataRecord::default();
244    /// let static_field = Arc::new(Field::new(DataType::Chars, "static", Value::from("val")));
245    /// record.push_shared(static_field);
246    ///
247    /// let dynamic_field = Field::new(DataType::Digit, "dynamic", Value::from(10));
248    /// record.push_owned(dynamic_field);
249    ///
250    /// let (shared, owned) = record.storage_stats();
251    /// assert_eq!(shared, 1);
252    /// assert_eq!(owned, 1);
253    /// ```
254    pub fn storage_stats(&self) -> (usize, usize) {
255        let mut shared_count = 0;
256        let mut owned_count = 0;
257        for item in &self.items {
258            match item {
259                FieldStorage::Shared(_) => shared_count += 1,
260                FieldStorage::Owned(_) => owned_count += 1,
261            }
262        }
263        (shared_count, owned_count)
264    }
265
266    /// Convert to a fully owned record (Record<Field<Value>>)
267    ///
268    /// This is useful when you need to pass the record to code that expects
269    /// the old Record<Field<Value>> type.
270    ///
271    /// # Examples
272    ///
273    /// ```
274    /// use wp_model_core::model::{DataRecord, Field, Value, DataType};
275    ///
276    /// let mut record = DataRecord::default();
277    /// let field = Field::new(DataType::Digit, "x", Value::from(10));
278    /// record.push_owned(field);
279    ///
280    /// let owned_record = record.into_owned_record();
281    /// assert_eq!(owned_record.items.len(), 1);
282    /// ```
283    pub fn into_owned_record(self) -> Record<Field<Value>> {
284        Record {
285            id: self.id,
286            items: self
287                .items
288                .into_iter()
289                .map(|storage| storage.into_owned())
290                .collect(),
291        }
292    }
293}
294
295// ValueGetter impl removed from core; use function-style adapters in extension crates.
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use crate::model::{DataField, DataRecord, FieldStorage};
301    use std::net::Ipv4Addr;
302
303    fn make_test_record() -> DataRecord {
304        let fields = vec![
305            FieldStorage::from_chars("name", "Alice"),
306            FieldStorage::from_digit("age", 30),
307            FieldStorage::from_ip("ip", IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))),
308        ];
309        Record::from(fields)
310    }
311
312    // ========== Record creation tests ==========
313
314    #[test]
315    fn test_record_default() {
316        let record: DataRecord = Record::default();
317        assert!(record.items.is_empty());
318    }
319
320    #[test]
321    fn test_record_from_vec() {
322        let fields: Vec<FieldStorage> = vec![
323            FieldStorage::from_digit("x", 1),
324            FieldStorage::from_digit("y", 2),
325        ];
326        let record: DataRecord = Record::from(fields);
327        assert_eq!(record.items.len(), 2);
328    }
329
330    #[test]
331    fn test_record_test_value() {
332        let record: DataRecord = Record::test_value();
333        assert_eq!(record.items.len(), 2);
334        assert!(record.field("ip").is_some());
335        assert!(record.field("chars").is_some());
336    }
337
338    // ========== Record field access tests ==========
339
340    #[test]
341    fn test_record_field() {
342        let record = make_test_record();
343
344        let name_field = record.field("name");
345        assert!(name_field.is_some());
346        assert_eq!(name_field.unwrap().get_name(), "name");
347
348        let missing = record.field("missing");
349        assert!(missing.is_none());
350    }
351
352    #[test]
353    fn test_record_get2() {
354        let record = make_test_record();
355
356        let age_field = record.get2("age");
357        assert!(age_field.is_some());
358        assert_eq!(age_field.unwrap().get_meta(), &DataType::Digit);
359    }
360
361    #[test]
362    fn test_record_get_value() {
363        let record = make_test_record();
364
365        let age_value = record.get_value("age");
366        assert!(age_value.is_some());
367        assert_eq!(age_value.unwrap(), &Value::Digit(30));
368
369        let missing = record.get_value("missing");
370        assert!(missing.is_none());
371    }
372
373    #[test]
374    fn test_record_get_value_mut() {
375        let mut record = make_test_record();
376
377        let field = record.get_value_mut("age");
378        assert!(field.is_some());
379
380        // Modify the value through mutable reference
381        if let Some(f) = field {
382            *f.get_value_mut() = Value::Digit(31);
383        }
384
385        assert_eq!(record.get_value("age"), Some(&Value::Digit(31)));
386    }
387
388    // ========== Record mutation tests ==========
389
390    #[test]
391    fn test_record_append() {
392        let mut record: DataRecord = Record::default();
393        assert_eq!(record.items.len(), 0);
394
395        record.append(FieldStorage::from_digit("count", 100));
396        assert_eq!(record.items.len(), 1);
397
398        record.append(FieldStorage::from_chars("msg", "hello"));
399        assert_eq!(record.items.len(), 2);
400    }
401
402    #[test]
403    fn test_record_merge() {
404        let mut record1: DataRecord = Record::from(vec![FieldStorage::from_digit("a", 1)]);
405        let record2: DataRecord = Record::from(vec![
406            FieldStorage::from_digit("b", 2),
407            FieldStorage::from_digit("c", 3),
408        ]);
409
410        record1.merge(record2);
411        assert_eq!(record1.items.len(), 3);
412        assert!(record1.field("a").is_some());
413        assert!(record1.field("b").is_some());
414        assert!(record1.field("c").is_some());
415    }
416
417    #[test]
418    fn test_record_remove_field() {
419        let mut record = make_test_record();
420        assert_eq!(record.items.len(), 3);
421
422        let removed = record.remove_field("age");
423        assert!(removed);
424        assert_eq!(record.items.len(), 2);
425        assert!(record.field("age").is_none());
426
427        let not_found = record.remove_field("nonexistent");
428        assert!(!not_found);
429        assert_eq!(record.items.len(), 2);
430    }
431
432    // ========== set_id tests ==========
433
434    #[test]
435    fn test_record_set_id() {
436        let mut record = make_test_record();
437        let original_len = record.items.len();
438
439        record.set_id(12345);
440
441        // ID should be set in both the id field and added to items
442        assert_eq!(record.id, 12345);
443        assert_eq!(record.items.len(), original_len + 1);
444        // ID should be inserted at position 0
445        assert_eq!(record.items[0].get_name(), WP_EVENT_ID);
446        assert_eq!(record.items[0].get_value(), &Value::Digit(12345));
447    }
448
449    #[test]
450    fn test_record_set_id_no_duplicate() {
451        let mut record = make_test_record();
452
453        record.set_id(100);
454        assert_eq!(record.id, 100);
455        let len_after_first = record.items.len();
456
457        // Try to set ID again - should update id field but not add duplicate to items
458        record.set_id(200);
459        assert_eq!(record.id, 200);
460        assert_eq!(record.items.len(), len_after_first);
461        // Original ID in items should remain
462        assert_eq!(record.get_value(WP_EVENT_ID), Some(&Value::Digit(100)));
463    }
464
465    // ========== RecordItem trait tests ==========
466
467    #[test]
468    fn test_field_as_record_item() {
469        let field: DataField = Field::from_chars("key", "value");
470
471        // Test RecordItem trait methods
472        assert_eq!(field.get_name(), "key");
473        assert_eq!(field.get_meta(), &DataType::Chars);
474        assert_eq!(field.get_value(), &Value::Chars("value".into()));
475    }
476
477    #[test]
478    fn test_field_record_item_get_value_mut() {
479        let mut field: DataField = Field::from_digit("num", 10);
480
481        *field.get_value_mut() = Value::Digit(20);
482        assert_eq!(field.get_value(), &Value::Digit(20));
483    }
484
485    // ========== FieldStorage RecordItem tests ==========
486
487    #[test]
488    fn test_field_storage_as_record_item() {
489        let storage: FieldStorage = FieldStorage::from_chars("key", "value");
490
491        // Test RecordItem trait methods
492        assert_eq!(storage.get_name(), "key");
493        assert_eq!(storage.get_meta(), &DataType::Chars);
494        assert_eq!(storage.get_value(), &Value::Chars("value".into()));
495    }
496
497    #[test]
498    fn test_field_storage_record_item_get_value_mut() {
499        let mut storage: FieldStorage = FieldStorage::from_digit("num", 10);
500
501        *storage.get_value_mut() = Value::Digit(20);
502        assert_eq!(storage.get_value(), &Value::Digit(20));
503    }
504
505    // ========== RecordItemFactory trait tests ==========
506
507    #[test]
508    fn test_record_item_factory() {
509        let digit: FieldStorage = <FieldStorage as RecordItemFactory>::from_digit("n", 42);
510        assert_eq!(digit.get_meta(), &DataType::Digit);
511
512        let ip: FieldStorage = <FieldStorage as RecordItemFactory>::from_ip(
513            "addr",
514            IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
515        );
516        assert_eq!(ip.get_meta(), &DataType::IP);
517
518        let chars: FieldStorage = <FieldStorage as RecordItemFactory>::from_chars("s", "hello");
519        assert_eq!(chars.get_meta(), &DataType::Chars);
520    }
521
522    // ========== Display test ==========
523
524    #[test]
525    fn test_record_display() {
526        let record = make_test_record();
527        let display = format!("{}", record);
528
529        assert!(display.contains("name"));
530        assert!(display.contains("age"));
531        assert!(display.contains("ip"));
532    }
533
534    // ========== Record<FieldStorage> convenience methods tests ==========
535
536    #[test]
537    fn test_push_shared() {
538        let mut record = DataRecord::default();
539        let field = Arc::new(Field::new(DataType::Chars, "static", Value::from("value")));
540
541        record.push_shared(field.clone());
542        assert_eq!(record.items.len(), 1);
543        assert!(record.items[0].is_shared());
544        assert_eq!(record.items[0].as_field().get_name(), "static");
545    }
546
547    #[test]
548    fn test_push_owned() {
549        let mut record = DataRecord::default();
550        let field = Field::new(DataType::Digit, "dynamic", Value::from(42));
551
552        record.push_owned(field);
553        assert_eq!(record.items.len(), 1);
554        assert!(!record.items[0].is_shared());
555        assert_eq!(record.items[0].as_field().get_name(), "dynamic");
556    }
557
558    #[test]
559    fn test_get_field() {
560        let mut record = DataRecord::default();
561        record.push_owned(Field::new(DataType::Chars, "test", Value::from("value")));
562
563        let field = record.get_field(0);
564        assert!(field.is_some());
565        assert_eq!(field.unwrap().get_name(), "test");
566
567        let missing = record.get_field(10);
568        assert!(missing.is_none());
569    }
570
571    #[test]
572    fn test_storage_stats() {
573        let mut record = DataRecord::default();
574
575        // Add shared fields
576        record.push_shared(Arc::new(Field::new(
577            DataType::Chars,
578            "s1",
579            Value::from("a"),
580        )));
581        record.push_shared(Arc::new(Field::new(
582            DataType::Chars,
583            "s2",
584            Value::from("b"),
585        )));
586
587        // Add owned fields
588        record.push_owned(Field::new(DataType::Digit, "o1", Value::from(1)));
589        record.push_owned(Field::new(DataType::Digit, "o2", Value::from(2)));
590        record.push_owned(Field::new(DataType::Digit, "o3", Value::from(3)));
591
592        let (shared, owned) = record.storage_stats();
593        assert_eq!(shared, 2);
594        assert_eq!(owned, 3);
595    }
596
597    #[test]
598    fn test_into_owned_record() {
599        let mut record = DataRecord::default();
600        record.push_shared(Arc::new(Field::new(
601            DataType::Chars,
602            "s",
603            Value::from("shared"),
604        )));
605        record.push_owned(Field::new(DataType::Digit, "o", Value::from(10)));
606
607        let owned_record = record.into_owned_record();
608        assert_eq!(owned_record.items.len(), 2);
609        assert_eq!(owned_record.items[0].get_name(), "s");
610        assert_eq!(owned_record.items[1].get_name(), "o");
611    }
612}