mod cbor;
mod color_compact;
mod color_full;
mod compact;
mod full;
mod json;
use std::io;
use crate::TimeFormat;
use crate::constant::{ATTRIBUTE_KEY_TIME, ATTRIBUTE_KEY_TIMESTAMP};
use crate::sink::LogUpdate;
#[derive(Clone, Debug, PartialEq)]
pub enum OutputFormat {
Compact,
ColorCompact,
Full,
ColorFull,
Json,
Cbor,
}
pub const ALL_OUTPUT_FORMATS: [OutputFormat; 6] = [
OutputFormat::Compact,
OutputFormat::ColorCompact,
OutputFormat::Full,
OutputFormat::ColorFull,
OutputFormat::Json,
OutputFormat::Cbor,
];
#[derive(Clone, Debug)]
pub enum FormatterError {
DelimiterNotAString,
}
impl OutputFormat {
pub fn as_short_str(&self) -> &str {
match self {
Self::Compact => "compact",
Self::ColorCompact => "color_compact",
Self::Full => "full",
Self::ColorFull => "color_full",
Self::Json => "json",
Self::Cbor => "cbor",
}
.into()
}
pub fn as_str(&self) -> &str {
match self {
Self::Compact => "compact",
Self::ColorCompact => "compact (w/console color)",
Self::Full => "full",
Self::ColorFull => "full (w/console color)",
Self::Json => "JSON",
Self::Cbor => "CBOR",
}
.into()
}
}
impl TryFrom<&str> for OutputFormat {
type Error = &'static str;
fn try_from(name: &str) -> Result<Self, <OutputFormat as TryFrom<&str>>::Error> {
for of in ALL_OUTPUT_FORMATS {
if name.eq_ignore_ascii_case(of.as_short_str()) {
return Ok(of);
}
}
Err("invalid OutputFormat name")
}
}
#[derive(Clone, Debug)]
pub struct FormatterConfig {
pub format: OutputFormat,
pub time_format: TimeFormat,
pub delimiter: Vec<u8>,
}
impl FormatterConfig {
pub fn default() -> Self {
compact::default_format_config()
}
pub fn default_compact() -> Self {
compact::default_format_config()
}
pub fn default_color_compact() -> Self {
color_compact::default_format_config()
}
pub fn default_full() -> Self {
full::default_format_config()
}
pub fn default_color_full() -> Self {
color_full::default_format_config()
}
pub fn default_json() -> Self {
json::default_format_config()
}
pub fn default_cbor() -> Self {
cbor::default_format_config()
}
}
#[derive(Clone, Debug)]
pub struct Formatter {
format: OutputFormat,
time_key: String,
time_format: TimeFormat,
delimiter: Vec<u8>,
work_buffer: Vec<u8>,
}
impl Formatter {
pub fn new(conf: FormatterConfig) -> Self {
Self {
format: conf.format,
time_key: match &conf.time_format.is_integer() {
true => String::from(ATTRIBUTE_KEY_TIMESTAMP),
false => String::from(ATTRIBUTE_KEY_TIME),
},
time_format: conf.time_format,
delimiter: conf.delimiter,
work_buffer: Vec::new(),
}
}
pub fn write<T: io::Write>(&mut self, out: &mut T, update: &LogUpdate) -> io::Result<()> {
match self.format {
OutputFormat::Compact => compact::write(out, &self.time_format, update),
OutputFormat::ColorCompact => color_compact::write(out, &self.time_format, update),
OutputFormat::Full => full::write(out, &self.delimiter, &self.time_format, update),
OutputFormat::ColorFull => color_full::write(out, &self.delimiter, &self.time_format, update),
OutputFormat::Json => json::write(out, &self.time_format, &self.time_key, update),
OutputFormat::Cbor => cbor::write(out, &mut self.work_buffer, &self.time_format, &self.time_key, update),
}
}
pub fn as_bytes(&mut self, update: &LogUpdate) -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
match self.write(&mut out, update) {
Ok(_) => out,
Err(e) => panic!("failed to convert log update {update:?} to bytes buffer: {e}"),
}
}
pub fn as_string(&mut self, update: &LogUpdate) -> String {
let bytes = self.as_bytes(update);
match String::from_utf8(bytes) {
Ok(s) => s,
Err(e) => panic!("failed to convert log update {update:?} to UTF8: {e}"),
}
}
pub fn delimiter(&self) -> &[u8] {
self.delimiter.as_slice()
}
pub fn delimiter_as_string(&self) -> Result<String, FormatterError> {
match String::from_utf8(self.delimiter.clone()) {
Ok(s) => Ok(s),
Err(_) => Err(FormatterError::DelimiterNotAString),
}
}
pub fn write_delimiter<T: io::Write>(&self, out: &mut T) -> io::Result<()> {
match out.write(self.delimiter.as_slice()) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
}
pub fn as_panic_string(update: &LogUpdate) -> String {
let mut formatter = Formatter::new(FormatterConfig {
format: OutputFormat::Compact,
..FormatterConfig::default_compact()
});
formatter.as_string(update)
}
#[cfg(test)]
mod from {
use super::*;
#[test]
fn from_name() {
assert_eq!(OutputFormat::try_from(""), Err("invalid OutputFormat name"));
assert_eq!(OutputFormat::try_from("boo"), Err("invalid OutputFormat name"));
assert_eq!(OutputFormat::try_from("compact"), Ok(OutputFormat::Compact));
assert_eq!(OutputFormat::try_from("COLOR_COMPACT"), Ok(OutputFormat::ColorCompact));
assert_eq!(OutputFormat::try_from("fUlL"), Ok(OutputFormat::Full));
assert_eq!(OutputFormat::try_from("cOlOr_fUlL"), Ok(OutputFormat::ColorFull));
assert_eq!(OutputFormat::try_from("json"), Ok(OutputFormat::Json));
assert_eq!(OutputFormat::try_from("CBOR"), Ok(OutputFormat::Cbor));
}
}