Skip to main content

hdf5_pure/
message_type.rs

1//! HDF5 object header message type identifiers.
2
3use core::fmt;
4
5/// An HDF5 object header message type, as carried by
6/// [`Error::MissingMessage`](crate::Error::MissingMessage).
7///
8/// Every piece of an object's metadata — its dataspace, its datatype, where its
9/// data lives — is a message in its object header, identified by one of these.
10/// A type this crate does not recognize is reported as [`Unknown`](Self::Unknown)
11/// with its raw identifier rather than discarded.
12///
13/// `#[non_exhaustive]` because a type now reported as `Unknown` may later get a
14/// name of its own, and that should not be a breaking change.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum MessageType {
18    Nil,
19    Dataspace,
20    LinkInfo,
21    Datatype,
22    FillValueOld,
23    FillValue,
24    Link,
25    /// External Data Files: the dataset's elements live in files outside this
26    /// one (`H5Pset_external`). The message names them; this crate does not
27    /// follow them, and refuses to read such a dataset rather than answering the
28    /// fill value its address-less contiguous layout would otherwise imply.
29    ExternalDataFiles,
30    DataLayout,
31    GroupInfo,
32    FilterPipeline,
33    Attribute,
34    ObjectHeaderContinuation,
35    SymbolTable,
36    ObjectModificationTime,
37    BTreeKValues,
38    SharedMessageTable,
39    AttributeInfo,
40    ObjectReferenceCount,
41    FileSpaceInfo,
42    /// Unknown message type with its raw type ID.
43    Unknown(u16),
44}
45
46impl MessageType {
47    /// Convert a raw u16 type ID to a `MessageType`.
48    pub fn from_u16(val: u16) -> MessageType {
49        match val {
50            0x0000 => MessageType::Nil,
51            0x0001 => MessageType::Dataspace,
52            0x0002 => MessageType::LinkInfo,
53            0x0003 => MessageType::Datatype,
54            0x0004 => MessageType::FillValueOld,
55            0x0005 => MessageType::FillValue,
56            0x0006 => MessageType::Link,
57            0x0007 => MessageType::ExternalDataFiles,
58            0x0008 => MessageType::DataLayout,
59            0x000A => MessageType::GroupInfo,
60            0x000B => MessageType::FilterPipeline,
61            0x000C => MessageType::Attribute,
62            0x000F => MessageType::SharedMessageTable,
63            0x0010 => MessageType::ObjectHeaderContinuation,
64            0x0011 => MessageType::SymbolTable,
65            0x0012 => MessageType::ObjectModificationTime,
66            0x0013 => MessageType::BTreeKValues,
67            0x0015 => MessageType::AttributeInfo,
68            0x0016 => MessageType::ObjectReferenceCount,
69            0x0017 => MessageType::FileSpaceInfo,
70            other => MessageType::Unknown(other),
71        }
72    }
73
74    /// Convert back to the raw u16 type ID.
75    pub fn to_u16(self) -> u16 {
76        match self {
77            MessageType::Nil => 0x0000,
78            MessageType::Dataspace => 0x0001,
79            MessageType::LinkInfo => 0x0002,
80            MessageType::Datatype => 0x0003,
81            MessageType::FillValueOld => 0x0004,
82            MessageType::FillValue => 0x0005,
83            MessageType::Link => 0x0006,
84            MessageType::ExternalDataFiles => 0x0007,
85            MessageType::DataLayout => 0x0008,
86            MessageType::GroupInfo => 0x000A,
87            MessageType::FilterPipeline => 0x000B,
88            MessageType::Attribute => 0x000C,
89            MessageType::SharedMessageTable => 0x000F,
90            MessageType::ObjectHeaderContinuation => 0x0010,
91            MessageType::SymbolTable => 0x0011,
92            MessageType::ObjectModificationTime => 0x0012,
93            MessageType::BTreeKValues => 0x0013,
94            MessageType::AttributeInfo => 0x0015,
95            MessageType::ObjectReferenceCount => 0x0016,
96            MessageType::FileSpaceInfo => 0x0017,
97            MessageType::Unknown(v) => v,
98        }
99    }
100}
101
102impl fmt::Display for MessageType {
103    /// The message name as the format specification writes it.
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        let name = match self {
106            Self::Nil => "NIL",
107            Self::Dataspace => "dataspace",
108            Self::LinkInfo => "link info",
109            Self::Datatype => "datatype",
110            Self::FillValueOld => "fill value (old)",
111            Self::FillValue => "fill value",
112            Self::Link => "link",
113            Self::ExternalDataFiles => "external data files",
114            Self::DataLayout => "data layout",
115            Self::GroupInfo => "group info",
116            Self::FilterPipeline => "filter pipeline",
117            Self::Attribute => "attribute",
118            Self::ObjectHeaderContinuation => "object header continuation",
119            Self::SymbolTable => "symbol table",
120            Self::ObjectModificationTime => "object modification time",
121            Self::BTreeKValues => "B-tree K values",
122            Self::SharedMessageTable => "shared message table",
123            Self::AttributeInfo => "attribute info",
124            Self::ObjectReferenceCount => "object reference count",
125            Self::FileSpaceInfo => "file space info",
126            Self::Unknown(id) => return write!(f, "unknown message 0x{id:04x}"),
127        };
128        // `write_str`, not `pad`: the arm above cannot honor a width without
129        // building the string first, and a type that pads on some values and
130        // not others is worse than one that never pads.
131        f.write_str(name)
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn known_types_roundtrip() {
141        let known = [
142            (0x0000, MessageType::Nil),
143            (0x0001, MessageType::Dataspace),
144            (0x0002, MessageType::LinkInfo),
145            (0x0003, MessageType::Datatype),
146            (0x0004, MessageType::FillValueOld),
147            (0x0005, MessageType::FillValue),
148            (0x0006, MessageType::Link),
149            (0x0007, MessageType::ExternalDataFiles),
150            (0x0008, MessageType::DataLayout),
151            (0x000A, MessageType::GroupInfo),
152            (0x000B, MessageType::FilterPipeline),
153            (0x000C, MessageType::Attribute),
154            (0x000F, MessageType::SharedMessageTable),
155            (0x0010, MessageType::ObjectHeaderContinuation),
156            (0x0011, MessageType::SymbolTable),
157            (0x0012, MessageType::ObjectModificationTime),
158            (0x0013, MessageType::BTreeKValues),
159            (0x0015, MessageType::AttributeInfo),
160            (0x0016, MessageType::ObjectReferenceCount),
161            (0x0017, MessageType::FileSpaceInfo),
162        ];
163        for (val, expected) in &known {
164            let mt = MessageType::from_u16(*val);
165            assert_eq!(mt, *expected);
166            assert_eq!(mt.to_u16(), *val);
167        }
168    }
169
170    #[test]
171    fn unknown_type() {
172        let mt = MessageType::from_u16(0x00FF);
173        assert_eq!(mt, MessageType::Unknown(0x00FF));
174        assert_eq!(mt.to_u16(), 0x00FF);
175    }
176
177    /// 0x0009 is the specification's Bogus message — "For testing only; should
178    /// never be stored in a valid file" — so it stays unnamed on purpose, and a
179    /// file carrying one is a file worth refusing. Being unnamed is not itself a
180    /// claim that a type is undefined: 0x000D (object comment), 0x000E (old
181    /// object modification time) and 0x0014 (driver info) are all defined and
182    /// unnamed here, for the ordinary reason that nothing in this crate reads
183    /// them.
184    ///
185    /// This test used to assert the same of 0x0007 under a comment reading
186    /// "0x0007 is not a defined type". It is External Data Files, which the C
187    /// library writes for every dataset created with `H5Pset_external`, and that
188    /// comment is the mistaken basis on which the message went unnamed and so
189    /// silently ignored (issue #331).
190    #[test]
191    fn the_specifications_testing_only_type_stays_unknown() {
192        let mt = MessageType::from_u16(0x0009);
193        assert_eq!(mt, MessageType::Unknown(0x0009));
194    }
195
196    #[test]
197    fn external_data_files_is_a_named_type() {
198        let mt = MessageType::from_u16(0x0007);
199        assert_eq!(mt, MessageType::ExternalDataFiles);
200        assert_eq!(mt.to_u16(), 0x0007);
201    }
202}
203
204#[cfg(all(test, feature = "std"))]
205mod display_tests {
206    use super::*;
207
208    /// `Error::MissingMessage` quotes this, so it must read as prose rather
209    /// than as a Rust variant name.
210    #[test]
211    fn known_messages_read_as_prose() {
212        assert_eq!(MessageType::Dataspace.to_string(), "dataspace");
213        assert_eq!(MessageType::LinkInfo.to_string(), "link info");
214        assert_eq!(
215            MessageType::ObjectHeaderContinuation.to_string(),
216            "object header continuation"
217        );
218    }
219
220    #[test]
221    fn an_unknown_message_reports_its_raw_identifier() {
222        assert_eq!(
223            MessageType::Unknown(0x00ff).to_string(),
224            "unknown message 0x00ff"
225        );
226    }
227
228    /// A message this crate names must not fall through to the raw-identifier
229    /// wording, which would hide that the type is in fact recognized.
230    #[test]
231    fn every_known_message_has_a_name() {
232        for raw in 0x0000..=0x0017u16 {
233            let message = MessageType::from_u16(raw);
234            if message == MessageType::Unknown(raw) {
235                continue;
236            }
237            let shown = message.to_string();
238            assert!(!shown.starts_with("unknown message"), "{raw:#06x}: {shown}");
239        }
240    }
241}