diff --git a/ncdufmt/src/lib.rs b/ncdufmt/src/lib.rs index 76cc0c4..42c10bd 100644 --- a/ncdufmt/src/lib.rs +++ b/ncdufmt/src/lib.rs @@ -3,8 +3,10 @@ use num_traits::identities::One; use num_traits::identities::Zero; use serde::de; +use serde::Serializer; // use serde::de::Error; use serde::de::Visitor; +use serde::ser::SerializeSeq; use serde_repr::Deserialize_repr; use serde_repr::Serialize_repr; use std::ops::Not; @@ -16,15 +18,28 @@ use serde::{Deserialize, Serialize}; // This is based on https://dev.yorhel.nl/ncdu/jsonfmt -#[derive(Serialize, Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone)] struct NcduFile { pub major_version: MajorVersion, pub minor_version: MinorVersion, pub header_metadata: HeaderMetadata, - #[serde(flatten)] pub contents: Directory, } +impl Serialize for NcduFile { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(4))?; + seq.serialize_element(&self.major_version)?; + seq.serialize_element(&self.minor_version)?; + seq.serialize_element(&self.header_metadata)?; + seq.serialize_element(&self.contents)?; + seq.end() + } +} + impl<'de> Deserialize<'de> for NcduFile { fn deserialize(deserializer: D) -> Result where @@ -123,13 +138,34 @@ impl HeaderMetadata { } } -#[derive(Serialize, Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Directory { info: InfoBlock, - #[serde(flatten)] contents: Vec, } +impl Serialize for Directory { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::Error; + let mut seq = serializer.serialize_seq(Some( + self.contents + .len() + .checked_add(1) + .ok_or_else(|| S::Error::custom("Directory contents too large"))?, + ))?; + // Add the info block first + seq.serialize_element(&self.info)?; + // Then any entries + for element in self.contents.iter() { + seq.serialize_element(element)?; + } + seq.end() + } +} + impl<'de> Deserialize<'de> for Directory { fn deserialize(deserializer: D) -> Result where @@ -200,6 +236,7 @@ pub struct InfoBlock { /// Device ID. A unique ID within the context of the exported dump. Could be a serialization of lstat().st_dev, but could be randomly generated and used within this file /// /// Accepted values are in the range of 0 <= dev < 2^64. + #[serde(skip_serializing_if = "Option::is_none", default)] pub dev: Option, /// Apparent filesize @@ -261,17 +298,22 @@ pub struct InfoBlock { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct ExtendedInfoBlock { /// Number, user ID who owns the file. Accepted values are in the range 0 <= uid < 2^31. + #[serde(skip_serializing_if = "Option::is_none", default)] uid: Option, /// Number, group ID who owns the file. Accepted values are in the range 0 <= uid < 2^31. + #[serde(skip_serializing_if = "Option::is_none", default)] gid: Option, /// Number, the raw file mode as returned by lstat(3). For Linux systems, see inode(7) for the interpretation of this field. Accepted range: 0 <= mode < 2^16. + #[serde(skip_serializing_if = "Option::is_none", default)] mode: Option, /// Number, last modification time as a UNIX timestamp. Accepted range: 0 <= mtime < 2^64. As of ncdu 1.16, this number may also include an (infinite precision) decimal part for fractional seconds, though the decimal part is (currently) discarded during import. + #[serde(skip_serializing_if = "Option::is_none", default)] mtime: Option, } #[cfg(test)] mod tests { + use serde_path_to_error; use std::fs; use super::*; @@ -354,7 +396,6 @@ mod tests { .is_ok()); } - use serde_path_to_error; #[test] fn test_simple_ncdu_output() { let input = fs::read_to_string("../sample-ncdu-output.json").unwrap(); @@ -365,7 +406,6 @@ mod tests { e }) .unwrap(); - // let parsed = serde_path_to_error::from_str::(&input).unwrap(); assert_eq!(parsed.major_version, MajorVersion::Version1); assert_eq!(parsed.minor_version, MinorVersion::Minor2); @@ -373,4 +413,17 @@ mod tests { assert_eq!(parsed.contents.info.name, "/tmp/tmp.2gWrgcHU4X"); assert_eq!(parsed.contents.contents.len(), 2); } + + #[test] + fn test_serialization() { + let input = fs::read_to_string("../sample-ncdu-output.json").unwrap(); + let deserialized = serde_json::from_str::(&input).unwrap(); + let serialized = serde_json::to_string(&deserialized).unwrap(); + let deserialized_again = serde_json::from_str::(&serialized).unwrap(); + let serialized_again = serde_json::to_string(&deserialized_again).unwrap(); + + assert_eq!(serialized, serialized_again); + assert_eq!(deserialized, deserialized_again); + assert_eq!(serialized, input); + } }