use std::{
borrow::Cow,
collections::HashMap,
net::IpAddr,
path::{Path, PathBuf},
};
use crate::{FieldValue, XPath};
pub struct FieldNameIterator<'f> {
i: Option<usize>,
field_names: &'f [String],
}
impl<'f> From<&'f [String]> for FieldNameIterator<'f> {
fn from(value: &'f [String]) -> Self {
Self {
i: None,
field_names: value,
}
}
}
impl<'f> From<&'f XPath> for FieldNameIterator<'f> {
fn from(value: &'f XPath) -> Self {
value.segments().into()
}
}
impl<'f> FieldNameIterator<'f> {
#[inline]
pub fn next_field_name(&mut self) -> Option<&str> {
match self.i.as_mut() {
Some(i) => {
*i = i.checked_add(1)?;
self.field_names.get(*i).map(|s| s.as_ref())
}
None => {
self.i = Some(0);
self.field_names.first().map(|s| s.as_ref())
}
}
}
#[inline(always)]
pub fn is_terminal(&self) -> bool {
self.i.unwrap_or_default() == self.field_names.len() - 1
}
}
pub trait Event<'event>: FieldGetter<'event> {
fn id(&self) -> i64;
fn source(&self) -> Cow<'_, str>;
}
pub trait FieldGetter<'field> {
#[inline]
fn get_from_path(&'field self, path: &XPath) -> Option<FieldValue<'field>> {
self.get_from_iter(FieldNameIterator::from(path))
}
fn get_from_iter(&'field self, i: FieldNameIterator<'_>) -> Option<FieldValue<'field>>;
}
macro_rules! impl_with_getter {
($(($type:ty, $getter:tt)),*) => {
$(
impl<'f> FieldGetter<'f> for $type {
#[inline]
fn get_from_iter(&'f self, i: FieldNameIterator<'_>) -> Option<FieldValue<'f>> {
if !i.is_terminal() {
return None;
}
Some(self.$getter().into())
}
}
)*
};
}
macro_rules! impl_for_type {
($($type: ty),*) => {
$(
impl<'f> FieldGetter<'f> for $type {
#[inline]
fn get_from_iter(&'f self, i: FieldNameIterator<'_>) -> Option<FieldValue<'f>> {
if !i.is_terminal() {
return None;
}
Some(self.into())
}
}
)*
};
}
impl_for_type!(
Cow<'_, str>,
Cow<'_, PathBuf>,
&'_ str,
str,
String,
i8,
i16,
i32,
i64,
isize,
u8,
u16,
u32,
u64,
usize,
f32,
f64,
bool
);
impl_with_getter!(
(Path, to_string_lossy),
(PathBuf, to_string_lossy),
(IpAddr, to_string)
);
impl<'field, T> FieldGetter<'field> for Option<T>
where
T: FieldGetter<'field>,
{
#[inline]
fn get_from_iter(&'field self, i: FieldNameIterator<'_>) -> Option<FieldValue<'field>> {
match self {
Some(v) => v.get_from_iter(i),
None => Some(FieldValue::None),
}
}
}
impl<'f, T> FieldGetter<'f> for HashMap<String, T>
where
T: FieldGetter<'f>,
{
#[inline]
fn get_from_iter(&'f self, mut i: FieldNameIterator<'_>) -> Option<FieldValue<'f>> {
let k = match i.next_field_name() {
Some(s) => s,
None => {
return Some(FieldValue::Some);
}
};
self.get(k)?.get_from_iter(i)
}
}
macro_rules! impl_field_getter_for_vec {
($($ty:ty),*) => {
$(
impl<'f> FieldGetter<'f> for Vec<$ty>
where
$ty: Into<FieldValue<'f>>,
{
#[inline]
fn get_from_iter(
&'f self,
i: FieldNameIterator<'_>,
) -> Option<FieldValue<'f>> {
if !i.is_terminal(){
return None;
}
Some(FieldValue::Vector(self.iter().map(|v| v.into()).collect()))
}
}
)*
};
}
impl_field_getter_for_vec!(
String,
Cow<'f, str>,
Cow<'f, PathBuf>,
&'f str,
i8,
i16,
i32,
i64,
u8,
u16,
u32,
u64,
f32,
f64,
bool
);
#[cfg(test)]
mod test {
use std::str::FromStr;
use gene_derive::{Event, FieldGetter};
use serde::Deserialize;
use super::*;
macro_rules! path {
($p:literal) => {
XPath::from_str($p).unwrap()
};
}
#[derive(FieldGetter, Deserialize, Default)]
#[getter(use_serde_rename)]
struct LogData {
some_float: f32,
#[serde(rename = "serde_renamed")]
some_string: String,
#[getter(rename = "event_id")]
id: i64,
#[getter(skip)]
#[allow(dead_code)]
osef: u32,
path: PathBuf,
}
#[inline]
fn source() -> Cow<'static, str> {
"test".into()
}
#[derive(Event, FieldGetter, Default)]
#[event(id = self.data.id, source = source())]
struct LogEntry<T>
where
T: Default,
{
name: String,
data: LogData,
t: T,
}
#[test]
fn test_derive_event() {
let entry = LogEntry::<i64> {
name: "SomeLogEntry".into(),
data: LogData {
some_float: 4242.0,
some_string: "some string".into(),
id: 42,
osef: 34,
path: PathBuf::from("/absolute/path"),
},
t: 24,
};
assert_eq!(
entry.get_from_path(&path!(".name")),
Some("SomeLogEntry".into())
);
assert_eq!(
entry.get_from_path(&path!(".data.some_float")),
Some(4242.0.into())
);
assert_eq!(entry.id(), 42);
assert_eq!(
entry.get_from_path(&path!(".data.event_id")),
Some(42i64.into())
);
assert_eq!(entry.get_from_path(&path!(".data.id")), Some(42i64.into()));
assert_eq!(
entry.get_from_path(&path!(".data.serde_renamed")),
Some("some string".into())
);
assert_eq!(entry.get_from_path(&path!(".data.osef")), None,);
assert_eq!(entry.get_from_path(&path!(".t")), Some(24.into()));
assert_eq!(
entry.get_from_path(&path!(".data.path")),
Some("/absolute/path".into())
);
assert_eq!(entry.source(), "test");
}
#[test]
fn test_option_bug() {
#[derive(FieldGetter, Debug)]
pub struct SomeStruct {
some_value: u64,
}
#[derive(Event, FieldGetter, Debug)]
#[event(id = 1, source = "whatever".into())]
pub struct SomeEvent {
pub type_id: String,
pub data: Option<SomeStruct>,
}
let event = SomeEvent {
type_id: "some_id".to_string(),
data: Some(SomeStruct { some_value: 1 }),
};
assert_eq!(
event.get_from_path(&path!(".type_id")),
Some("some_id".into())
);
assert_eq!(
event.get_from_path(&path!(".data.some_value")),
Some(1u64.into())
);
assert!(event.get_from_path(&path!(".data")).is_some());
assert!(event.get_from_path(&path!(".unknown")).is_none());
}
#[test]
fn test_vec_field_getter() {
#[derive(FieldGetter)]
struct TestStruct {
items: Vec<String>,
numbers: Vec<i32>,
}
let test_struct = TestStruct {
items: vec!["first".into(), "second".into()],
numbers: vec![10, 20, 30],
};
assert!(test_struct
.get_from_path(&path!(".items"))
.unwrap()
.is_vector());
assert!(test_struct
.get_from_path(&path!(".numbers"))
.unwrap()
.is_vector());
assert!(test_struct.get_from_path(&path!(".items.0")).is_none());
assert!(test_struct
.get_from_path(&path!(".numbers.first"))
.is_none());
}
#[test]
fn test_hashmap_field_getter() {
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("name".to_string(), "test".to_string());
assert_eq!(map.get_from_path(&path!(".name")), Some("test".into()));
assert_eq!(map.get_from_path(&path!(".unknown")), None);
assert_eq!(map.get_from_path(&path!(".name.nested")), None);
#[derive(FieldGetter)]
struct TestStruct {
data: HashMap<String, String>,
metadata: HashMap<String, i32>,
}
let mut data_map = HashMap::new();
data_map.insert("key1".to_string(), "value1".to_string());
data_map.insert("key2".to_string(), "value2".to_string());
let mut metadata_map = HashMap::new();
metadata_map.insert("count".to_string(), 100);
metadata_map.insert("size".to_string(), 200);
let test_struct = TestStruct {
data: data_map,
metadata: metadata_map,
};
assert_eq!(
test_struct.get_from_path(&path!(".data")),
Some(FieldValue::Some)
);
assert_eq!(
test_struct.get_from_path(&path!(".metadata")),
Some(FieldValue::Some)
);
assert_eq!(
test_struct.get_from_path(&path!(".data.key1")),
Some("value1".into())
);
assert_eq!(
test_struct.get_from_path(&path!(".metadata.count")),
Some(100i32.into())
);
assert_eq!(test_struct.get_from_path(&path!(".data.unknown")), None);
assert_eq!(test_struct.get_from_path(&path!(".metadata.missing")), None);
}
}