gene/
event.rs

1use std::{
2    borrow::Cow,
3    collections::HashMap,
4    net::IpAddr,
5    path::{Path, PathBuf},
6};
7
8use crate::{FieldValue, XPath};
9
10/// Trait representing a log event
11pub trait Event<'event>: FieldGetter<'event> {
12    fn id(&self) -> i64;
13    fn source(&self) -> Cow<'_, str>;
14}
15
16/// Trait representing a structure we can fetch field values
17/// from a [`XPath`]
18pub trait FieldGetter<'field> {
19    #[inline]
20    fn get_from_path(&'field self, path: &XPath) -> Option<FieldValue<'field>> {
21        self.get_from_iter(path.iter_segments())
22    }
23
24    fn get_from_iter(
25        &'field self,
26        i: core::slice::Iter<'_, std::string::String>,
27    ) -> Option<FieldValue<'field>>;
28}
29
30macro_rules! impl_with_getter {
31    ($(($type:ty, $getter:tt)),*) => {
32        $(
33            impl<'f> FieldGetter<'f> for $type {
34                #[inline]
35                fn get_from_iter(&'f self, i: core::slice::Iter<'_, std::string::String>) -> Option<FieldValue<'f>> {
36                    if i.len() > 0 {
37                        return None;
38                    }
39                    Some(self.$getter().into())
40                }
41            }
42        )*
43    };
44}
45
46macro_rules! impl_for_type {
47    ($($type: ty),*) => {
48        $(
49            impl<'f> FieldGetter<'f>  for $type {
50                #[inline]
51                fn get_from_iter(&'f self, i: core::slice::Iter<'_, std::string::String>) -> Option<FieldValue<'f>> {
52                    if i.len() > 0 {
53                        return None;
54                    }
55                    Some(self.into())
56                }
57            }
58        )*
59    };
60}
61
62impl_for_type!(
63    Cow<'_, str>,
64    Cow<'_, PathBuf>,
65    str,
66    String,
67    i8,
68    i16,
69    i32,
70    i64,
71    isize,
72    u8,
73    u16,
74    u32,
75    u64,
76    usize,
77    f32,
78    f64,
79    bool
80);
81
82impl_with_getter!(
83    (Path, to_string_lossy),
84    (PathBuf, to_string_lossy),
85    (IpAddr, to_string)
86);
87
88impl<'field, T> FieldGetter<'field> for Option<T>
89where
90    T: FieldGetter<'field>,
91{
92    #[inline]
93    fn get_from_iter(
94        &'field self,
95        i: core::slice::Iter<'_, std::string::String>,
96    ) -> Option<FieldValue<'field>> {
97        match self {
98            Some(v) => v.get_from_iter(i),
99            None => Some(FieldValue::None),
100        }
101    }
102}
103
104impl<'field> FieldGetter<'field> for HashMap<String, String> {
105    #[inline]
106    fn get_from_iter(
107        &'field self,
108        mut i: core::slice::Iter<'_, std::string::String>,
109    ) -> Option<FieldValue<'field>> {
110        let k = match i.next() {
111            Some(s) => s,
112            None => return Some(FieldValue::Some),
113        };
114
115        if i.len() > 0 {
116            return None;
117        }
118        self.get(k).map(|f| f.into())
119    }
120}
121
122#[cfg(test)]
123mod test {
124    use std::str::FromStr;
125
126    use gene_derive::{Event, FieldGetter};
127    use serde::Deserialize;
128
129    use super::*;
130
131    macro_rules! path {
132        ($p:literal) => {
133            XPath::from_str($p).unwrap()
134        };
135    }
136
137    #[derive(FieldGetter, Deserialize, Default)]
138    #[getter(use_serde_rename)]
139    struct LogData {
140        some_float: f32,
141        #[serde(rename = "serde_renamed")]
142        some_string: String,
143        #[getter(rename = "event_id")]
144        id: i64,
145        #[getter(skip)]
146        #[allow(dead_code)]
147        osef: u32,
148        path: PathBuf,
149    }
150
151    // this is just a show case, it would be
152    // shorter to set source = "test".into()
153    #[inline]
154    fn source() -> Cow<'static, str> {
155        "test".into()
156    }
157
158    #[derive(Event, FieldGetter, Default)]
159    #[event(id = self.data.id, source = source())]
160    struct LogEntry<T>
161    where
162        T: Default,
163    {
164        name: String,
165        data: LogData,
166        t: T,
167    }
168
169    #[test]
170    fn test_derive_event() {
171        let entry = LogEntry::<i64> {
172            name: "SomeLogEntry".into(),
173            data: LogData {
174                some_float: 4242.0,
175                some_string: "some string".into(),
176                id: 42,
177                osef: 34,
178                path: PathBuf::from("/absolute/path"),
179            },
180            t: 24,
181        };
182
183        assert_eq!(
184            entry.get_from_path(&path!(".name")),
185            Some("SomeLogEntry".into())
186        );
187        assert_eq!(
188            entry.get_from_path(&path!(".data.some_float")),
189            Some(4242.0.into())
190        );
191
192        // test that event id is generated correctly
193        assert_eq!(entry.id(), 42);
194
195        // check that #[event(rename)] worked
196        assert_eq!(
197            entry.get_from_path(&path!(".data.event_id")),
198            Some(42i64.into())
199        );
200        // even if renamed we should still be able to reach via the real field name
201        assert_eq!(entry.get_from_path(&path!(".data.id")), Some(42i64.into()));
202
203        // check that #[serde(rename)] worked
204        assert_eq!(
205            entry.get_from_path(&path!(".data.serde_renamed")),
206            Some("some string".into())
207        );
208
209        // check that #[event(skip)] worked
210        assert_eq!(entry.get_from_path(&path!(".data.osef")), None,);
211
212        assert_eq!(entry.get_from_path(&path!(".t")), Some(24.into()));
213
214        // getting a PathBuf must return a FieldValue::String
215        assert_eq!(
216            entry.get_from_path(&path!(".data.path")),
217            Some("/absolute/path".into())
218        );
219
220        // checking that source function got generated properly
221        assert_eq!(entry.source(), "test");
222    }
223
224    #[test]
225    // test reproducing https://github.com/0xrawsec/gene-rs/issues/1
226    fn test_option_bug() {
227        #[derive(FieldGetter, Debug)]
228        pub struct SomeStruct {
229            some_value: u64,
230        }
231
232        #[derive(Event, FieldGetter, Debug)]
233        #[event(id = 1, source = "whatever".into())]
234        pub struct SomeEvent {
235            pub type_id: String,
236            pub data: Option<SomeStruct>,
237        }
238
239        let event = SomeEvent {
240            type_id: "some_id".to_string(),
241            data: Some(SomeStruct { some_value: 1 }),
242        };
243
244        assert_eq!(
245            event.get_from_path(&path!(".type_id")),
246            Some("some_id".into())
247        );
248
249        assert_eq!(
250            event.get_from_path(&path!(".data.some_value")),
251            Some(1u64.into())
252        );
253
254        // if value is not known, it must at least return FieldValue::Some
255        assert!(event.get_from_path(&path!(".data")).is_some());
256        // None must be returned if trying to get a non existing field
257        assert!(event.get_from_path(&path!(".unknown")).is_none());
258    }
259}