use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::convert::Into;
use std::ops;
use std::str::FromStr;
use std::string::String;
#[cfg(feature = "serde-serialize")]
use serde::{Serialize, Serializer};
#[allow(non_camel_case_types)]
pub type time_t = i64;
#[allow(non_camel_case_types)]
pub type pid_t = i32;
#[allow(non_camel_case_types)]
pub type msgid_t = String;
use crate::facility;
use crate::parser;
use crate::severity;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProcId {
PID(pid_t),
Name(String),
}
impl PartialOrd for ProcId {
fn partial_cmp(&self, other: &ProcId) -> Option<Ordering> {
match (self, other) {
(ProcId::PID(s_p), ProcId::PID(o_p)) => Some(s_p.cmp(o_p)),
(ProcId::Name(s_n), ProcId::Name(o_n)) => Some(s_n.cmp(o_n)),
_ => None,
}
}
}
#[cfg(feature = "serde-serialize")]
impl Serialize for ProcId {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
match *self {
ProcId::PID(ref p) => ser.serialize_i32(*p),
ProcId::Name(ref n) => ser.serialize_str(n),
}
}
}
pub type SDIDType = String;
pub type SDParamIDType = String;
pub type SDParamValueType = String;
pub type StructuredDataElement = BTreeMap<SDParamIDType, SDParamValueType>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StructuredData {
elements: BTreeMap<SDIDType, StructuredDataElement>,
}
impl ops::Deref for StructuredData {
type Target = BTreeMap<SDIDType, StructuredDataElement>;
fn deref(&self) -> &Self::Target {
&self.elements
}
}
#[cfg(feature = "serde-serialize")]
impl Serialize for StructuredData {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
self.elements.serialize(ser)
}
}
impl StructuredData {
pub fn new_empty() -> Self {
StructuredData {
elements: BTreeMap::new(),
}
}
pub fn entry<SI>(&mut self, sd_id: SI) -> &mut BTreeMap<String, String>
where
SI: Into<SDIDType>,
{
self.elements.entry(sd_id.into()).or_default()
}
pub fn insert_tuple<SI, SPI, SPV>(&mut self, sd_id: SI, sd_param_id: SPI, sd_param_value: SPV)
where
SI: Into<SDIDType>,
SPI: Into<SDParamIDType>,
SPV: Into<SDParamValueType>,
{
self.entry(sd_id)
.insert(sd_param_id.into(), sd_param_value.into());
}
pub fn find_tuple<'b>(
&'b self,
sd_id: &str,
sd_param_id: &str,
) -> Option<&'b SDParamValueType> {
if let Some(sub_map) = self.elements.get(sd_id) {
if let Some(value) = sub_map.get(sd_param_id) {
Some(value)
} else {
None
}
} else {
None
}
}
pub fn find_sdid<'b>(&'b self, sd_id: &str) -> Option<&'b StructuredDataElement> {
self.elements.get(sd_id)
}
pub fn len(&self) -> usize {
self.elements.len()
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SyslogMessage {
pub severity: severity::SyslogSeverity,
pub facility: facility::SyslogFacility,
pub version: i32,
pub timestamp: Option<time_t>,
pub timestamp_nanos: Option<u32>,
pub hostname: Option<String>,
pub appname: Option<String>,
pub procid: Option<ProcId>,
pub msgid: Option<msgid_t>,
pub sd: StructuredData,
pub msg: String,
}
impl FromStr for SyslogMessage {
type Err = parser::ParseErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parser::parse_message(s)
}
}
#[cfg(test)]
mod tests {
use super::StructuredData;
use super::SyslogMessage;
#[cfg(feature = "serde-serialize")]
use crate::facility::SyslogFacility::*;
#[cfg(feature = "serde-serialize")]
use crate::severity::SyslogSeverity::*;
#[cfg(feature = "serde-serialize")]
use serde_json;
#[test]
fn test_structured_data_basic() {
let mut s = StructuredData::new_empty();
s.insert_tuple("foo", "bar", "baz");
let v = s.find_tuple("foo", "bar").expect("should find foo/bar");
assert_eq!(v, "baz");
assert!(s.find_tuple("foo", "baz").is_none());
}
#[cfg(feature = "serde-serialize")]
#[test]
fn test_structured_data_serialization_serde() {
let mut s = StructuredData::new_empty();
s.insert_tuple("foo", "bar", "baz");
s.insert_tuple("foo", "baz", "bar");
s.insert_tuple("faa", "bar", "baz");
let encoded = serde_json::to_string(&s).expect("Should encode to JSON");
assert_eq!(
encoded,
r#"{"faa":{"bar":"baz"},"foo":{"bar":"baz","baz":"bar"}}"#
);
}
#[cfg(feature = "serde-serialize")]
#[test]
fn test_serialization_serde() {
let m = SyslogMessage {
severity: SEV_INFO,
facility: LOG_KERN,
version: 1,
timestamp: None,
timestamp_nanos: None,
hostname: None,
appname: None,
procid: None,
msgid: None,
sd: StructuredData::new_empty(),
msg: String::from(""),
};
let encoded = serde_json::to_string(&m).expect("Should encode to JSON");
assert_eq!(
encoded,
"{\"severity\":\"info\",\"facility\":\"kern\",\"version\":1,\"timestamp\":null,\"timestamp_nanos\":null,\"hostname\":null,\"appname\":null,\"procid\":null,\"msgid\":null,\"sd\":{},\"msg\":\"\"}"
);
}
#[test]
fn test_deref_structureddata() {
let mut s = StructuredData::new_empty();
s.insert_tuple("foo", "bar", "baz");
s.insert_tuple("foo", "baz", "bar");
s.insert_tuple("faa", "bar", "baz");
assert_eq!("baz", s.get("foo").and_then(|foo| foo.get("bar")).unwrap());
assert_eq!("bar", s.get("foo").and_then(|foo| foo.get("baz")).unwrap());
assert_eq!("baz", s.get("faa").and_then(|foo| foo.get("bar")).unwrap());
}
#[test]
fn test_fromstr() {
let msg = "<1>1 1985-04-12T23:20:50.52Z host - - - -"
.parse::<SyslogMessage>()
.expect("Should parse empty message");
assert_eq!(msg.timestamp, Some(482196050));
}
}