1use std::{
2 borrow::Cow,
3 collections::HashMap,
4 net::IpAddr,
5 path::{Path, PathBuf},
6};
7
8use crate::{FieldValue, XPath};
9
10pub trait Event<'event>: FieldGetter<'event> {
12 fn id(&self) -> i64;
13 fn source(&self) -> Cow<'_, str>;
14}
15
16pub 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 #[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 assert_eq!(entry.id(), 42);
194
195 assert_eq!(
197 entry.get_from_path(&path!(".data.event_id")),
198 Some(42i64.into())
199 );
200 assert_eq!(entry.get_from_path(&path!(".data.id")), Some(42i64.into()));
202
203 assert_eq!(
205 entry.get_from_path(&path!(".data.serde_renamed")),
206 Some("some string".into())
207 );
208
209 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 assert_eq!(
216 entry.get_from_path(&path!(".data.path")),
217 Some("/absolute/path".into())
218 );
219
220 assert_eq!(entry.source(), "test");
222 }
223
224 #[test]
225 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 assert!(event.get_from_path(&path!(".data")).is_some());
256 assert!(event.get_from_path(&path!(".unknown")).is_none());
258 }
259}