use core::{fmt, str::FromStr};
use emit_core::{
event::ToEvent,
filter::Filter,
props::Props,
value::{FromValue, ToValue, Value},
well_known::{EVENT_KIND_METRIC, EVENT_KIND_SPAN, KEY_EVT_KIND},
};
#[non_exhaustive]
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub enum Kind {
Span,
Metric,
}
impl Kind {
pub fn try_from_str(s: &str) -> Result<Self, ParseKindError> {
s.parse()
}
pub fn as_str(&self) -> &'static str {
match self {
Kind::Span => EVENT_KIND_SPAN,
Kind::Metric => EVENT_KIND_METRIC,
}
}
}
impl fmt::Debug for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"{}\"", self)
}
}
impl fmt::Display for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(feature = "sval")]
impl sval::Value for Kind {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
stream.value(self.as_str())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Kind {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_str().serialize(serializer)
}
}
impl ToValue for Kind {
fn to_value(&self) -> Value<'_> {
Value::capture_display(self)
}
}
impl<'v> FromValue<'v> for Kind {
fn from_value(value: Value<'v>) -> Option<Self> {
value
.downcast_ref::<Kind>()
.copied()
.or_else(|| value.parse())
}
}
impl FromStr for Kind {
type Err = ParseKindError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
if s.eq_ignore_ascii_case(EVENT_KIND_SPAN) {
return Ok(Kind::Span);
}
if s.eq_ignore_ascii_case(EVENT_KIND_METRIC) {
return Ok(Kind::Metric);
}
Err(ParseKindError {})
}
}
#[derive(Debug)]
pub struct ParseKindError {}
impl fmt::Display for ParseKindError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "the input was not a valid kind")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseKindError {}
#[derive(Debug)]
pub struct KindFilter(Kind);
impl KindFilter {
pub fn new(kind: Kind) -> Self {
KindFilter(kind)
}
}
pub fn is_span_filter() -> KindFilter {
KindFilter::new(Kind::Span)
}
pub fn is_metric_filter() -> KindFilter {
KindFilter::new(Kind::Metric)
}
impl Filter for KindFilter {
fn matches<E: ToEvent>(&self, evt: E) -> bool {
evt.to_event().props().pull::<Kind, _>(KEY_EVT_KIND) == Some(self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse() {
for (case, expected) in [
("span", Ok::<Kind, ParseKindError>(Kind::Span)),
("metric", Ok(Kind::Metric)),
("spanx", Err(ParseKindError {})),
("sp", Err(ParseKindError {})),
("met", Err(ParseKindError {})),
("", Err(ParseKindError {})),
("span span", Err(ParseKindError {})),
] {
match expected {
Ok(expected) => {
assert_eq!(expected, Kind::from_str(case).unwrap());
assert_eq!(expected, Kind::from_str(&case.to_uppercase()).unwrap());
assert_eq!(expected, Kind::from_str(&format!(" {case} ")).unwrap());
}
Err(expected) => assert_eq!(
expected.to_string(),
Kind::from_str(case).unwrap_err().to_string()
),
}
}
}
#[test]
fn roundtrip() {
for case in [Kind::Span, Kind::Metric] {
assert_eq!(case, Kind::from_str(&case.to_string()).unwrap());
}
}
#[test]
fn to_from_value() {
for case in [Kind::Span, Kind::Metric] {
let value = case.to_value();
assert_eq!(case, value.cast::<Kind>().unwrap());
let formatted = case.to_string();
let value = Value::from(&*formatted);
assert_eq!(case, value.cast::<Kind>().unwrap());
}
}
#[test]
fn kind_filter() {
let filter = KindFilter::new(Kind::Span);
assert!(filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_EVT_KIND, EVENT_KIND_SPAN),
)));
assert!(!filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
(KEY_EVT_KIND, EVENT_KIND_METRIC),
)));
assert!(!filter.matches(crate::Event::new(
crate::Path::new_raw("test"),
crate::Template::literal("test"),
crate::Empty,
crate::Empty,
)));
}
#[cfg(feature = "sval")]
#[test]
fn kind_stream() {
sval_test::assert_tokens(
&Kind::Span,
&[
sval_test::Token::TextBegin(Some(4)),
sval_test::Token::TextFragment("span"),
sval_test::Token::TextEnd,
],
);
}
#[cfg(feature = "serde")]
#[test]
fn kind_serialize() {
serde_test::assert_ser_tokens(&Kind::Span, &[serde_test::Token::Str("span")]);
}
}