use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LogEntry {
pub timestamp: Option<String>,
pub level: Option<String>,
pub message: Option<String>,
pub tag: Option<String>,
pub fields: Map<String, Value>,
pub raw: String,
}
impl LogEntry {
pub const KNOWN_KEYS: &'static [&'static str] = &["timestamp", "level", "message", "tag"];
pub fn new(raw: impl Into<String>) -> Self {
Self {
timestamp: None,
level: None,
message: None,
tag: None,
fields: Map::new(),
raw: raw.into(),
}
}
pub fn with_tag(mut self, tag: Option<String>) -> Self {
if tag.is_some() {
self.tag = tag;
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_entry_has_no_known_fields_and_empty_map() {
let e = LogEntry::new("hello");
assert_eq!(e.raw, "hello");
assert!(e.timestamp.is_none());
assert!(e.level.is_none());
assert!(e.message.is_none());
assert!(e.tag.is_none());
assert!(e.fields.is_empty());
}
#[test]
fn with_tag_sets_when_some() {
let e = LogEntry::new("x").with_tag(Some("api".to_string()));
assert_eq!(e.tag.as_deref(), Some("api"));
}
#[test]
fn with_tag_is_a_noop_when_none() {
let e = LogEntry::new("x")
.with_tag(Some("first".to_string()))
.with_tag(None);
assert_eq!(e.tag.as_deref(), Some("first"));
}
#[test]
fn known_keys_are_the_four_documented_ones() {
assert_eq!(
LogEntry::KNOWN_KEYS,
&["timestamp", "level", "message", "tag"]
);
}
#[test]
fn roundtrips_through_serde_json() {
let mut e = LogEntry::new(r#"{"level":"error","service":"pay"}"#);
e.level = Some("error".to_string());
e.fields
.insert("service".to_string(), Value::String("pay".to_string()));
let s = serde_json::to_string(&e).expect("serialize");
let back: LogEntry = serde_json::from_str(&s).expect("deserialize");
assert_eq!(e, back);
}
}