From acd007cf0b67a44feac74d3220975d516ca3ab79 Mon Sep 17 00:00:00 2001 From: James Brown Date: Wed, 23 Sep 2020 11:04:31 -0700 Subject: [PATCH] update rust-syslog-rfc5424 for 2018ed - use Rust 2018 edition - rustfmt various things - use `thiserror` to generate Error implementations instead of doing it by hand - adds public `TryFrom` implementations for SyslogSeverity and SyslogFacility --- Cargo.toml | 7 +- README.md | 3 +- src/facility.rs | 81 ++++++++++------- src/lib.rs | 15 +--- src/message.rs | 89 +++++++++--------- src/parser.rs | 235 ++++++++++++++++++++++++++---------------------- src/severity.rs | 52 +++++++---- 7 files changed, 266 insertions(+), 216 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f6512c..127c638 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syslog_rfc5424" -version = "0.6.1" +version = "0.7.0" authors = ["James Brown "] description = "Parser for RFC5424 (IETF-format) syslog messages" documentation = "https://docs.rs/syslog_rfc5424/" @@ -8,15 +8,14 @@ homepage = "https://github.com/Roguelazer/rust-syslog-rfc5424" repository = "https://github.com/Roguelazer/rust-syslog-rfc5424" license = "ISC" readme = "README.md" +edition = "2018" [dependencies] time = "^0.1" -# this should be a dev dependency, but there's some kind of issue with -# dev-only macro crates -assert_matches = "1.0" serde = { version = "1.0", optional = true } serde_derive = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true } +thiserror = "1.0" [dev-dependencies] timeit = "0.1" diff --git a/README.md b/README.md index 17fc901..5526429 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,10 @@ This module implements an [RFC 5424](https://tools.ietf.org/html/rfc5424) IETF S This tool supports serializing the parsed messages using serde if it's built with the `serde-serialize` feature. - This library is licensed under the ISC license, a copy of which can be found in [LICENSE.txt](LICENSE.txt) +The minimum supported Rust version for this library is 1.34. + ## Performance On a recent system[1](#sysfootnote), a release build takes approximately 8µs to parse an average message and approximately 300ns to parse the smallest legal message. Debug timings are a bit worse -- about 60µs for an average message and about 8µs for the minimal message. A single-threaded Syslog server should be able to parse at least 100,000 messages/s, as long as you run a separate thread for the parser. diff --git a/src/facility.rs b/src/facility.rs index f2d2511..5e9c7ab 100644 --- a/src/facility.rs +++ b/src/facility.rs @@ -1,7 +1,11 @@ -#[cfg(feature="serde-serialize")] -use serde::{Serializer, Serialize}; +#[cfg(feature = "serde-serialize")] +use serde::{Serialize, Serializer}; + +use std::convert::TryFrom; + +use thiserror::Error; -#[derive(Copy,Clone,Debug,PartialEq, Eq, Ord, PartialOrd)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)] #[allow(non_camel_case_types)] /// Syslog facilities. Taken From RFC 5424, but I've heard that some platforms mix these around. /// Names are from Linux. @@ -32,36 +36,51 @@ pub enum SyslogFacility { LOG_LOCAL7 = 23, } +#[derive(Debug, Error)] +pub enum SyslogFacilityError { + #[error("integer does not correspond to a known facility")] + InvalidInteger, +} + +impl TryFrom for SyslogFacility { + type Error = SyslogFacilityError; + + #[inline(always)] + fn try_from(i: i32) -> Result { + Ok(match i { + 0 => SyslogFacility::LOG_KERN, + 1 => SyslogFacility::LOG_USER, + 2 => SyslogFacility::LOG_MAIL, + 3 => SyslogFacility::LOG_DAEMON, + 4 => SyslogFacility::LOG_AUTH, + 5 => SyslogFacility::LOG_SYSLOG, + 6 => SyslogFacility::LOG_LPR, + 7 => SyslogFacility::LOG_NEWS, + 8 => SyslogFacility::LOG_UUCP, + 9 => SyslogFacility::LOG_CRON, + 10 => SyslogFacility::LOG_AUTHPRIV, + 11 => SyslogFacility::LOG_FTP, + 12 => SyslogFacility::LOG_NTP, + 13 => SyslogFacility::LOG_AUDIT, + 14 => SyslogFacility::LOG_ALERT, + 15 => SyslogFacility::LOG_CLOCKD, + 16 => SyslogFacility::LOG_LOCAL0, + 17 => SyslogFacility::LOG_LOCAL1, + 18 => SyslogFacility::LOG_LOCAL2, + 19 => SyslogFacility::LOG_LOCAL3, + 20 => SyslogFacility::LOG_LOCAL4, + 21 => SyslogFacility::LOG_LOCAL5, + 22 => SyslogFacility::LOG_LOCAL6, + 23 => SyslogFacility::LOG_LOCAL7, + _ => return Err(SyslogFacilityError::InvalidInteger), + }) + } +} + impl SyslogFacility { /// Convert an int (as used in the wire serialization) into a `SyslogFacility` pub(crate) fn from_int(i: i32) -> Option { - match i { - 0 => Some(SyslogFacility::LOG_KERN), - 1 => Some(SyslogFacility::LOG_USER), - 2 => Some(SyslogFacility::LOG_MAIL), - 3 => Some(SyslogFacility::LOG_DAEMON), - 4 => Some(SyslogFacility::LOG_AUTH), - 5 => Some(SyslogFacility::LOG_SYSLOG), - 6 => Some(SyslogFacility::LOG_LPR), - 7 => Some(SyslogFacility::LOG_NEWS), - 8 => Some(SyslogFacility::LOG_UUCP), - 9 => Some(SyslogFacility::LOG_CRON), - 10 => Some(SyslogFacility::LOG_AUTHPRIV), - 11 => Some(SyslogFacility::LOG_FTP), - 12 => Some(SyslogFacility::LOG_NTP), - 13 => Some(SyslogFacility::LOG_AUDIT), - 14 => Some(SyslogFacility::LOG_ALERT), - 15 => Some(SyslogFacility::LOG_CLOCKD), - 16 => Some(SyslogFacility::LOG_LOCAL0), - 17 => Some(SyslogFacility::LOG_LOCAL1), - 18 => Some(SyslogFacility::LOG_LOCAL2), - 19 => Some(SyslogFacility::LOG_LOCAL3), - 20 => Some(SyslogFacility::LOG_LOCAL4), - 21 => Some(SyslogFacility::LOG_LOCAL5), - 22 => Some(SyslogFacility::LOG_LOCAL6), - 23 => Some(SyslogFacility::LOG_LOCAL7), - _ => None, - } + Self::try_from(i).ok() } /// Convert a syslog facility into a unique string representation @@ -95,7 +114,6 @@ impl SyslogFacility { } } - #[cfg(feature = "serde-serialize")] impl Serialize for SyslogFacility { fn serialize(&self, ser: S) -> Result { @@ -103,7 +121,6 @@ impl Serialize for SyslogFacility { } } - #[cfg(test)] mod tests { use super::SyslogFacility; diff --git a/src/lib.rs b/src/lib.rs index 78d28fd..b3586ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,24 +30,17 @@ //! message. Rust doesn't have a convenient way to only treat *some* of a buffer as utf-8, //! so I'm just not supporting that. Most "real" syslog servers barf on it anway. //! -#[cfg(test)] -extern crate assert_matches; -extern crate time; -#[cfg(feature = "serde-serialize")] -extern crate serde; #[cfg(feature = "serde-serialize")] #[macro_use] extern crate serde_derive; -#[cfg(feature = "serde-serialize")] -extern crate serde_json; -pub mod message; -mod severity; mod facility; +pub mod message; pub mod parser; +mod severity; -pub use severity::SyslogSeverity; pub use facility::SyslogFacility; +pub use severity::SyslogSeverity; -pub use parser::parse_message; pub use message::SyslogMessage; +pub use parser::parse_message; diff --git a/src/message.rs b/src/message.rs index 87b05b3..3ad6c85 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,14 +1,14 @@ //! In-memory representation of a single Syslog message. -use std::string::String; +use std::cmp::Ordering; use std::collections::BTreeMap; use std::convert::Into; use std::ops; -use std::cmp::Ordering; use std::str::FromStr; +use std::string::String; -#[cfg(feature="serde-serialize")] -use serde::{Serializer, Serialize}; +#[cfg(feature = "serde-serialize")] +use serde::{Serialize, Serializer}; #[allow(non_camel_case_types)] pub type time_t = i64; @@ -17,19 +17,17 @@ pub type pid_t = i32; #[allow(non_camel_case_types)] pub type msgid_t = String; -use severity; -use facility; -use parser; - +use crate::facility; +use crate::parser; +use crate::severity; -#[derive(Clone,Debug,PartialEq,Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] /// `ProcID`s are usually numeric PIDs; however, on some systems, they may be something else pub enum ProcId { PID(pid_t), Name(String), } - impl PartialOrd for ProcId { fn partial_cmp(&self, other: &ProcId) -> Option { match (self, other) { @@ -40,7 +38,6 @@ impl PartialOrd for ProcId { } } - #[cfg(feature = "serde-serialize")] impl Serialize for ProcId { fn serialize(&self, ser: S) -> Result { @@ -55,11 +52,9 @@ pub type SDIDType = String; pub type SDParamIDType = String; pub type SDParamValueType = String; - pub type StructuredDataElement = BTreeMap; - -#[derive(Clone,Debug,PartialEq,Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] /// Container for the `StructuredData` component of a syslog message. /// /// This is a map from `SD_ID` to pairs of `SD_ParamID`, `SD_ParamValue` @@ -81,7 +76,6 @@ impl ops::Deref for StructuredData { } } - #[cfg(feature = "serde-serialize")] impl Serialize for StructuredData { fn serialize(&self, ser: S) -> Result { @@ -91,29 +85,37 @@ impl Serialize for StructuredData { impl StructuredData { pub fn new_empty() -> Self { - StructuredData { elements: BTreeMap::new() } + StructuredData { + elements: BTreeMap::new(), + } } /// Insert a new (sd_id, sd_param_id) -> sd_value mapping into the StructuredData - pub fn insert_tuple(&mut self, - sd_id: SI, - sd_param_id: SPI, - sd_param_value: SPV) - -> () - where SI: Into, - SPI: Into, - SPV: Into + pub fn insert_tuple( + &mut self, + sd_id: SI, + sd_param_id: SPI, + sd_param_value: SPV, + ) -> () + where + SI: Into, + SPI: Into, + SPV: Into, { - let sub_map = self.elements.entry(sd_id.into()).or_insert_with(BTreeMap::new); + let sub_map = self + .elements + .entry(sd_id.into()) + .or_insert_with(BTreeMap::new); sub_map.insert(sd_param_id.into(), sd_param_value.into()); } /// Lookup by SDID, SDParamID pair - pub fn find_tuple<'b>(&'b self, - sd_id: &str, - sd_param_id: &str) - -> Option<&'b SDParamValueType> { - // TODO: use traits to make these based on the public types isntead of &str + pub fn find_tuple<'b>( + &'b self, + sd_id: &str, + sd_param_id: &str, + ) -> Option<&'b SDParamValueType> { + // TODO: use traits to make these based on the public types instead of &str if let Some(sub_map) = self.elements.get(sd_id) { if let Some(value) = sub_map.get(sd_param_id) { Some(value) @@ -141,9 +143,8 @@ impl StructuredData { } } - #[cfg_attr(feature = "serde-serialize", derive(Serialize))] -#[derive(Clone,Debug,PartialEq,Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] /// A RFC5424-protocol syslog message pub struct SyslogMessage { pub severity: severity::SyslogSeverity, @@ -159,7 +160,6 @@ pub struct SyslogMessage { pub msg: String, } - impl FromStr for SyslogMessage { type Err = parser::ParseErr; @@ -171,17 +171,16 @@ impl FromStr for SyslogMessage { } } - #[cfg(test)] mod tests { - #[cfg(feature = "serde-serialize")] - use serde_json; use super::StructuredData; use super::SyslogMessage; - #[cfg(feature="serde-serialize")] - use severity::SyslogSeverity::*; - #[cfg(feature="serde-serialize")] - use facility::SyslogFacility::*; + #[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() { @@ -200,8 +199,10 @@ mod tests { 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"}}"#); + assert_eq!( + encoded, + r#"{"faa":{"bar":"baz"},"foo":{"bar":"baz","baz":"bar"}}"# + ); } #[cfg(feature = "serde-serialize")] @@ -241,7 +242,9 @@ mod tests { #[test] fn test_fromstr() { - let msg = "<1>1 1985-04-12T23:20:50.52Z host - - - -".parse::().expect("Should parse empty message"); + let msg = "<1>1 1985-04-12T23:20:50.52Z host - - - -" + .parse::() + .expect("Should parse empty message"); assert_eq!(msg.timestamp, Some(482196050)); } } diff --git a/src/parser.rs b/src/parser.rs index 2f0e9f0..e38f29e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,54 +1,44 @@ use std::borrow::Cow; -use std::str::FromStr; -use std::str; use std::num; +use std::str; +use std::str::FromStr; use std::string; -use std::fmt; -use std::error::Error; +use thiserror::Error; use time; -use severity; -use facility; -use message::{SyslogMessage, ProcId, StructuredData}; +use crate::facility; +use crate::message::{ProcId, StructuredData, SyslogMessage}; +use crate::severity; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ParseErr { + #[error("regular expression does not parse")] RegexDoesNotMatchErr, + #[error("bad severity in message")] BadSeverityInPri, + #[error("bad facility in message")] BadFacilityInPri, + #[error("unexpected eof")] UnexpectedEndOfInput, + #[error("too few digits in numeric field")] TooFewDigits, + #[error("too many digits in numeric field")] TooManyDigits, + #[error("invalid UTC offset")] InvalidUTCOffset, - BaseUnicodeError(str::Utf8Error), - UnicodeError(string::FromUtf8Error), + #[error("unicode error: {0}")] + BaseUnicodeError(#[from] str::Utf8Error), + #[error("unicode error: {0}")] + UnicodeError(#[from] string::FromUtf8Error), + #[error("unexpected input at character {0}")] ExpectedTokenErr(char), - IntConversionErr(num::ParseIntError), + #[error("integer conversion error: {0}")] + IntConversionErr(#[from] num::ParseIntError), + #[error("missing field {0}")] MissingField(&'static str), } -impl fmt::Display for ParseErr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(self, f) - } -} - -impl Error for ParseErr { - fn description(&self) -> &'static str { - "error parsing RFC5424 syslog message" - } - - fn cause(&self) -> Option<&Error> { - match *self { - ParseErr::BaseUnicodeError(ref e) => Some(e), - ParseErr::UnicodeError(ref e) => Some(e), - ParseErr::IntConversionErr(ref e) => Some(e), - _ => None, - } - } -} - // We parse with this super-duper-dinky hand-coded recursive descent parser because we don't really // have much other choice: // @@ -63,10 +53,12 @@ impl Error for ParseErr { // macros will update that slice as they consume tokens. macro_rules! maybe_expect_char { - ($s:expr, $e: expr) => (match $s.chars().next() { - Some($e) => Some(&$s[1..]), - _ => None, - }) + ($s:expr, $e: expr) => { + match $s.chars().next() { + Some($e) => Some(&$s[1..]), + _ => None, + } + }; } macro_rules! take_item { @@ -74,10 +66,9 @@ macro_rules! take_item { let (t, r) = $e?; $r = r; t - }} + }}; } - type ParseResult = Result; macro_rules! take_char { @@ -86,18 +77,18 @@ macro_rules! take_char { Some($c) => &$e[1..], Some(_) => { return Err(ParseErr::ExpectedTokenErr($c)); - }, + } None => { return Err(ParseErr::UnexpectedEndOfInput); } } - }} + }}; } fn take_while(input: &str, f: F, max_chars: usize) -> (&str, Option<&str>) - where F: Fn(char) -> bool +where + F: Fn(char) -> bool, { - for (idx, chr) in input.char_indices() { if !f(chr) { return (&input[..idx], Some(&input[idx..])); @@ -111,10 +102,13 @@ fn take_while(input: &str, f: F, max_chars: usize) -> (&str, Option<&str>) fn parse_sd_id(input: &str) -> ParseResult<(String, &str)> { let (res, rest) = take_while(input, |c| c != ' ' && c != '=' && c != ']', 128); - Ok((String::from(res), match rest { - Some(s) => s, - None => return Err(ParseErr::UnexpectedEndOfInput) - })) + Ok(( + String::from(res), + match rest { + Some(s) => s, + None => return Err(ParseErr::UnexpectedEndOfInput), + }, + )) } /** Parse a `param_value`... a.k.a. a quoted string */ @@ -218,21 +212,23 @@ fn parse_num(s: &str, min_digits: usize, max_digits: usize) -> ParseResult<(i32, } else if res.len() > max_digits { Err(ParseErr::TooManyDigits) } else { - Ok((i32::from_str(res).map_err(ParseErr::IntConversionErr)?, rest)) + Ok(( + i32::from_str(res).map_err(ParseErr::IntConversionErr)?, + rest, + )) } } fn parse_decimal(d: &str, min_digits: usize, max_digits: usize) -> ParseResult<(i32, &str)> { - parse_num(d, min_digits, max_digits) - .map(|(val, s)| { - let mut multiplicand = 1; - let z = 10 - (d.len() - s.len()); + parse_num(d, min_digits, max_digits).map(|(val, s)| { + let mut multiplicand = 1; + let z = 10 - (d.len() - s.len()); - for _i in 1..(z) { - multiplicand *= 10; - } - (val * multiplicand, s) - }) + for _i in 1..(z) { + multiplicand *= 10; + } + (val * multiplicand, s) + }) } fn parse_timestamp(m: &str) -> ParseResult<(Option, &str)> { @@ -283,10 +279,11 @@ fn parse_timestamp(m: &str) -> ParseResult<(Option, &str)> { Ok((Some(tm.to_utc().to_timespec()), rest)) } -fn parse_term(m: &str, - min_length: usize, - max_length: usize) - -> ParseResult<(Option, &str)> { +fn parse_term( + m: &str, + min_length: usize, + max_length: usize, +) -> ParseResult<(Option, &str)> { if m.starts_with('-') && (m.len() <= 1 || m.as_bytes()[1] == 0x20) { return Ok((None, &m[1..])); } @@ -307,7 +304,6 @@ fn parse_term(m: &str, Err(ParseErr::UnexpectedEndOfInput) } - fn parse_message_s(m: &str) -> ParseResult { let mut rest = m; take_char!(rest, '<'); @@ -325,12 +321,10 @@ fn parse_message_s(m: &str) -> ParseResult { let procid_r = take_item!(parse_term(rest, 1, 128), rest); let procid = match procid_r { None => None, - Some(s) => { - Some(match i32::from_str(&s) { - Ok(n) => ProcId::PID(n), - Err(_) => ProcId::Name(s), - }) - } + Some(s) => Some(match i32::from_str(&s) { + Ok(n) => ProcId::PID(n), + Err(_) => ProcId::Name(s), + }), }; take_char!(rest, ' '); let msgid = take_item!(parse_term(rest, 1, 32), rest); @@ -343,22 +337,20 @@ fn parse_message_s(m: &str) -> ParseResult { let msg = String::from(rest); Ok(SyslogMessage { - severity: sev, - facility: fac, - version, - timestamp: event_time.map(|t| t.sec), - timestamp_nanos: event_time.map(|t| t.nsec), - hostname, - appname, - procid, - msgid, - sd, - msg, - }) + severity: sev, + facility: fac, + version, + timestamp: event_time.map(|t| t.sec), + timestamp_nanos: event_time.map(|t| t.nsec), + hostname, + appname, + procid, + msgid, + sd, + msg, + }) } - - /// Parse a string into a `SyslogMessage` object /// /// # Arguments @@ -382,17 +374,16 @@ pub fn parse_message>(s: S) -> ParseResult { parse_message_s(s.as_ref()) } - #[cfg(test)] mod tests { use std::collections::BTreeMap; use std::mem; use super::{parse_message, ParseErr}; - use message; + use crate::message; - use facility::SyslogFacility; - use severity::SyslogSeverity; + use crate::facility::SyslogFacility; + use crate::severity::SyslogSeverity; #[test] fn test_simple() { @@ -409,27 +400,34 @@ mod tests { #[test] fn test_with_time_zulu() { - let msg = parse_message("<1>1 2015-01-01T00:00:00Z host - - - -").expect("Should parse empty message"); + let msg = parse_message("<1>1 2015-01-01T00:00:00Z host - - - -") + .expect("Should parse empty message"); assert_eq!(msg.timestamp, Some(1420070400)); } #[test] fn test_with_time_offset() { - let msg = parse_message("<1>1 2015-01-01T00:00:00+00:00 - - - - -").expect("Should parse empty message"); + let msg = parse_message("<1>1 2015-01-01T00:00:00+00:00 - - - - -") + .expect("Should parse empty message"); assert_eq!(msg.timestamp, Some(1420070400)); } #[test] fn test_with_time_offset_nonzero() { - let msg = parse_message("<1>1 2015-01-01T00:00:00-10:00 - - - - -").expect("Should parse empty message"); + let msg = parse_message("<1>1 2015-01-01T00:00:00-10:00 - - - - -") + .expect("Should parse empty message"); assert_eq!(msg.timestamp, Some(1420106400)); // example from RFC 3339 - let msg1 = parse_message("<1>1 2015-01-01T18:50:00-04:00 - - - - -").expect("Should parse empty message"); - let msg2 = parse_message("<1>1 2015-01-01T22:50:00Z - - - - -").expect("Should parse empty message"); + let msg1 = parse_message("<1>1 2015-01-01T18:50:00-04:00 - - - - -") + .expect("Should parse empty message"); + let msg2 = parse_message("<1>1 2015-01-01T22:50:00Z - - - - -") + .expect("Should parse empty message"); assert_eq!(msg1.timestamp, msg2.timestamp); // example with fractional minutes - let msg1 = parse_message("<1>1 2019-01-20T00:46:39+05:45 - - - - -").expect("Should parse empty message"); - let msg2 = parse_message("<1>1 2019-01-19T11:01:39-08:00 - - - - -").expect("Should parse empty message"); + let msg1 = parse_message("<1>1 2019-01-20T00:46:39+05:45 - - - - -") + .expect("Should parse empty message"); + let msg2 = parse_message("<1>1 2019-01-19T11:01:39-08:00 - - - - -") + .expect("Should parse empty message"); assert_eq!(msg1.timestamp, msg2.timestamp); } @@ -444,7 +442,10 @@ mod tests { assert_eq!(msg.msg, String::from("some_message")); assert_eq!(msg.timestamp, Some(1452816241)); assert_eq!(msg.sd.len(), 1); - let v = msg.sd.find_tuple("meta", "sequenceId").expect("Should contain meta sequenceId"); + let v = msg + .sd + .find_tuple("meta", "sequenceId") + .expect("Should contain meta sequenceId"); assert_eq!(v, "29"); } @@ -459,19 +460,22 @@ mod tests { assert_eq!(msg.msg, String::from("some_message")); assert_eq!(msg.timestamp, Some(1452816241)); assert_eq!(msg.sd.len(), 2); - assert_eq!(msg.sd - .find_sdid("meta") - .expect("should contain meta") - .len(), - 3); + assert_eq!( + msg.sd.find_sdid("meta").expect("should contain meta").len(), + 3 + ); } #[test] fn test_sd_with_escaped_quote() { let msg_text = r#"<1>1 - - - - - [meta key="val\"ue"] message"#; let msg = parse_message(msg_text).expect("should parse"); - assert_eq!(msg.sd.find_tuple("meta", "key").expect("Should contain meta key"), - r#"val"ue"#); + assert_eq!( + msg.sd + .find_tuple("meta", "key") + .expect("Should contain meta key"), + r#"val"ue"# + ); } #[test] @@ -533,14 +537,18 @@ mod tests { assert_eq!(msg.timestamp, Some(1526286181)); assert_eq!(msg.timestamp_nanos, Some(520000000)); assert_eq!(msg.sd.len(), 1); - let sd = msg.sd.find_sdid("junos@2636.1.1.1.2.57").expect("should contain root SD"); + let sd = msg + .sd + .find_sdid("junos@2636.1.1.1.2.57") + .expect("should contain root SD"); let expected = { let mut expected = BTreeMap::new(); expected.insert("pid", "14374"); expected.insert("return-value", "5"); expected.insert("core-dump-status", ""); expected.insert("command", "/usr/sbin/mustd"); - expected.into_iter() + expected + .into_iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect::>() }; @@ -551,14 +559,27 @@ mod tests { fn test_fields_start_with_dash() { let msg = parse_message("<39>1 2018-05-15T20:56:58+00:00 -web1west -201805020050-bc5d6a47c3-master - - [meta sequenceId=\"28485532\"] 25450-uWSGI worker 6: getaddrinfo*.gaih_getanswer: got type \"DNAME\"").expect("should parse"); assert_eq!(msg.hostname, Some("-web1west".to_string())); - assert_eq!(msg.appname, Some("-201805020050-bc5d6a47c3-master".to_string())); - assert_eq!(msg.sd.find_tuple("meta", "sequenceId"), Some(&"28485532".to_string())); - assert_eq!(msg.msg, "25450-uWSGI worker 6: getaddrinfo*.gaih_getanswer: got type \"DNAME\"".to_string()); + assert_eq!( + msg.appname, + Some("-201805020050-bc5d6a47c3-master".to_string()) + ); + assert_eq!( + msg.sd.find_tuple("meta", "sequenceId"), + Some(&"28485532".to_string()) + ); + assert_eq!( + msg.msg, + "25450-uWSGI worker 6: getaddrinfo*.gaih_getanswer: got type \"DNAME\"".to_string() + ); } #[test] fn test_truncated() { - let err = parse_message("<39>1 2018-05-15T20:56:58+00:00 -web1west -").expect_err("should fail"); - assert_eq!(mem::discriminant(&err), mem::discriminant(&ParseErr::UnexpectedEndOfInput)); + let err = + parse_message("<39>1 2018-05-15T20:56:58+00:00 -web1west -").expect_err("should fail"); + assert_eq!( + mem::discriminant(&err), + mem::discriminant(&ParseErr::UnexpectedEndOfInput) + ); } } diff --git a/src/severity.rs b/src/severity.rs index f662209..45f21b2 100644 --- a/src/severity.rs +++ b/src/severity.rs @@ -1,7 +1,11 @@ -#[cfg(feature="serde-serialize")] -use serde::{Serializer, Serialize}; +use std::convert::TryFrom; -#[derive(Copy,Clone,Debug,PartialEq,Eq,PartialOrd,Ord)] +#[cfg(feature = "serde-serialize")] +use serde::{Serialize, Serializer}; + +use thiserror::Error; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[allow(non_camel_case_types)] /// Syslog Severities from RFC 5424. pub enum SyslogSeverity { @@ -15,23 +19,38 @@ pub enum SyslogSeverity { SEV_DEBUG = 7, } +#[derive(Debug, Error)] +pub enum SyslogSeverityError { + #[error("integer does not correspond to a known severity")] + InvalidInteger, +} + +impl TryFrom for SyslogSeverity { + type Error = SyslogSeverityError; + + #[inline(always)] + fn try_from(i: i32) -> Result { + Ok(match i { + 0 => SyslogSeverity::SEV_EMERG, + 1 => SyslogSeverity::SEV_ALERT, + 2 => SyslogSeverity::SEV_CRIT, + 3 => SyslogSeverity::SEV_ERR, + 4 => SyslogSeverity::SEV_WARNING, + 5 => SyslogSeverity::SEV_NOTICE, + 6 => SyslogSeverity::SEV_INFO, + 7 => SyslogSeverity::SEV_DEBUG, + _ => return Err(SyslogSeverityError::InvalidInteger), + }) + } +} + impl SyslogSeverity { /// Convert an int (as used in the wire serialization) into a `SyslogSeverity` /// /// Returns an Option, but the wire protocol will only include 0..7, so should /// never return None in practical usage. pub(crate) fn from_int(i: i32) -> Option { - match i { - 0 => Some(SyslogSeverity::SEV_EMERG), - 1 => Some(SyslogSeverity::SEV_ALERT), - 2 => Some(SyslogSeverity::SEV_CRIT), - 3 => Some(SyslogSeverity::SEV_ERR), - 4 => Some(SyslogSeverity::SEV_WARNING), - 5 => Some(SyslogSeverity::SEV_NOTICE), - 6 => Some(SyslogSeverity::SEV_INFO), - 7 => Some(SyslogSeverity::SEV_DEBUG), - _ => None, - } + Self::try_from(i).ok() } /// Convert a syslog severity into a unique string representation @@ -44,13 +63,11 @@ impl SyslogSeverity { SyslogSeverity::SEV_WARNING => "warning", SyslogSeverity::SEV_NOTICE => "notice", SyslogSeverity::SEV_INFO => "info", - SyslogSeverity::SEV_DEBUG => "debug" + SyslogSeverity::SEV_DEBUG => "debug", } } } - - #[cfg(feature = "serde-serialize")] impl Serialize for SyslogSeverity { fn serialize(&self, ser: S) -> Result { @@ -58,7 +75,6 @@ impl Serialize for SyslogSeverity { } } - #[cfg(test)] mod tests { use super::SyslogSeverity;