use std::collections::BTreeMap;
use std::num::NonZeroU32;
use crate::decode::u64_from_index;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
macro_rules! label_enum {
($(#[$meta:meta])* $name:ident { $($variant:ident => $label:literal,)* }) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum $name {
$(
#[doc = concat!("The `", $label, "` classification.")]
#[serde(rename = $label)]
$variant,
)*
}
impl $name {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self { $(Self::$variant => $label,)* }
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
};
}
label_enum! {
ContainerRole {
AcisBinary => "acis-binary",
ActiveBodyIndex => "active-body-index",
Ancillary => "ancillary",
Arrangements => "arrangements",
AssetCatalog => "asset-catalog",
Auxiliary => "auxiliary",
Block => "block",
Brep => "brep",
BrepSmb => "brep-smb",
BrepSmbh => "brep-smbh",
BrepText => "brep-text",
Bulkstream => "bulkstream",
CacheCell => "cache-cell",
CompoundStream => "compound-stream",
DesignConfig => "design-config",
Directory => "directory",
DirectoryEntry => "directory-entry",
DisplayJt => "display-jt",
Document => "document",
EntityRecords => "entity-records",
ExternalReference => "external-reference",
ExternalReferences => "external-references",
FastLoadJt => "fast-load-jt",
FastLoadStructure => "fast-load-structure",
FinjplSegment => "finjpl-segment",
GuiDocument => "gui-document",
Image => "image",
InFileAnchors => "in-file-anchors",
Manifest => "manifest",
MaterialTexture => "material-texture",
Metadata => "metadata",
Metastream => "metastream",
ModelData => "model-data",
NamedOpaqueStream => "named-opaque-stream",
NestedArchive => "nested-archive",
ObjectClass => "object-class",
OgsCache => "ogs-cache",
Opaque => "opaque",
Other => "other",
Paramesh => "paramesh",
ParasolidStream => "parasolid-stream",
PartAttributes => "part-attributes",
PartPayload => "part-payload",
Preview => "preview",
PreviewImage => "preview-image",
Properties => "properties",
Protein => "protein",
ProteinAssets => "protein-assets",
PsbGeometry => "psb-geometry",
RetainedTrailingRecords => "retained-trailing-records",
RootExchange => "root-exchange",
RseDatabase => "rse-database",
RseRevisionTable => "rse-revision-table",
RseSegmentBulk => "rse-segment-bulk",
RseSegmentMetadata => "rse-segment-metadata",
RseSegmentRegistry => "rse-segment-registry",
RseStorage => "rse-storage",
SaveToggleInfo => "save-toggle-info",
Section => "section",
Signature => "signature",
Storage => "storage",
Stream => "stream",
SubsidiaryExchange => "subsidiary-exchange",
Table => "table",
Thumbnail => "thumbnail",
}
}
label_enum! {
VerbatimLabel {
None => "none",
Stored => "stored",
}
}
label_enum! {
CompressionMethod {
Deflate => "deflate",
Zstd => "zstd",
Jpeg => "jpeg",
UnixCompress => "unix-compress",
Zlib => "zlib",
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerbatimSize {
Unreported,
PayloadOnly(u64),
StoredOnly(u64),
Exact(u64),
Framed(FramedSpan),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "schema", schemars(with = "u64"))]
#[serde(try_from = "u64", into = "u64")]
pub struct AllocatedLen(usize);
const LENGTH_OVER_ALLOCATION: &str = "declared length exceeds the largest live allocation";
impl AllocatedLen {
#[must_use]
pub fn new(bytes: u64) -> Option<Self> {
if bytes > u64_from_index(isize::MAX.unsigned_abs()) {
return None;
}
Some(Self(usize::try_from(bytes).ok()?))
}
#[must_use]
pub fn get(self) -> u64 {
u64_from_index(self.0)
}
}
impl TryFrom<u64> for AllocatedLen {
type Error = &'static str;
fn try_from(bytes: u64) -> Result<Self, Self::Error> {
Self::new(bytes).ok_or(LENGTH_OVER_ALLOCATION)
}
}
impl From<AllocatedLen> for u64 {
fn from(length: AllocatedLen) -> Self {
length.get()
}
}
impl From<&[u8]> for AllocatedLen {
fn from(bytes: &[u8]) -> Self {
Self(bytes.len())
}
}
impl From<&str> for AllocatedLen {
fn from(text: &str) -> Self {
Self(text.len())
}
}
const _: () = assert!(usize::BITS <= 64);
const _: () = assert!(isize::BITS <= 64);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FramedSpan {
payload: AllocatedLen,
framing: NonZeroU32,
}
impl FramedSpan {
#[must_use]
pub const fn from_parts(payload: AllocatedLen, framing: NonZeroU32) -> Self {
Self { payload, framing }
}
#[must_use]
pub fn payload(self) -> u64 {
self.payload.get()
}
#[must_use]
pub fn stored(self) -> u64 {
self.payload.get() + u64::from(self.framing.get())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EntryStorage {
Directory,
Verbatim {
label: VerbatimLabel,
size: VerbatimSize,
},
Compressed {
method: CompressionMethod,
stored: Option<u64>,
expanded: Option<u64>,
},
}
impl EntryStorage {
#[must_use]
pub const fn verbatim(label: VerbatimLabel, size: u64) -> Self {
Self::Verbatim {
label,
size: VerbatimSize::Exact(size),
}
}
#[must_use]
pub const fn payload_only(label: VerbatimLabel, payload: u64) -> Self {
Self::Verbatim {
label,
size: VerbatimSize::PayloadOnly(payload),
}
}
pub fn framed(
label: VerbatimLabel,
payload: u64,
stored_span: u64,
) -> Result<Self, &'static str> {
let Some(framing) = stored_span.checked_sub(payload) else {
return Err("verbatim container entry stores fewer bytes than it expands to");
};
if framing == 0 {
return Ok(Self::Verbatim {
label,
size: VerbatimSize::Exact(stored_span),
});
}
let Some(payload) = AllocatedLen::new(payload) else {
return Err(LENGTH_OVER_ALLOCATION);
};
let Some(framing) = u32::try_from(framing).ok().and_then(NonZeroU32::new) else {
return Err("verbatim container entry declares more framing than a span can carry");
};
Ok(Self::Verbatim {
label,
size: VerbatimSize::Framed(FramedSpan::from_parts(payload, framing)),
})
}
#[must_use]
pub fn framed_by(label: VerbatimLabel, payload: AllocatedLen, framing: NonZeroU32) -> Self {
Self::Verbatim {
label,
size: VerbatimSize::Framed(FramedSpan::from_parts(payload, framing)),
}
}
#[must_use]
pub const fn unreported(label: VerbatimLabel) -> Self {
Self::Verbatim {
label,
size: VerbatimSize::Unreported,
}
}
#[must_use]
pub fn stored_size(&self) -> Option<u64> {
match self {
Self::Directory => None,
Self::Verbatim { size, .. } => match size {
VerbatimSize::Unreported | VerbatimSize::PayloadOnly(_) => None,
VerbatimSize::StoredOnly(size) | VerbatimSize::Exact(size) => Some(*size),
VerbatimSize::Framed(span) => Some(span.stored()),
},
Self::Compressed { stored, .. } => *stored,
}
}
#[must_use]
pub fn expanded_size(&self) -> Option<u64> {
match self {
Self::Directory => None,
Self::Verbatim { size, .. } => match size {
VerbatimSize::Unreported | VerbatimSize::StoredOnly(_) => None,
VerbatimSize::PayloadOnly(payload) | VerbatimSize::Exact(payload) => Some(*payload),
VerbatimSize::Framed(span) => Some(span.payload()),
},
Self::Compressed { expanded, .. } => *expanded,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[cfg_attr(feature = "schema", schemars(with = "ContainerEntryWire"))]
#[serde(from = "ContainerEntryWire", into = "ContainerEntryWire")]
pub struct ContainerEntry {
pub name: String,
pub role: ContainerRole,
pub storage: EntryStorage,
#[serde(deserialize_with = "crate::distinct_keys::btree_map")]
pub attributes: BTreeMap<String, String>,
}
impl ContainerEntry {
#[must_use]
pub fn stored_size(&self) -> Option<u64> {
self.storage.stored_size()
}
#[must_use]
pub fn expanded_size(&self) -> Option<u64> {
self.storage.expanded_size()
}
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct EntryIdentityWire {
name: String,
role: ContainerRole,
#[serde(default)]
#[serde(deserialize_with = "crate::distinct_keys::btree_map")]
attributes: BTreeMap<String, String>,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct CompressedSizesWire {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_stored"
)]
stored: Option<u64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_expanded"
)]
expanded: Option<u64>,
}
#[derive(Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "form", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
enum VerbatimSizeWire {
Unreported {},
PayloadOnly {
payload: u64,
},
StoredOnly {
stored: u64,
},
Exact {
size: u64,
},
Framed {
payload: AllocatedLen,
framing: NonZeroU32,
},
}
impl From<VerbatimSize> for VerbatimSizeWire {
fn from(size: VerbatimSize) -> Self {
match size {
VerbatimSize::Unreported => Self::Unreported {},
VerbatimSize::PayloadOnly(payload) => Self::PayloadOnly { payload },
VerbatimSize::StoredOnly(stored) => Self::StoredOnly { stored },
VerbatimSize::Exact(size) => Self::Exact { size },
VerbatimSize::Framed(span) => Self::Framed {
payload: span.payload,
framing: span.framing,
},
}
}
}
impl From<VerbatimSizeWire> for VerbatimSize {
fn from(wire: VerbatimSizeWire) -> Self {
match wire {
VerbatimSizeWire::Unreported {} => Self::Unreported,
VerbatimSizeWire::PayloadOnly { payload } => Self::PayloadOnly(payload),
VerbatimSizeWire::StoredOnly { stored } => Self::StoredOnly(stored),
VerbatimSizeWire::Exact { size } => Self::Exact(size),
VerbatimSizeWire::Framed { payload, framing } => {
Self::Framed(FramedSpan::from_parts(payload, framing))
}
}
}
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "compression")]
#[serde(deny_unknown_fields)]
enum ContainerEntryWire {
#[serde(rename = "storage")]
Directory {
#[serde(flatten)]
identity: EntryIdentityWire,
},
#[serde(rename = "none")]
VerbatimNone {
#[serde(flatten)]
identity: EntryIdentityWire,
size: VerbatimSizeWire,
},
#[serde(rename = "stored")]
VerbatimStored {
#[serde(flatten)]
identity: EntryIdentityWire,
size: VerbatimSizeWire,
},
#[serde(rename = "deflate")]
Deflate {
#[serde(flatten)]
identity: EntryIdentityWire,
#[serde(flatten)]
sizes: CompressedSizesWire,
},
#[serde(rename = "zstd")]
Zstd {
#[serde(flatten)]
identity: EntryIdentityWire,
#[serde(flatten)]
sizes: CompressedSizesWire,
},
#[serde(rename = "jpeg")]
Jpeg {
#[serde(flatten)]
identity: EntryIdentityWire,
#[serde(flatten)]
sizes: CompressedSizesWire,
},
#[serde(rename = "unix-compress")]
UnixCompress {
#[serde(flatten)]
identity: EntryIdentityWire,
#[serde(flatten)]
sizes: CompressedSizesWire,
},
#[serde(rename = "zlib")]
Zlib {
#[serde(flatten)]
identity: EntryIdentityWire,
#[serde(flatten)]
sizes: CompressedSizesWire,
},
}
impl From<ContainerEntry> for ContainerEntryWire {
fn from(entry: ContainerEntry) -> Self {
let identity = EntryIdentityWire {
name: entry.name,
role: entry.role,
attributes: entry.attributes,
};
match entry.storage {
EntryStorage::Directory => Self::Directory { identity },
EntryStorage::Verbatim { label, size } => {
let size = size.into();
match label {
VerbatimLabel::None => Self::VerbatimNone { identity, size },
VerbatimLabel::Stored => Self::VerbatimStored { identity, size },
}
}
EntryStorage::Compressed {
method,
stored,
expanded,
} => {
let sizes = CompressedSizesWire { stored, expanded };
match method {
CompressionMethod::Deflate => Self::Deflate { identity, sizes },
CompressionMethod::Zstd => Self::Zstd { identity, sizes },
CompressionMethod::Jpeg => Self::Jpeg { identity, sizes },
CompressionMethod::UnixCompress => Self::UnixCompress { identity, sizes },
CompressionMethod::Zlib => Self::Zlib { identity, sizes },
}
}
}
}
}
impl From<ContainerEntryWire> for ContainerEntry {
fn from(wire: ContainerEntryWire) -> Self {
let verbatim = |label: VerbatimLabel, size: VerbatimSizeWire| EntryStorage::Verbatim {
label,
size: size.into(),
};
let compressed =
|method: CompressionMethod, sizes: CompressedSizesWire| EntryStorage::Compressed {
method,
stored: sizes.stored,
expanded: sizes.expanded,
};
let (identity, storage) = match wire {
ContainerEntryWire::Directory { identity } => (identity, EntryStorage::Directory),
ContainerEntryWire::VerbatimNone { identity, size } => {
(identity, verbatim(VerbatimLabel::None, size))
}
ContainerEntryWire::VerbatimStored { identity, size } => {
(identity, verbatim(VerbatimLabel::Stored, size))
}
ContainerEntryWire::Deflate { identity, sizes } => {
(identity, compressed(CompressionMethod::Deflate, sizes))
}
ContainerEntryWire::Zstd { identity, sizes } => {
(identity, compressed(CompressionMethod::Zstd, sizes))
}
ContainerEntryWire::Jpeg { identity, sizes } => {
(identity, compressed(CompressionMethod::Jpeg, sizes))
}
ContainerEntryWire::UnixCompress { identity, sizes } => {
(identity, compressed(CompressionMethod::UnixCompress, sizes))
}
ContainerEntryWire::Zlib { identity, sizes } => {
(identity, compressed(CompressionMethod::Zlib, sizes))
}
};
Self {
name: identity.name,
role: identity.role,
storage,
attributes: identity.attributes,
}
}
}
#[cfg(test)]
mod tests {
use super::u64_from_index;
use super::{
CompressionMethod, ContainerEntry, ContainerRole, EntryStorage, VerbatimLabel, VerbatimSize,
};
use std::collections::BTreeMap;
use std::num::{NonZeroU32, NonZeroU64};
fn verbatim_wire(compression: &str, size: &serde_json::Value) -> serde_json::Value {
serde_json::json!({
"name": "entry",
"role": "stream",
"compression": compression,
"size": size.clone(),
})
}
fn compressed_wire(
compression: &str,
stored: Option<u64>,
expanded: Option<u64>,
) -> serde_json::Value {
let mut value = serde_json::json!({
"name": "entry",
"role": "stream",
"compression": compression,
});
if let Some(stored) = stored {
value["stored"] = stored.into();
}
if let Some(expanded) = expanded {
value["expanded"] = expanded.into();
}
value
}
fn admit(wire: serde_json::Value) -> ContainerEntry {
serde_json::from_value(wire).expect("the declared sizes are admissible")
}
fn reject(wire: serde_json::Value) -> String {
serde_json::from_value::<ContainerEntry>(wire)
.expect_err("the declared sizes are inadmissible")
.to_string()
}
fn nonzero(value: u64) -> NonZeroU64 {
NonZeroU64::new(value).expect("a nonzero test size")
}
fn entry(storage: EntryStorage) -> ContainerEntry {
ContainerEntry {
name: "entry".to_string(),
role: ContainerRole::Stream,
storage,
attributes: BTreeMap::new(),
}
}
fn framed(payload: u64, framing: u32) -> VerbatimSize {
VerbatimSize::Framed(super::FramedSpan::from_parts(
super::AllocatedLen::new(payload).expect("a live payload length"),
NonZeroU32::new(framing).expect("a nonzero test framing"),
))
}
#[test]
fn each_declared_verbatim_size_has_one_spelling() {
for (size, expected) in [
(
serde_json::json!({"form": "unreported"}),
VerbatimSize::Unreported,
),
(
serde_json::json!({"form": "payload_only", "payload": 12}),
VerbatimSize::PayloadOnly(12),
),
(
serde_json::json!({"form": "payload_only", "payload": 0}),
VerbatimSize::PayloadOnly(0),
),
(
serde_json::json!({"form": "stored_only", "stored": 5}),
VerbatimSize::StoredOnly(5),
),
(
serde_json::json!({"form": "exact", "size": 7}),
VerbatimSize::Exact(7),
),
(
serde_json::json!({"form": "exact", "size": 0}),
VerbatimSize::Exact(0),
),
(
serde_json::json!({"form": "framed", "payload": 10, "framing": 20}),
framed(10, 20),
),
(
serde_json::json!({"form": "framed", "payload": 0, "framing": 5}),
framed(0, 5),
),
] {
let storage = EntryStorage::Verbatim {
label: VerbatimLabel::None,
size: expected,
};
let read = admit(verbatim_wire("none", &size));
assert_eq!(read.storage, storage, "{size}");
let mut expected_wire = verbatim_wire("none", &size);
expected_wire["attributes"] = serde_json::json!({});
assert_eq!(
serde_json::to_value(entry(storage)).expect("a container entry serializes"),
expected_wire,
"{size}"
);
}
}
#[test]
fn a_framed_payload_carries_no_stored_span_to_disagree_with() {
let error = reject(verbatim_wire(
"none",
&serde_json::json!({"form": "framed", "payload": 9, "framing": 1, "stored": 5}),
));
assert!(error.contains("unknown field"), "{error}");
assert!(error.contains("stored"), "{error}");
let error = reject(verbatim_wire(
"none",
&serde_json::json!({"form": "framed", "payload": 9, "framing": 0}),
));
assert!(error.contains("zero"), "{error}");
let error = reject(verbatim_wire(
"none",
&serde_json::json!({"form": "framed", "payload": u64::MAX, "framing": 1}),
));
assert!(
error.contains("declared length exceeds the largest live allocation"),
"{error}"
);
}
#[test]
fn a_container_door_refuses_a_span_under_its_payload() {
assert_eq!(
EntryStorage::framed(VerbatimLabel::Stored, 9, 5),
Err("verbatim container entry stores fewer bytes than it expands to")
);
assert_eq!(
EntryStorage::framed(VerbatimLabel::Stored, 9, 9),
Ok(EntryStorage::verbatim(VerbatimLabel::Stored, 9))
);
assert_eq!(
EntryStorage::framed(VerbatimLabel::Stored, 10, 30),
Ok(EntryStorage::Verbatim {
label: VerbatimLabel::Stored,
size: framed(10, 20),
})
);
assert_eq!(
EntryStorage::framed(VerbatimLabel::Stored, 0, u64::from(u32::MAX) + 1),
Err("verbatim container entry declares more framing than a span can carry")
);
}
#[test]
fn compressed_sizes_spell_unreported_only_as_absence() {
for (stored, expanded) in [
(None, None),
(None, Some(99)),
(Some(44), None),
(Some(4), Some(16)),
(Some(0), Some(0)),
] {
let storage = EntryStorage::Compressed {
method: CompressionMethod::Deflate,
stored,
expanded,
};
let mut expected = compressed_wire("deflate", stored, expanded);
expected["attributes"] = serde_json::json!({});
let serialized =
serde_json::to_value(entry(storage.clone())).expect("a container entry serializes");
assert_eq!(serialized, expected);
let read_back: ContainerEntry =
serde_json::from_value(serialized).expect("the wire value re-admits");
assert_eq!(read_back.storage, storage);
}
}
#[test]
fn a_minted_framed_span_always_exceeds_its_payload() {
let empty: &[u8] = &[];
let span = super::FramedSpan::from_parts(empty.into(), NonZeroU32::MAX);
assert_eq!(span.payload(), 0);
assert_eq!(
nonzero(span.stored() - span.payload()),
nonzero(u64::from(u32::MAX))
);
assert_eq!(span.stored(), u64::from(u32::MAX));
assert!(span.stored() > span.payload());
let span = super::FramedSpan::from_parts(empty.into(), NonZeroU32::MIN);
assert_eq!(span.stored(), 1);
let body = vec![0u8; 12];
let span = super::FramedSpan::from_parts(body.as_slice().into(), NonZeroU32::MIN);
assert_eq!(span.payload(), u64_from_index(body.len()));
assert_eq!(span.stored(), 13);
assert_eq!(nonzero(span.stored() - span.payload()), nonzero(1));
let span = super::FramedSpan::from_parts("target.CATPart".into(), NonZeroU32::MAX);
assert_eq!(span.payload(), u64_from_index("target.CATPart".len()));
assert_eq!(
span.stored(),
u64_from_index("target.CATPart".len()) + u64::from(u32::MAX)
);
}
#[test]
fn a_reported_zero_size_is_not_an_unreported_one() {
let reported = admit(verbatim_wire(
"none",
&serde_json::json!({"form": "exact", "size": 0}),
));
let unreported = admit(verbatim_wire(
"none",
&serde_json::json!({"form": "unreported"}),
));
assert_ne!(reported.storage, unreported.storage);
assert_eq!(reported.stored_size(), Some(0));
assert_eq!(reported.expanded_size(), Some(0));
assert_eq!(unreported.stored_size(), None);
assert_eq!(unreported.expanded_size(), None);
assert_ne!(
EntryStorage::verbatim(VerbatimLabel::None, 0),
EntryStorage::unreported(VerbatimLabel::None)
);
}
#[test]
fn a_storage_label_cannot_declare_a_byte_size() {
let error = reject(verbatim_wire(
"storage",
&serde_json::json!({"form": "exact", "size": 4}),
));
assert!(error.contains("unknown field"), "{error}");
assert!(error.contains("size"), "{error}");
let error = reject(compressed_wire("storage", Some(4), Some(7)));
assert!(error.contains("unknown field"), "{error}");
let entry: ContainerEntry = serde_json::from_value(serde_json::json!({
"name": "entry",
"role": "stream",
"compression": "storage",
}))
.expect("a directory node declares no size");
assert_eq!(entry.storage, EntryStorage::Directory);
}
#[test]
fn reported_sizes_are_absent_rather_than_zero() {
let payload_only = admit(verbatim_wire(
"none",
&serde_json::json!({"form": "payload_only", "payload": 12}),
));
assert_eq!(payload_only.stored_size(), None);
assert_eq!(payload_only.expanded_size(), Some(12));
let framed_entry = admit(verbatim_wire(
"none",
&serde_json::json!({"form": "framed", "payload": 10, "framing": 20}),
));
assert_eq!(framed_entry.stored_size(), Some(30));
assert_eq!(framed_entry.expanded_size(), Some(10));
let compressed = admit(compressed_wire("zlib", None, Some(99)));
assert_eq!(compressed.stored_size(), None);
assert_eq!(compressed.expanded_size(), Some(99));
}
#[test]
fn a_container_entry_refuses_an_unknown_key_beside_its_name() {
let wire = serde_json::json!({
"name": "entry",
"role": "stream",
"compression": "none",
"size": {"form": "unreported"},
});
serde_json::from_value::<ContainerEntry>(wire.clone()).expect("a legal entry");
let mut stray = wire;
stray["zz_bogus"] = serde_json::json!(1);
let error = serde_json::from_value::<ContainerEntry>(stray)
.expect_err("an unknown key has no encoding")
.to_string();
assert!(error.contains("zz_bogus"), "{error}");
}
}
crate::named_optional_field!(deserialize_stored, u64, "stored");
crate::named_optional_field!(deserialize_expanded, u64, "expanded");