diff --git a/.cargo/config.toml b/.cargo/config.toml index 18a3cb3183..751dcc6a23 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,7 @@ +[build] +# https://github.com/open-telemetry/opentelemetry-rust/issues/2819 +rustflags = ["--cfg", "tracing_unstable"] + [resolver] # https://doc.rust-lang.org/cargo/reference/config.html#resolverincompatible-rust-versions incompatible-rust-versions = "fallback" diff --git a/Cargo.toml b/Cargo.toml index b9bab5d982..855563c816 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ rust_2024_compatibility = { level = "warn", priority = -1 } edition_2024_expr_fragment_specifier = "allow" if_let_rescope = "allow" tail_expr_drop_order = "allow" +unexpected_cfgs = { level = "warn", check-cfg = ["cfg(tracing_unstable)"] } [workspace.lints.clippy] all = { level = "warn", priority = 1 } diff --git a/opentelemetry-appender-log/Cargo.toml b/opentelemetry-appender-log/Cargo.toml index 25ace0a787..6c777ebdea 100644 --- a/opentelemetry-appender-log/Cargo.toml +++ b/opentelemetry-appender-log/Cargo.toml @@ -26,7 +26,7 @@ opentelemetry-semantic-conventions = { version = "0.30", path = "../opentelemetr [features] spec_unstable_logs_enabled = ["opentelemetry/spec_unstable_logs_enabled"] -with-serde = ["log/kv_serde", "serde"] +with-serde = ["log/kv_serde", "opentelemetry/with-serde", "serde"] experimental_metadata_attributes = ["dep:opentelemetry-semantic-conventions"] [dev-dependencies] diff --git a/opentelemetry-appender-log/src/lib.rs b/opentelemetry-appender-log/src/lib.rs index e12f20b44e..10a3d930f3 100644 --- a/opentelemetry-appender-log/src/lib.rs +++ b/opentelemetry-appender-log/src/lib.rs @@ -111,6 +111,8 @@ //! long as doing so complies with this policy. use log::{Level, Metadata, Record}; +#[cfg(feature = "with-serde")] +use opentelemetry::logs::serialize; use opentelemetry::{ logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity}, Key, @@ -222,6 +224,12 @@ fn log_attributes(kvs: impl log::kv::Source) -> Vec<(Key, AnyValue)> { ) -> Result<(), log::kv::Error> { let key = Key::from(String::from(key.as_str())); + #[cfg(feature = "with-serde")] + if let Some(value) = serialize(value) { + self.0.push((key, value)); + } + + #[cfg(not(feature = "with-serde"))] if let Some(value) = any_value::serialize(value) { self.0.push((key, value)); } @@ -306,469 +314,6 @@ mod any_value { } } -// This could make a nice addition to the SDK itself for serializing into `AnyValue`s -#[cfg(feature = "with-serde")] -mod any_value { - use std::{collections::HashMap, fmt}; - - use opentelemetry::{logs::AnyValue, Key, StringValue}; - use serde::ser::{ - Error, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, - SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer, StdError, - }; - - /// Serialize an arbitrary `serde::Serialize` into an `AnyValue`. - /// - /// This method performs the following translations when converting between `serde`'s data model and OpenTelemetry's: - /// - /// - Integers that don't fit in a `i64` are converted into strings. - /// - Unit types and nones are discarded (effectively treated as undefined). - /// - Struct and tuple variants are converted into an internally tagged map. - /// - Unit variants are converted into strings. - pub(crate) fn serialize(value: impl serde::Serialize) -> Option { - value.serialize(ValueSerializer).ok()? - } - - struct ValueSerializer; - - struct ValueSerializeSeq { - value: Vec, - } - - struct ValueSerializeTuple { - value: Vec, - } - - struct ValueSerializeTupleStruct { - value: Vec, - } - - struct ValueSerializeMap { - key: Option, - value: HashMap, - } - - struct ValueSerializeStruct { - value: HashMap, - } - - struct ValueSerializeTupleVariant { - variant: &'static str, - value: Vec, - } - - struct ValueSerializeStructVariant { - variant: &'static str, - value: HashMap, - } - - #[derive(Debug)] - struct ValueError(String); - - impl fmt::Display for ValueError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } - } - - impl Error for ValueError { - fn custom(msg: T) -> Self - where - T: fmt::Display, - { - ValueError(msg.to_string()) - } - } - - impl StdError for ValueError {} - - impl Serializer for ValueSerializer { - type Ok = Option; - - type Error = ValueError; - - type SerializeSeq = ValueSerializeSeq; - - type SerializeTuple = ValueSerializeTuple; - - type SerializeTupleStruct = ValueSerializeTupleStruct; - - type SerializeTupleVariant = ValueSerializeTupleVariant; - - type SerializeMap = ValueSerializeMap; - - type SerializeStruct = ValueSerializeStruct; - - type SerializeStructVariant = ValueSerializeStructVariant; - - fn serialize_bool(self, v: bool) -> Result { - Ok(Some(AnyValue::Boolean(v))) - } - - fn serialize_i8(self, v: i8) -> Result { - self.serialize_i64(v as i64) - } - - fn serialize_i16(self, v: i16) -> Result { - self.serialize_i64(v as i64) - } - - fn serialize_i32(self, v: i32) -> Result { - self.serialize_i64(v as i64) - } - - fn serialize_i64(self, v: i64) -> Result { - Ok(Some(AnyValue::Int(v))) - } - - fn serialize_i128(self, v: i128) -> Result { - if let Ok(v) = v.try_into() { - self.serialize_i64(v) - } else { - self.collect_str(&v) - } - } - - fn serialize_u8(self, v: u8) -> Result { - self.serialize_i64(v as i64) - } - - fn serialize_u16(self, v: u16) -> Result { - self.serialize_i64(v as i64) - } - - fn serialize_u32(self, v: u32) -> Result { - self.serialize_i64(v as i64) - } - - fn serialize_u64(self, v: u64) -> Result { - if let Ok(v) = v.try_into() { - self.serialize_i64(v) - } else { - self.collect_str(&v) - } - } - - fn serialize_u128(self, v: u128) -> Result { - if let Ok(v) = v.try_into() { - self.serialize_i64(v) - } else { - self.collect_str(&v) - } - } - - fn serialize_f32(self, v: f32) -> Result { - self.serialize_f64(v as f64) - } - - fn serialize_f64(self, v: f64) -> Result { - Ok(Some(AnyValue::Double(v))) - } - - fn serialize_char(self, v: char) -> Result { - self.collect_str(&v) - } - - fn serialize_str(self, v: &str) -> Result { - Ok(Some(AnyValue::String(StringValue::from(v.to_owned())))) - } - - fn serialize_bytes(self, v: &[u8]) -> Result { - Ok(Some(AnyValue::Bytes(Box::new(v.to_owned())))) - } - - fn serialize_none(self) -> Result { - Ok(None) - } - - fn serialize_some( - self, - value: &T, - ) -> Result { - value.serialize(self) - } - - fn serialize_unit(self) -> Result { - Ok(None) - } - - fn serialize_unit_struct(self, name: &'static str) -> Result { - name.serialize(self) - } - - fn serialize_unit_variant( - self, - _: &'static str, - _: u32, - variant: &'static str, - ) -> Result { - variant.serialize(self) - } - - fn serialize_newtype_struct( - self, - _: &'static str, - value: &T, - ) -> Result { - value.serialize(self) - } - - fn serialize_newtype_variant( - self, - _: &'static str, - _: u32, - variant: &'static str, - value: &T, - ) -> Result { - let mut map = self.serialize_map(Some(1))?; - map.serialize_entry(variant, value)?; - map.end() - } - - fn serialize_seq(self, _: Option) -> Result { - Ok(ValueSerializeSeq { value: Vec::new() }) - } - - fn serialize_tuple(self, _: usize) -> Result { - Ok(ValueSerializeTuple { value: Vec::new() }) - } - - fn serialize_tuple_struct( - self, - _: &'static str, - _: usize, - ) -> Result { - Ok(ValueSerializeTupleStruct { value: Vec::new() }) - } - - fn serialize_tuple_variant( - self, - _: &'static str, - _: u32, - variant: &'static str, - _: usize, - ) -> Result { - Ok(ValueSerializeTupleVariant { - variant, - value: Vec::new(), - }) - } - - fn serialize_map(self, _: Option) -> Result { - Ok(ValueSerializeMap { - key: None, - value: HashMap::new(), - }) - } - - fn serialize_struct( - self, - _: &'static str, - _: usize, - ) -> Result { - Ok(ValueSerializeStruct { - value: HashMap::new(), - }) - } - - fn serialize_struct_variant( - self, - _: &'static str, - _: u32, - variant: &'static str, - _: usize, - ) -> Result { - Ok(ValueSerializeStructVariant { - variant, - value: HashMap::new(), - }) - } - } - - impl SerializeSeq for ValueSerializeSeq { - type Ok = Option; - - type Error = ValueError; - - fn serialize_element( - &mut self, - value: &T, - ) -> Result<(), Self::Error> { - if let Some(value) = value.serialize(ValueSerializer)? { - self.value.push(value); - } - - Ok(()) - } - - fn end(self) -> Result { - Ok(Some(AnyValue::ListAny(Box::new(self.value)))) - } - } - - impl SerializeTuple for ValueSerializeTuple { - type Ok = Option; - - type Error = ValueError; - - fn serialize_element( - &mut self, - value: &T, - ) -> Result<(), Self::Error> { - if let Some(value) = value.serialize(ValueSerializer)? { - self.value.push(value); - } - - Ok(()) - } - - fn end(self) -> Result { - Ok(Some(AnyValue::ListAny(Box::new(self.value)))) - } - } - - impl SerializeTupleStruct for ValueSerializeTupleStruct { - type Ok = Option; - - type Error = ValueError; - - fn serialize_field( - &mut self, - value: &T, - ) -> Result<(), Self::Error> { - if let Some(value) = value.serialize(ValueSerializer)? { - self.value.push(value); - } - - Ok(()) - } - - fn end(self) -> Result { - Ok(Some(AnyValue::ListAny(Box::new(self.value)))) - } - } - - impl SerializeTupleVariant for ValueSerializeTupleVariant { - type Ok = Option; - - type Error = ValueError; - - fn serialize_field( - &mut self, - value: &T, - ) -> Result<(), Self::Error> { - if let Some(value) = value.serialize(ValueSerializer)? { - self.value.push(value); - } - - Ok(()) - } - - fn end(self) -> Result { - Ok(Some(AnyValue::Map({ - let mut variant = Box::>::default(); - variant.insert( - Key::from(self.variant), - AnyValue::ListAny(Box::new(self.value)), - ); - variant - }))) - } - } - - impl SerializeMap for ValueSerializeMap { - type Ok = Option; - - type Error = ValueError; - - fn serialize_key( - &mut self, - key: &T, - ) -> Result<(), Self::Error> { - let key = match key.serialize(ValueSerializer)? { - Some(AnyValue::String(key)) => Key::from(String::from(key)), - key => Key::from(format!("{key:?}")), - }; - - self.key = Some(key); - - Ok(()) - } - - fn serialize_value( - &mut self, - value: &T, - ) -> Result<(), Self::Error> { - let key = self - .key - .take() - .ok_or_else(|| Self::Error::custom("missing key"))?; - - if let Some(value) = value.serialize(ValueSerializer)? { - self.value.insert(key, value); - } - - Ok(()) - } - - fn end(self) -> Result { - Ok(Some(AnyValue::Map(Box::new(self.value)))) - } - } - - impl SerializeStruct for ValueSerializeStruct { - type Ok = Option; - - type Error = ValueError; - - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), Self::Error> { - let key = Key::from(key); - - if let Some(value) = value.serialize(ValueSerializer)? { - self.value.insert(key, value); - } - - Ok(()) - } - - fn end(self) -> Result { - Ok(Some(AnyValue::Map(Box::new(self.value)))) - } - } - - impl SerializeStructVariant for ValueSerializeStructVariant { - type Ok = Option; - - type Error = ValueError; - - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), Self::Error> { - let key = Key::from(key); - - if let Some(value) = value.serialize(ValueSerializer)? { - self.value.insert(key, value); - } - - Ok(()) - } - - fn end(self) -> Result { - Ok(Some(AnyValue::Map({ - let mut variant = Box::>::default(); - variant.insert(Key::from(self.variant), AnyValue::Map(Box::new(self.value))); - variant - }))) - } - } -} - #[cfg(test)] mod tests { use super::OpenTelemetryLogBridge; diff --git a/opentelemetry-appender-tracing/CHANGELOG.md b/opentelemetry-appender-tracing/CHANGELOG.md index 1a1df995d5..0f40973367 100644 --- a/opentelemetry-appender-tracing/CHANGELOG.md +++ b/opentelemetry-appender-tracing/CHANGELOG.md @@ -2,6 +2,9 @@ ## vNext +- [#999](https://github.com/open-telemetry/opentelemetry-rust/pull/9999) Support the [experimental `valuable` feature](https://github.com/tokio-rs/tracing/discussions/1906) in tracing. + The feature is named `valuable` and must be enabled with the `tracing_unstable` build configuration. + ## 0.30.1 Released 2025-June-05 diff --git a/opentelemetry-appender-tracing/Cargo.toml b/opentelemetry-appender-tracing/Cargo.toml index bbf753c002..df264c406f 100644 --- a/opentelemetry-appender-tracing/Cargo.toml +++ b/opentelemetry-appender-tracing/Cargo.toml @@ -19,6 +19,8 @@ tracing-core = { workspace = true } tracing-log = { workspace = true, optional = true } tracing-subscriber = { workspace = true, features = ["registry", "std"] } tracing-opentelemetry = { workspace = true, optional = true } +valuable = { version = "0.1", optional = true } +valuable-serde = { version = "0.1", optional = true } [dev-dependencies] log = { workspace = true } @@ -29,6 +31,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter","registry", "s tracing-log = { workspace = true } criterion = { workspace = true } tokio = { workspace = true, features = ["full"]} +valuable = { version = "0.1", features = ["derive"] } [target.'cfg(not(target_os = "windows"))'.dev-dependencies] pprof = { version = "0.14", features = ["flamegraph", "criterion"] } @@ -38,7 +41,7 @@ default = [] experimental_metadata_attributes = ["dep:tracing-log"] spec_unstable_logs_enabled = ["opentelemetry/spec_unstable_logs_enabled"] experimental_use_tracing_span_context = ["tracing-opentelemetry"] - +valuable = ["opentelemetry/with-serde", "tracing-core/valuable", "dep:valuable", "valuable-serde"] [[bench]] name = "logs" diff --git a/opentelemetry-appender-tracing/src/layer.rs b/opentelemetry-appender-tracing/src/layer.rs index fceac59a04..6dd73b99a6 100644 --- a/opentelemetry-appender-tracing/src/layer.rs +++ b/opentelemetry-appender-tracing/src/layer.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "valuable")] +use opentelemetry::logs::serialize; use opentelemetry::{ logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity}, Key, @@ -175,6 +177,17 @@ impl tracing::field::Visit for EventVisitor<'_, LR> { } } + #[cfg(feature = "valuable")] + fn record_value(&mut self, field: &tracing::field::Field, value: valuable::Value<'_>) { + match serialize(valuable_serde::Serializable::new(value)) { + Some(value) => self.log_record.add_attribute(Key::new(field.name()), value), + None => { + self.log_record + .add_attribute(Key::new(field.name()), AnyValue::from(format!("{value:?}"))); + } + } + } + // TODO: Remaining field types from AnyValue : Bytes, ListAny, Boolean } @@ -955,4 +968,53 @@ mod tests { // The validation is done in the LogProcessorWithIsEnabled struct. error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io"); } + + #[cfg(all(tracing_unstable, feature = "valuable"))] + #[test] + fn valuable() { + use std::collections::HashMap; + use valuable::Valuable; + + #[derive(Clone, Debug, Valuable)] + struct User { + name: String, + age: u32, + } + + // Arrange + let exporter: InMemoryLogExporter = InMemoryLogExporter::default(); + let logger_provider = SdkLoggerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + + let subscriber = create_tracing_subscriber(&logger_provider); + + // avoiding setting tracing subscriber as global as that does not + // play well with unit tests. + let _guard = tracing::subscriber::set_default(subscriber); + + let user = User { + name: "Arwen Undomiel".to_string(), + age: 3000, + }; + + error!(name: "my-event-name", target: "my-system", user = user.as_value()); + + let exported_logs = exporter + .get_emitted_logs() + .expect("Logs are expected to be exported."); + assert_eq!(exported_logs.len(), 1); + let log = exported_logs + .first() + .expect("Atleast one log is expected to be present."); + + assert!(attributes_contains( + &log.record, + &Key::new("user"), + &AnyValue::Map(Box::new(HashMap::from([ + (Key::new("name"), AnyValue::String("Arwen Undomiel".into())), + (Key::new("age"), AnyValue::Int(3000)), + ]))) + )); + } } diff --git a/opentelemetry/Cargo.toml b/opentelemetry/Cargo.toml index 49994d0280..6fd204ae68 100644 --- a/opentelemetry/Cargo.toml +++ b/opentelemetry/Cargo.toml @@ -27,6 +27,7 @@ futures-sink = { workspace = true, optional = true } pin-project-lite = { workspace = true, optional = true } thiserror = { workspace = true, optional = true} tracing = {workspace = true, optional = true} # optional for opentelemetry internal logging +serde = { workspace = true, optional = true, features = ["std"] } [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] js-sys = "0.3.63" @@ -40,6 +41,7 @@ testing = ["trace"] logs = [] spec_unstable_logs_enabled = ["logs"] internal-logs = ["tracing"] +with-serde = ["serde"] [dev-dependencies] opentelemetry_sdk = { path = "../opentelemetry-sdk", features = ["spec_unstable_logs_enabled"]} # for documentation tests diff --git a/opentelemetry/allowed-external-types.toml b/opentelemetry/allowed-external-types.toml index 90d58d7e9c..fb25399303 100644 --- a/opentelemetry/allowed-external-types.toml +++ b/opentelemetry/allowed-external-types.toml @@ -6,4 +6,5 @@ allowed_external_types = [ "equivalent::Equivalent", "futures_sink::Sink", # TODO: This is a pre-1.0 crate, we can't easily stabilize with this in the public API "futures_core::stream::Stream", # TODO: This is a pre-1.0 crate, we can't easily stabilize with this in the public API + "serde::ser::Serialize", ] diff --git a/opentelemetry/src/logs/mod.rs b/opentelemetry/src/logs/mod.rs index e57684bc0f..859ce54950 100644 --- a/opentelemetry/src/logs/mod.rs +++ b/opentelemetry/src/logs/mod.rs @@ -9,3 +9,8 @@ mod record; pub use logger::{Logger, LoggerProvider}; pub use noop::NoopLoggerProvider; pub use record::{AnyValue, LogRecord, Severity}; + +#[cfg(feature = "with-serde")] +mod serde; +#[cfg(feature = "with-serde")] +pub use serde::serialize; diff --git a/opentelemetry/src/logs/serde.rs b/opentelemetry/src/logs/serde.rs new file mode 100644 index 0000000000..da095d6306 --- /dev/null +++ b/opentelemetry/src/logs/serde.rs @@ -0,0 +1,655 @@ +use std::{collections::HashMap, fmt}; + +use crate::{logs::AnyValue, Key, StringValue}; +use serde::ser::{ + Error, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, + SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer, StdError, +}; + +/// Serialize an arbitrary `serde::Serialize` into an `AnyValue`. +/// +/// This method performs the following translations when converting between `serde`'s data model and OpenTelemetry's: +/// +/// - Integers that don't fit in a `i64` are converted into strings. +/// - Unit types and nones are discarded (effectively treated as undefined). +/// - Struct and tuple variants are converted into an internally tagged map. +/// - Unit variants are converted into strings. +pub fn serialize(value: impl serde::Serialize) -> Option { + value.serialize(ValueSerializer).ok()? +} + +struct ValueSerializer; + +struct ValueSerializeSeq { + value: Vec, +} + +struct ValueSerializeTuple { + value: Vec, +} + +struct ValueSerializeTupleStruct { + value: Vec, +} + +struct ValueSerializeMap { + key: Option, + value: HashMap, +} + +struct ValueSerializeStruct { + value: HashMap, +} + +struct ValueSerializeTupleVariant { + variant: &'static str, + value: Vec, +} + +struct ValueSerializeStructVariant { + variant: &'static str, + value: HashMap, +} + +#[derive(Debug)] +struct ValueError(String); + +impl fmt::Display for ValueError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl Error for ValueError { + fn custom(msg: T) -> Self + where + T: fmt::Display, + { + ValueError(msg.to_string()) + } +} + +impl StdError for ValueError {} + +impl Serializer for ValueSerializer { + type Ok = Option; + + type Error = ValueError; + + type SerializeSeq = ValueSerializeSeq; + + type SerializeTuple = ValueSerializeTuple; + + type SerializeTupleStruct = ValueSerializeTupleStruct; + + type SerializeTupleVariant = ValueSerializeTupleVariant; + + type SerializeMap = ValueSerializeMap; + + type SerializeStruct = ValueSerializeStruct; + + type SerializeStructVariant = ValueSerializeStructVariant; + + fn serialize_bool(self, v: bool) -> Result { + Ok(Some(AnyValue::Boolean(v))) + } + + fn serialize_i8(self, v: i8) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_i16(self, v: i16) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_i32(self, v: i32) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_i64(self, v: i64) -> Result { + Ok(Some(AnyValue::Int(v))) + } + + fn serialize_i128(self, v: i128) -> Result { + if let Ok(v) = v.try_into() { + self.serialize_i64(v) + } else { + self.collect_str(&v) + } + } + + fn serialize_u8(self, v: u8) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_u16(self, v: u16) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_u32(self, v: u32) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_u64(self, v: u64) -> Result { + if let Ok(v) = v.try_into() { + self.serialize_i64(v) + } else { + self.collect_str(&v) + } + } + + fn serialize_u128(self, v: u128) -> Result { + if let Ok(v) = v.try_into() { + self.serialize_i64(v) + } else { + self.collect_str(&v) + } + } + + fn serialize_f32(self, v: f32) -> Result { + self.serialize_f64(v as f64) + } + + fn serialize_f64(self, v: f64) -> Result { + Ok(Some(AnyValue::Double(v))) + } + + fn serialize_char(self, v: char) -> Result { + self.collect_str(&v) + } + + fn serialize_str(self, v: &str) -> Result { + Ok(Some(AnyValue::String(StringValue::from(v.to_owned())))) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + Ok(Some(AnyValue::Bytes(Box::new(v.to_owned())))) + } + + fn serialize_none(self) -> Result { + Ok(None) + } + + fn serialize_some( + self, + value: &T, + ) -> Result { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + Ok(None) + } + + fn serialize_unit_struct(self, name: &'static str) -> Result { + name.serialize(self) + } + + fn serialize_unit_variant( + self, + _: &'static str, + _: u32, + variant: &'static str, + ) -> Result { + variant.serialize(self) + } + + fn serialize_newtype_struct( + self, + _: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _: &'static str, + _: u32, + variant: &'static str, + value: &T, + ) -> Result { + let mut map = self.serialize_map(Some(1))?; + map.serialize_entry(variant, value)?; + map.end() + } + + fn serialize_seq(self, _: Option) -> Result { + Ok(ValueSerializeSeq { value: Vec::new() }) + } + + fn serialize_tuple(self, _: usize) -> Result { + Ok(ValueSerializeTuple { value: Vec::new() }) + } + + fn serialize_tuple_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + Ok(ValueSerializeTupleStruct { value: Vec::new() }) + } + + fn serialize_tuple_variant( + self, + _: &'static str, + _: u32, + variant: &'static str, + _: usize, + ) -> Result { + Ok(ValueSerializeTupleVariant { + variant, + value: Vec::new(), + }) + } + + fn serialize_map(self, _: Option) -> Result { + Ok(ValueSerializeMap { + key: None, + value: HashMap::new(), + }) + } + + fn serialize_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + Ok(ValueSerializeStruct { + value: HashMap::new(), + }) + } + + fn serialize_struct_variant( + self, + _: &'static str, + _: u32, + variant: &'static str, + _: usize, + ) -> Result { + Ok(ValueSerializeStructVariant { + variant, + value: HashMap::new(), + }) + } +} + +impl SerializeSeq for ValueSerializeSeq { + type Ok = Option; + + type Error = ValueError; + + fn serialize_element( + &mut self, + value: &T, + ) -> Result<(), Self::Error> { + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.push(value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::ListAny(Box::new(self.value)))) + } +} + +impl SerializeTuple for ValueSerializeTuple { + type Ok = Option; + + type Error = ValueError; + + fn serialize_element( + &mut self, + value: &T, + ) -> Result<(), Self::Error> { + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.push(value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::ListAny(Box::new(self.value)))) + } +} + +impl SerializeTupleStruct for ValueSerializeTupleStruct { + type Ok = Option; + + type Error = ValueError; + + fn serialize_field( + &mut self, + value: &T, + ) -> Result<(), Self::Error> { + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.push(value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::ListAny(Box::new(self.value)))) + } +} + +impl SerializeTupleVariant for ValueSerializeTupleVariant { + type Ok = Option; + + type Error = ValueError; + + fn serialize_field( + &mut self, + value: &T, + ) -> Result<(), Self::Error> { + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.push(value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::Map({ + let mut variant = Box::>::default(); + variant.insert( + Key::from(self.variant), + AnyValue::ListAny(Box::new(self.value)), + ); + variant + }))) + } +} + +impl SerializeMap for ValueSerializeMap { + type Ok = Option; + + type Error = ValueError; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> { + let key = match key.serialize(ValueSerializer)? { + Some(AnyValue::String(key)) => Key::from(String::from(key)), + key => Key::from(format!("{key:?}")), + }; + + self.key = Some(key); + + Ok(()) + } + + fn serialize_value( + &mut self, + value: &T, + ) -> Result<(), Self::Error> { + let key = self + .key + .take() + .ok_or_else(|| Self::Error::custom("missing key"))?; + + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.insert(key, value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::Map(Box::new(self.value)))) + } +} + +impl SerializeStruct for ValueSerializeStruct { + type Ok = Option; + + type Error = ValueError; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> { + let key = Key::from(key); + + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.insert(key, value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::Map(Box::new(self.value)))) + } +} + +impl SerializeStructVariant for ValueSerializeStructVariant { + type Ok = Option; + + type Error = ValueError; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> { + let key = Key::from(key); + + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.insert(key, value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::Map({ + let mut variant = Box::>::default(); + variant.insert(Key::from(self.variant), AnyValue::Map(Box::new(self.value))); + variant + }))) + } +} + +#[cfg(test)] +mod tests { + use super::serialize; + + use crate::{logs::AnyValue, Key, StringValue}; + + use std::collections::HashMap; + + #[test] + fn test_serialize() { + assert_eq!(None, serialize(None::)); + assert_eq!(None, serialize(())); + + assert_eq!(AnyValue::Int(1), serialize(1u8).unwrap()); + assert_eq!(AnyValue::Int(1), serialize(1u16).unwrap()); + assert_eq!(AnyValue::Int(1), serialize(1u32).unwrap()); + assert_eq!(AnyValue::Int(1), serialize(1u64).unwrap()); + assert_eq!(AnyValue::Int(1), serialize(1u128).unwrap()); + assert_eq!(AnyValue::Int(1), serialize(1i8).unwrap()); + assert_eq!(AnyValue::Int(1), serialize(1i16).unwrap()); + assert_eq!(AnyValue::Int(1), serialize(1i32).unwrap()); + assert_eq!(AnyValue::Int(1), serialize(1i64).unwrap()); + assert_eq!(AnyValue::Int(1), serialize(1i128).unwrap()); + + assert_eq!(AnyValue::Double(1.0), serialize(1.0f32).unwrap()); + assert_eq!(AnyValue::Double(1.0), serialize(1.0f64).unwrap()); + + assert_eq!( + AnyValue::String(StringValue::from("a")), + serialize("a").unwrap() + ); + + assert_eq!( + AnyValue::String(StringValue::from("a")), + serialize('a').unwrap() + ); + + assert_eq!(AnyValue::Boolean(true), serialize(true).unwrap()); + + assert_eq!( + AnyValue::Map({ + let mut map = Box::>::default(); + + map.insert(Key::from("a"), AnyValue::Int(1)); + map.insert(Key::from("b"), AnyValue::Int(1)); + map.insert(Key::from("c"), AnyValue::Int(1)); + + map + }), + serialize({ + let mut map = HashMap::<&str, i32>::default(); + + map.insert("a", 1); + map.insert("b", 1); + map.insert("c", 1); + + map + }) + .unwrap(), + ); + + assert_eq!( + AnyValue::ListAny(Box::new(vec![ + AnyValue::Int(1), + AnyValue::Int(1), + AnyValue::Int(1) + ])), + serialize(vec![1, 1, 1]).unwrap(), + ); + + assert_eq!( + AnyValue::ListAny(Box::new(vec![ + AnyValue::Int(1), + AnyValue::Int(1), + AnyValue::Int(1) + ])), + serialize((1, 1, 1)).unwrap(), + ); + + #[derive(serde::Serialize)] + struct Newtype(i32); + + assert_eq!(AnyValue::Int(1), serialize(Newtype(1)).unwrap()); + + #[derive(serde::Serialize)] + enum Enum { + Unit, + Newtype(i32), + Struct { a: i32, b: i32, c: i32 }, + Tuple(i32, i32, i32), + } + + assert_eq!( + AnyValue::String(StringValue::from("Unit")), + serialize(Enum::Unit).unwrap() + ); + + assert_eq!( + AnyValue::Map({ + let mut map = HashMap::new(); + + map.insert(Key::from("Newtype"), AnyValue::Int(42)); + + Box::new(map) + }), + serialize(Enum::Newtype(42)).unwrap() + ); + + assert_eq!( + AnyValue::Map({ + let mut map = HashMap::new(); + + map.insert( + Key::from("Struct"), + AnyValue::Map(Box::new({ + let mut map = HashMap::new(); + map.insert(Key::from("a"), AnyValue::Int(1)); + map.insert(Key::from("b"), AnyValue::Int(1)); + map.insert(Key::from("c"), AnyValue::Int(1)); + map + })), + ); + + Box::new(map) + }), + serialize(Enum::Struct { a: 1, b: 1, c: 1 }).unwrap(), + ); + + assert_eq!( + AnyValue::Map({ + let mut map = HashMap::new(); + + map.insert( + Key::from("Tuple"), + AnyValue::ListAny(Box::new(vec![ + AnyValue::Int(1), + AnyValue::Int(1), + AnyValue::Int(1), + ])), + ); + + Box::new(map) + }), + serialize(Enum::Tuple(1, 1, 1)).unwrap(), + ); + + struct Bytes(B); + + impl> serde::Serialize for Bytes { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(self.0.as_ref()) + } + } + + assert_eq!( + AnyValue::Bytes(Box::new(vec![1, 2, 3])), + serialize(Bytes(vec![1, 2, 3])).unwrap(), + ); + + struct Map { + a: i32, + b: i32, + c: i32, + } + + impl serde::Serialize for Map { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeMap; + + let mut map = serializer.serialize_map(Some(3))?; + + map.serialize_entry(&"a", &self.a)?; + map.serialize_entry(&"b", &self.b)?; + map.serialize_entry(&"c", &self.c)?; + + map.end() + } + } + + assert_eq!( + AnyValue::Map({ + let mut map = Box::>::default(); + + map.insert(Key::from("a"), AnyValue::Int(1)); + map.insert(Key::from("b"), AnyValue::Int(1)); + map.insert(Key::from("c"), AnyValue::Int(1)); + + map + }), + serialize(&Map { a: 1, b: 1, c: 1 }).unwrap(), + ); + } +}