diff --git a/CHANGELOG.md b/CHANGELOG.md index 2085aaf4..7779ce2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - context: add `Context::try_send`. - errors: add `TrySendError::map` and `RequestError::map`. - dumping: write `ts` firstly to support the `sort` utility. +- dumper: extract a dump's name if it's not specified. ### Fixed - init: do not start termination if the memory tracker fails to read files. diff --git a/elfo-dumper/src/extract_name.rs b/elfo-dumper/src/extract_name.rs new file mode 100644 index 00000000..8f1e920b --- /dev/null +++ b/elfo-dumper/src/extract_name.rs @@ -0,0 +1,319 @@ +use std::fmt; + +use serde::{ser, Serialize, Serializer}; + +// === Outcome === + +#[derive(Debug)] +enum Outcome { + Done, + Inapplicable, + Error(String), +} + +impl fmt::Display for Outcome { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + Ok(()) + } +} + +impl std::error::Error for Outcome {} + +impl ser::Error for Outcome { + fn custom(msg: T) -> Self { + Self::Error(msg.to_string()) + } +} + +// === NameExtractor === + +type Result = std::result::Result; +type Impossible = ser::Impossible<(), Outcome>; + +struct NameExtractor<'a>(&'a mut String); + +impl<'a> NameExtractor<'a> { + fn struct_name(&mut self, name: &str) { + self.0.push_str(name); + } + + fn variant_name(&mut self, enum_name: &str, variant_name: &str) { + self.0.push_str(enum_name); + self.0.push_str("::"); + self.0.push_str(variant_name); + } +} + +macro_rules! inapplicable { + ($ser_method:ident($ty:ty) -> $ret:ty, $($other:tt)*) => { + #[inline] + fn $ser_method(self, _v: $ty) -> $ret { + Err(Outcome::Inapplicable) + } + inapplicable!($($other)*); + }; + ($ser_method:ident($ty:ty), $($other:tt)*) => { + inapplicable!($ser_method($ty) -> Result, $($other)*); + }; + ($ser_method:ident, $($other:tt)*) => { + #[inline] + fn $ser_method(self) -> Result { + Err(Outcome::Inapplicable) + } + inapplicable!($($other)*); + }; + () => {}; +} + +impl<'a> Serializer for NameExtractor<'a> { + type Error = Outcome; + type Ok = (); + type SerializeMap = Impossible; + type SerializeSeq = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; + type SerializeTuple = Impossible; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + + inapplicable!( + serialize_i8(i8), + serialize_i16(i16), + serialize_i32(i32), + serialize_i64(i64), + serialize_i128(i128), + serialize_u8(u8), + serialize_u16(u16), + serialize_u32(u32), + serialize_u64(u64), + serialize_u128(u128), + serialize_f32(f32), + serialize_f64(f64), + serialize_bool(bool), + serialize_char(char), + serialize_str(&str), + serialize_bytes(&[u8]), + serialize_none, + serialize_unit, + serialize_seq(Option) -> Result, + serialize_tuple(usize) -> Result, + serialize_map(Option) -> Result, + ); + + #[inline] + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + #[inline] + fn serialize_unit_struct(mut self, name: &'static str) -> Result { + self.struct_name(name); + Err(Outcome::Done) + } + + #[inline] + fn serialize_unit_variant( + mut self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.variant_name(name, variant); + Err(Outcome::Done) + } + + #[inline] + fn serialize_newtype_struct( + mut self, + name: &'static str, + _value: &T, + ) -> Result { + self.struct_name(name); + Err(Outcome::Done) + } + + #[inline] + fn serialize_newtype_variant( + mut self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _value: &T, + ) -> Result { + self.variant_name(name, variant); + Err(Outcome::Done) + } + + #[inline] + fn serialize_tuple_struct(mut self, name: &'static str, _len: usize) -> Result { + self.struct_name(name); + Err(Outcome::Done) + } + + #[inline] + fn serialize_tuple_variant( + mut self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + self.variant_name(name, variant); + Err(Outcome::Done) + } + + #[inline] + fn serialize_struct( + mut self, + name: &'static str, + _len: usize, + ) -> Result { + self.struct_name(name); + Err(Outcome::Done) + } + + #[inline] + fn serialize_struct_variant( + mut self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + self.variant_name(name, variant); + Err(Outcome::Done) + } + + #[inline] + fn is_human_readable(&self) -> bool { + true + } +} + +/// Extract a name from the provided `Serialize` instance. +/// +/// Returns +/// * `Ok(true)` if the name is extracted successfully. +/// * `Ok(false)` if the name cannot be extracted. +/// * `Err(err)` if a custom error occurs. +pub(crate) fn extract_name_into(s: &mut String, value: &impl Serialize) -> Result { + match value.serialize(NameExtractor(s)).unwrap_err() { + Outcome::Done => Ok(true), + Outcome::Inapplicable => Ok(false), + Outcome::Error(err) => Err(err), + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use serde::ser::Error as _; + + use super::*; + + #[test] + fn struct_() { + #[derive(Serialize)] + struct Struct { + n: u8, + } + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &Struct { n: 42 }), Ok(true)); + assert_eq!(s, "Struct"); + } + + #[test] + fn unit_struct() { + #[derive(Serialize)] + struct UnitStruct; + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &UnitStruct), Ok(true)); + assert_eq!(s, "UnitStruct"); + } + + #[test] + fn newtype_struct() { + #[derive(Serialize)] + struct NewtypeStruct(u8); + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &NewtypeStruct(42)), Ok(true)); + assert_eq!(s, "NewtypeStruct"); + } + + #[test] + fn tuple_struct() { + #[derive(Serialize)] + struct TupleStruct(u8, u8); + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &TupleStruct(42, 42)), Ok(true)); + assert_eq!(s, "TupleStruct"); + } + + #[test] + fn enum_() { + #[derive(Serialize)] + enum Enum { + A(u8), + B { n: u8 }, + C(u8, u8), + D, + } + + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &Enum::A(42)), Ok(true)); + assert_eq!(s, "Enum::A"); + + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &Enum::B { n: 42 }), Ok(true)); + assert_eq!(s, "Enum::B"); + + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &Enum::C(42, 42)), Ok(true)); + assert_eq!(s, "Enum::C"); + + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &Enum::D), Ok(true)); + assert_eq!(s, "Enum::D"); + } + + #[test] + fn some() { + #[derive(Serialize)] + struct Struct; + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &Some(Struct)), Ok(true)); + assert_eq!(s, "Struct"); + } + + #[test] + fn inapplicable() { + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &42u8), Ok(false)); + assert_eq!(extract_name_into(&mut s, &42f64), Ok(false)); + assert_eq!(extract_name_into(&mut s, &"foobar"), Ok(false)); + assert_eq!(extract_name_into(&mut s, &[42]), Ok(false)); + assert_eq!(extract_name_into(&mut s, &None::), Ok(false)); + assert_eq!( + extract_name_into(&mut s, &HashMap::::default()), + Ok(false) + ); + assert_eq!(extract_name_into(&mut s, &()), Ok(false)); + assert_eq!(extract_name_into(&mut s, &(42, 42)), Ok(false)); + assert!(s.is_empty()); + } + + #[test] + fn custom_error() { + struct Foo; + impl Serialize for Foo { + fn serialize(&self, _serializer: S) -> Result { + Err(S::Error::custom("oops")) + } + } + + let mut s = String::new(); + assert_eq!(extract_name_into(&mut s, &Foo), Err("oops".to_string())); + assert!(s.is_empty()); + } +} diff --git a/elfo-dumper/src/lib.rs b/elfo-dumper/src/lib.rs index 4a484e1b..7983f871 100644 --- a/elfo-dumper/src/lib.rs +++ b/elfo-dumper/src/lib.rs @@ -14,6 +14,7 @@ use self::storage::Storage; mod actor; mod config; +mod extract_name; mod recorder; mod storage;