diff --git a/Cargo.lock b/Cargo.lock index b737e3a..965a43f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3395,6 +3395,7 @@ dependencies = [ name = "tracing_duper" version = "0.1.0" dependencies = [ + "chrono", "duper", "tracing-core", "tracing-subscriber", @@ -3656,12 +3657,12 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] diff --git a/duper/src/ast.rs b/duper/src/ast.rs index 54760c8..ba65038 100644 --- a/duper/src/ast.rs +++ b/duper/src/ast.rs @@ -54,7 +54,7 @@ pub enum DuperInner<'a> { } /// A key in a [`DuperObject`]. -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct DuperKey<'a>(pub(crate) Cow<'a, str>); /// An object (or map) from [`DuperKey`]s to [`DuperValue`]s. @@ -360,6 +360,25 @@ impl<'a> DuperObject<'a> { pub fn iter(&self) -> impl Iterator, DuperValue<'a>)> { self.0.iter() } + + /// Create a valid object from the provided [`Vec`], dropping any duplicate keys + /// and keeping the first one. + pub fn from_lossy(value: Vec<(DuperKey<'a>, DuperValue<'a>)>) -> Self { + let mut keys = std::collections::HashSet::with_capacity(value.len()); + Self( + value + .into_iter() + .filter(|(key, _)| { + if keys.contains(key) { + false + } else { + keys.insert(key.clone()); + true + } + }) + .collect(), + ) + } } impl<'a> From, DuperValue<'a>>> for DuperObject<'a> { diff --git a/serde_duper/CHANGELOG.md b/serde_duper/CHANGELOG.md index dc5aa53..d41e6bf 100644 --- a/serde_duper/CHANGELOG.md +++ b/serde_duper/CHANGELOG.md @@ -10,7 +10,6 @@ - Bump `duper` to 0.5.0. - ## 0.4.1 (2025-11-25) ### Changed diff --git a/serde_duper/src/types/mod.rs b/serde_duper/src/types/mod.rs index 8f341e5..98216c8 100644 --- a/serde_duper/src/types/mod.rs +++ b/serde_duper/src/types/mod.rs @@ -971,5 +971,8 @@ pub mod DuperOptionBytesRegex { } } +#[cfg(feature = "temporal")] +pub mod temporal; + #[cfg(feature = "uuid")] duper_serde_module!(DuperUuid, DuperOptionUuid, ::uuid::Uuid, "Uuid"); diff --git a/serde_duper/src/types/temporal.rs b/serde_duper/src/types/temporal.rs new file mode 100644 index 0000000..780a262 --- /dev/null +++ b/serde_duper/src/types/temporal.rs @@ -0,0 +1,825 @@ +use super::*; + +pub mod DuperInstant { + use super::*; + use ::temporal_rs::Instant as WrappedType; + + pub fn serialize(value: &WrappedType, serializer: S) -> Result + where + S: Serializer, + { + ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_instant_from(::std::borrow::Cow::Owned( + value + .to_ixdtf_string(None, Default::default()) + .map_err(|err| ::custom(err.to_string()))?, + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::Instant(inner) => ::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + ), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"Instant", + )), + } + } +} +pub mod DuperOptionInstant { + use super::*; + use ::temporal_rs::Instant as WrappedType; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(value) => ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_instant_from(::std::borrow::Cow::Owned( + value + .to_ixdtf_string(None, Default::default()) + .map_err(|err| { + ::custom(err.to_string()) + })?, + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer), + None => serializer.serialize_newtype_struct( + "Instant", + &Option::<::duper::serde::temporal::TemporalString>::None, + ), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor + where + WrappedType: Deserialize<'de>, + { + type Value = Option; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an optional Temporal Instant") + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::Instant(inner) => Some(::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + )) + .transpose(), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"Instant", + )), + } + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_option(Visitor {}) + } +} + +pub mod DuperZonedDateTime { + use super::*; + use ::temporal_rs::ZonedDateTime as WrappedType; + + pub fn serialize(value: &WrappedType, serializer: S) -> Result + where + S: Serializer, + { + ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_zoned_date_time_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::ZonedDateTime(inner) => ::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + ), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainTime", + )), + } + } +} +pub mod DuperOptionZonedDateTime { + use super::*; + use ::temporal_rs::ZonedDateTime as WrappedType; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(value) => ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_zoned_date_time_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer), + None => serializer.serialize_newtype_struct( + "ZonedDateTime", + &Option::<::duper::serde::temporal::TemporalString>::None, + ), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor + where + WrappedType: Deserialize<'de>, + { + type Value = Option; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an optional Temporal ZonedDateTime") + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::ZonedDateTime(inner) => { + Some(::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + )) + .transpose() + } + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"ZonedDateTime", + )), + } + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_option(Visitor {}) + } +} + +pub mod DuperPlainDate { + use super::*; + use ::temporal_rs::PlainDate as WrappedType; + + pub fn serialize(value: &WrappedType, serializer: S) -> Result + where + S: Serializer, + { + ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_plain_date_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::PlainDate(inner) => ::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + ), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainTime", + )), + } + } +} +pub mod DuperOptionPlainDate { + use super::*; + use ::temporal_rs::PlainDate as WrappedType; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(value) => ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_plain_date_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer), + None => serializer.serialize_newtype_struct( + "PlainDate", + &Option::<::duper::serde::temporal::TemporalString>::None, + ), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor + where + WrappedType: Deserialize<'de>, + { + type Value = Option; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an optional Temporal PlainDate") + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::PlainDate(inner) => Some(::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + )) + .transpose(), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainDate", + )), + } + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_option(Visitor {}) + } +} + +pub mod DuperPlainTime { + use super::*; + use ::temporal_rs::PlainTime as WrappedType; + + pub fn serialize(value: &WrappedType, serializer: S) -> Result + where + S: Serializer, + { + ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_plain_time_from(::std::borrow::Cow::Owned( + value + .to_ixdtf_string(Default::default()) + .map_err(|err| ::custom(err.to_string()))?, + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::PlainTime(inner) => ::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + ), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainTime", + )), + } + } +} +pub mod DuperOptionPlainTime { + use super::*; + use ::temporal_rs::PlainTime as WrappedType; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(value) => ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_plain_time_from(::std::borrow::Cow::Owned( + value.to_ixdtf_string(Default::default()).map_err(|err| { + ::custom(err.to_string()) + })?, + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer), + None => serializer.serialize_newtype_struct( + "PlainTime", + &Option::<::duper::serde::temporal::TemporalString>::None, + ), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor + where + WrappedType: Deserialize<'de>, + { + type Value = Option; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an optional Temporal PlainTime") + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::PlainTime(inner) => Some(::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + )) + .transpose(), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainTime", + )), + } + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_option(Visitor {}) + } +} + +pub mod DuperPlainDateTime { + use super::*; + use ::temporal_rs::PlainDateTime as WrappedType; + + pub fn serialize(value: &WrappedType, serializer: S) -> Result + where + S: Serializer, + { + ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_plain_date_time_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::PlainDateTime(inner) => ::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + ), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainDateTime", + )), + } + } +} +pub mod DuperOptionPlainDateTime { + use super::*; + use ::temporal_rs::PlainDateTime as WrappedType; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(value) => ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_plain_date_time_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer), + None => serializer.serialize_newtype_struct( + "PlainDateTime", + &Option::<::duper::serde::temporal::TemporalString>::None, + ), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor + where + WrappedType: Deserialize<'de>, + { + type Value = Option; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an optional Temporal PlainDateTime") + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::PlainDateTime(inner) => { + Some(::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + )) + .transpose() + } + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainDateTime", + )), + } + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_option(Visitor {}) + } +} + +pub mod DuperPlainYearMonth { + use super::*; + use ::temporal_rs::PlainYearMonth as WrappedType; + + pub fn serialize(value: &WrappedType, serializer: S) -> Result + where + S: Serializer, + { + ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_plain_year_month_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::PlainYearMonth(inner) => ::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + ), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainYearMonth", + )), + } + } +} +pub mod DuperOptionPlainYearMonth { + use super::*; + use ::temporal_rs::PlainYearMonth as WrappedType; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(value) => ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_plain_year_month_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer), + None => serializer.serialize_newtype_struct( + "PlainYearMonth", + &Option::<::duper::serde::temporal::TemporalString>::None, + ), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor + where + WrappedType: Deserialize<'de>, + { + type Value = Option; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an optional Temporal PlainYearMonth") + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::PlainYearMonth(inner) => { + Some(::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + )) + .transpose() + } + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainYearMonth", + )), + } + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_option(Visitor {}) + } +} + +pub mod DuperPlainMonthDay { + use super::*; + use ::temporal_rs::PlainMonthDay as WrappedType; + + pub fn serialize(value: &WrappedType, serializer: S) -> Result + where + S: Serializer, + { + ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_plain_month_day_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::PlainMonthDay(inner) => ::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + ), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainMonthDay", + )), + } + } +} +pub mod DuperOptionPlainMonthDay { + use super::*; + use ::temporal_rs::PlainMonthDay as WrappedType; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(value) => ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_plain_month_day_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer), + None => serializer.serialize_newtype_struct( + "PlainMonthDay", + &Option::<::duper::serde::temporal::TemporalString>::None, + ), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor + where + WrappedType: Deserialize<'de>, + { + type Value = Option; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an optional Temporal PlainMonthDay") + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::PlainMonthDay(inner) => { + Some(::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + )) + .transpose() + } + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"PlainMonthDay", + )), + } + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_option(Visitor {}) + } +} + +pub mod DuperDuration { + use super::*; + use ::temporal_rs::Duration as WrappedType; + + pub fn serialize(value: &WrappedType, serializer: S) -> Result + where + S: Serializer, + { + ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_duration_from(::std::borrow::Cow::Owned(value.to_string())) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::Duration(inner) => ::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + ), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"Duration", + )), + } + } +} +pub mod DuperOptionDuration { + use super::*; + use ::temporal_rs::Duration as WrappedType; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(value) => ::duper::serde::temporal::TemporalString( + ::duper::DuperTemporal::try_duration_from(::std::borrow::Cow::Owned( + value.to_string(), + )) + .map_err(|err| ::custom(err.to_string()))?, + ) + .serialize(serializer), + None => serializer.serialize_newtype_struct( + "Duration", + &Option::<::duper::serde::temporal::TemporalString>::None, + ), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + WrappedType: Deserialize<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor + where + WrappedType: Deserialize<'de>, + { + type Value = Option; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an optional Temporal Duration") + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match ::duper::serde::temporal::TemporalString::deserialize(deserializer)?.0 { + ::duper::DuperTemporal::Duration(inner) => Some(::deserialize( + serde_core::de::IntoDeserializer::into_deserializer(inner.as_ref()), + )) + .transpose(), + typ => Err(serde_core::de::Error::invalid_value( + serde_core::de::Unexpected::Str(typ.name()), + &"Duration", + )), + } + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_option(Visitor {}) + } +} diff --git a/tracing_duper/Cargo.toml b/tracing_duper/Cargo.toml index ab5c84b..b53aa27 100644 --- a/tracing_duper/Cargo.toml +++ b/tracing_duper/Cargo.toml @@ -10,7 +10,12 @@ repository = "https://github.com/EpicEric/duper" homepage = "https://duper.dev.br" readme = "README.md" +[features] +default = ["chrono"] +chrono = ["dep:chrono"] + [dependencies] +chrono = { version = "0.4", optional = true } duper = { version = "0.5.0", path = "../duper", features = ["serde"] } -tracing-core = "0.1.35" +tracing-core = "0.1" tracing-subscriber = "0.3" diff --git a/tracing_duper/src/lib.rs b/tracing_duper/src/lib.rs index c6212df..3408f3e 100644 --- a/tracing_duper/src/lib.rs +++ b/tracing_duper/src/lib.rs @@ -1,25 +1,221 @@ #![doc(html_logo_url = "https://duper.dev.br/logos/duper-100-100.png")] -use duper::{DuperInner, DuperKey, DuperObject, DuperString, DuperValue, Serializer}; +use std::{borrow::Cow, collections::BTreeMap}; + +use chrono::{Local, Utc}; +use duper::{ + DuperArray, DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, DuperString, + DuperTemporal, DuperValue, Serializer, +}; use tracing_core::{Event, Subscriber}; use tracing_subscriber::{ - fmt::{FormatEvent, FormatFields}, + field::{RecordFields, VisitOutput}, + fmt::{FormatEvent, FormatFields, FormattedFields}, registry::LookupSpan, }; -struct DuperSubscriber { +pub trait DuperTimer { + fn get_time(&self) -> Option>; +} + +impl DuperTimer for () { + fn get_time(&self) -> Option> { + None + } +} + +#[cfg(feature = "chrono")] +#[derive(Debug)] +pub struct ChronoUtcTimer; + +#[cfg(feature = "chrono")] +impl DuperTimer for ChronoUtcTimer { + fn get_time(&self) -> Option> { + Some(DuperValue { + identifier: Some(DuperIdentifier::try_from("Instant").expect("valid identifier")), + inner: DuperInner::Temporal( + DuperTemporal::try_instant_from(Cow::Owned(Utc::now().to_rfc3339())) + .expect("valid ISO-8601 Instant"), + ), + }) + } +} + +#[cfg(feature = "chrono")] +#[derive(Debug)] +pub struct ChronoLocalTimer; + +#[cfg(feature = "chrono")] +impl DuperTimer for ChronoLocalTimer { + fn get_time(&self) -> Option> { + Some(DuperValue { + identifier: Some(DuperIdentifier::try_from("Instant").expect("valid identifier")), + inner: DuperInner::Temporal( + DuperTemporal::try_instant_from(Cow::Owned(Local::now().to_rfc3339())) + .expect("valid ISO-8601 Instant"), + ), + }) + } +} + +#[cfg(feature = "chrono")] +#[derive(Debug)] +pub struct DuperSubscriber { timer: Timer, display_timestamp: bool, display_level: bool, display_target: bool, display_file: bool, display_line: bool, + flatten_event: bool, + display_current_span: bool, + display_span_list: bool, +} + +#[cfg(not(any(feature = "chrono")))] +#[derive(Debug)] +pub struct DuperSubscriber { + timer: Timer, + display_timestamp: bool, + display_level: bool, + display_target: bool, + display_file: bool, + display_line: bool, + flatten_event: bool, + display_current_span: bool, + display_span_list: bool, +} + +impl Default for DuperSubscriber { + fn default() -> Self { + #[cfg(feature = "chrono")] + { + Self { + timer: ChronoLocalTimer, + display_timestamp: true, + display_level: true, + display_target: true, + display_file: false, + display_line: false, + flatten_event: false, + display_current_span: true, + display_span_list: true, + } + } + #[cfg(not(any(feature = "chrono")))] + { + Self { + timer: (), + display_timestamp: true, + display_level: true, + display_target: true, + display_file: false, + display_line: false, + flatten_event: false, + display_current_span: true, + display_span_list: true, + } + } + } +} + +impl DuperSubscriber { + pub fn new() -> Self { + Self::default() + } +} + +impl DuperSubscriber { + pub fn with_timer(self, timer: Timer2) -> DuperSubscriber { + DuperSubscriber { + timer, + display_timestamp: self.display_timestamp, + display_level: self.display_level, + display_target: self.display_target, + display_file: self.display_file, + display_line: self.display_line, + flatten_event: self.flatten_event, + display_current_span: self.display_current_span, + display_span_list: self.display_span_list, + } + } + + pub fn no_timer(self) -> DuperSubscriber<()> { + DuperSubscriber { + timer: (), + display_timestamp: self.display_timestamp, + display_level: self.display_level, + display_target: self.display_target, + display_file: self.display_file, + display_line: self.display_line, + flatten_event: self.flatten_event, + display_current_span: self.display_current_span, + display_span_list: self.display_span_list, + } + } + + pub fn with_timestamp(self, display_timestamp: bool) -> Self { + Self { + display_timestamp, + ..self + } + } + + pub fn with_level(self, display_level: bool) -> Self { + Self { + display_level, + ..self + } + } + + pub fn with_target(self, display_target: bool) -> Self { + Self { + display_target, + ..self + } + } + + pub fn with_file(self, display_file: bool) -> Self { + Self { + display_file, + ..self + } + } + + pub fn with_line(self, display_line: bool) -> Self { + Self { + display_line, + ..self + } + } + + pub fn flatten_event(self, flatten_event: bool) -> Self { + Self { + flatten_event, + ..self + } + } + + pub fn with_current_span(self, display_current_span: bool) -> Self { + Self { + display_current_span, + ..self + } + } + + pub fn with_span_list(self, display_span_list: bool) -> Self { + Self { + display_span_list, + ..self + } + } } impl FormatEvent for DuperSubscriber where S: Subscriber + for<'a> LookupSpan<'a>, N: for<'a> FormatFields<'a> + 'static, + Timer: DuperTimer + std::fmt::Debug, { fn format_event( &self, @@ -28,8 +224,8 @@ where event: &Event<'_>, ) -> std::fmt::Result { let mut log = vec![]; - let metadata = event.metadata(); + if self.display_level { log.push(( DuperKey::from("level"), @@ -39,6 +235,13 @@ where }, )); } + + if self.display_timestamp + && let Some(timestamp) = self.timer.get_time() + { + log.push((DuperKey::from("timestamp"), timestamp)); + } + if self.display_target { log.push(( DuperKey::from("target"), @@ -48,6 +251,7 @@ where }, )); } + if self.display_file { log.push(( DuperKey::from("file"), @@ -60,6 +264,7 @@ where }, )); } + if self.display_line { log.push(( DuperKey::from("line"), @@ -73,18 +278,276 @@ where )); } + if self.display_current_span || self.display_span_list { + let current_span = event + .parent() + .and_then(|id| ctx.span(id)) + .or_else(|| ctx.lookup_current()); + if let Some(ref span) = current_span { + let extensions = span.extensions(); + if self.display_current_span + && let Some(fields) = extensions.get::>() + { + let value = duper::DuperParser::parse_duper_trunk(fields.as_str()) + .expect("valid Duper value from FormattedFields"); + log.push((DuperKey::from("span"), value.static_clone())); + } + if self.display_span_list { + let mut spans = vec![]; + ctx.visit_spans(|span| { + let extensions = span.extensions(); + if let Some(fields) = extensions.get::>() { + let value = duper::DuperParser::parse_duper_trunk(fields.as_str()) + .expect("valid Duper value from FormattedFields"); + spans.push(value.static_clone()); + } + std::fmt::Result::Ok(()) + })?; + log.push(( + DuperKey::from("spans"), + DuperValue { + identifier: None, + inner: DuperInner::Array(DuperArray::from(spans)), + }, + )); + } + } + } + + let mut fields_visitor = DuperVisitor::new(); + event.record(&mut fields_visitor); + let duper = fields_visitor.finish(); + if self.flatten_event + && let DuperInner::Object(object) = duper.inner + { + log.extend(object.into_inner()); + } else { + log.push((DuperKey::from("fields"), duper)); + } + let mut serializer = Serializer::new(false, true); - if let Ok(object) = DuperObject::try_from(log) { - writeln!( - writer, - "{}", - serializer.serialize(&DuperValue { - identifier: None, - inner: DuperInner::Object(object), - }) - ) + writeln!( + writer, + "{}", + serializer.serialize(&DuperValue { + identifier: None, + inner: DuperInner::Object(DuperObject::from_lossy(log)), + }) + ) + } +} + +pub struct DuperFields; + +impl<'writer> FormatFields<'writer> for DuperFields { + fn format_fields( + &self, + mut writer: tracing_subscriber::fmt::format::Writer<'writer>, + fields: R, + ) -> std::fmt::Result { + let mut visitor = DuperVisitor::new(); + fields.record(&mut visitor); + writer.write_str(&duper::Serializer::new(false, true).serialize(&visitor.finish())) + } + + fn add_fields( + &self, + current: &'writer mut FormattedFields, + fields: &tracing_core::span::Record<'_>, + ) -> std::fmt::Result { + if current.fields.is_empty() { + self.format_fields(current.as_writer(), fields) } else { + let mut visitor = DuperVisitor::new(); + let value = duper::DuperParser::parse_duper_trunk(current.as_str()) + .expect("valid Duper value in current FormattedFields"); + let DuperInner::Object(value) = value.inner else { + unreachable!("invalid Duper value, expected object"); + }; + visitor.values.extend(value.into_inner()); + fields.record(&mut visitor); + current.fields = duper::Serializer::new(false, true).serialize(&visitor.finish()); Ok(()) } } } + +pub struct DuperVisitor<'a> { + values: BTreeMap, DuperValue<'a>>, +} + +impl DuperVisitor<'_> { + fn new() -> Self { + Self { + values: BTreeMap::new(), + } + } +} + +impl std::fmt::Debug for DuperVisitor<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DuperVisitor") + .field("values", &self.values) + .finish() + } +} + +impl<'a> tracing_subscriber::field::VisitOutput> for DuperVisitor<'a> { + fn finish(self) -> DuperValue<'a> { + DuperValue { + identifier: None, + inner: DuperInner::Object(DuperObject::from(self.values)), + } + } +} + +impl tracing_core::field::Visit for DuperVisitor<'_> { + fn record_debug(&mut self, field: &tracing_core::Field, value: &dyn core::fmt::Debug) { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: Some(DuperIdentifier::try_from("Debug").expect("valid identifier")), + inner: DuperInner::String(DuperString::from(format!("{:?}", value))), + }, + ); + } + + fn record_f64(&mut self, field: &tracing_core::Field, value: f64) { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: None, + inner: DuperInner::Float(value), + }, + ); + } + + fn record_i64(&mut self, field: &tracing_core::Field, value: i64) { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: None, + inner: DuperInner::Integer(value), + }, + ); + } + + fn record_u64(&mut self, field: &tracing_core::Field, value: u64) { + if let Ok(value) = i64::try_from(value) { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: None, + inner: DuperInner::Integer(value), + }, + ); + } else { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: Some(DuperIdentifier::try_from("U64").expect("valid identifier")), + inner: DuperInner::String(DuperString::from(value.to_string())), + }, + ); + } + } + + fn record_i128(&mut self, field: &tracing_core::Field, value: i128) { + if let Ok(value) = i64::try_from(value) { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: None, + inner: DuperInner::Integer(value), + }, + ); + } else { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: Some(DuperIdentifier::try_from("I128").expect("valid identifier")), + inner: DuperInner::String(DuperString::from(value.to_string())), + }, + ); + } + } + + fn record_u128(&mut self, field: &tracing_core::Field, value: u128) { + if let Ok(value) = i64::try_from(value) { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: None, + inner: DuperInner::Integer(value), + }, + ); + } else { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: Some(DuperIdentifier::try_from("U128").expect("valid identifier")), + inner: DuperInner::String(DuperString::from(value.to_string())), + }, + ); + } + } + + fn record_bool(&mut self, field: &tracing_core::Field, value: bool) { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: None, + inner: DuperInner::Boolean(value), + }, + ); + } + + fn record_str(&mut self, field: &tracing_core::Field, value: &str) { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: None, + inner: DuperInner::String(DuperString::from(value.to_string())), + }, + ); + } + + fn record_bytes(&mut self, field: &tracing_core::Field, value: &[u8]) { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: None, + inner: DuperInner::Bytes(DuperBytes::from(value.to_vec())), + }, + ); + } + + fn record_error( + &mut self, + field: &tracing_core::Field, + value: &(dyn std::error::Error + 'static), + ) { + self.values.insert( + DuperKey::from(field.name()), + DuperValue { + identifier: Some(DuperIdentifier::try_from("Error").expect("valid identifier")), + inner: DuperInner::String(DuperString::from(value.to_string())), + }, + ); + } +} + +#[cfg(test)] +mod tests { + use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt}; + + use super::{DuperFields, DuperSubscriber}; + + #[test] + fn create_duper_formatter() { + let fmt_layer = fmt::layer() + .fmt_fields(DuperFields) + .event_format(DuperSubscriber::new()); + tracing_subscriber::registry().with(fmt_layer).init(); + } +}