Skip to main content

hdf5_pure/
message_type.rs

1//! Defines HDF5 object header message type identifiers.
2//!
3//! [`MessageType`] preserves every raw identifier and gives symbolic names to the message types
4//! this crate understands. Object header message types are defined in "Data Object Header Messages"
5//! of the [format specification, version 4.0][spec].
6//!
7//! [spec]: https://support.hdfgroup.org/documentation/hdf5/latest/_f_m_t4.html#subsec_fmt4_dataobject_hdr_msg
8
9use core::fmt;
10
11/// Identifies an HDF5 object header message type.
12///
13/// Every [`u16`] is a valid `MessageType`, so an identifier that this crate does not recognize
14/// remains representable without losing its raw value. Recognized identifiers are exposed as
15/// associated constants such as [`Self::DATASPACE`].
16///
17/// `MessageType` is carried by [`Error::MissingMessage`](crate::Error::MissingMessage). Its
18/// transparent representation gives it the same size, alignment, and ABI as [`u16`].
19///
20/// The format specification calls the on-disk value the "Header Message Type" and defines its
21/// values in "Data Object Header Messages" of the [format specification, version 4.0][spec].
22///
23/// [spec]: https://support.hdfgroup.org/documentation/hdf5/latest/_f_m_t4.html#subsec_fmt4_dataobject_hdr_msg
24#[repr(transparent)]
25#[derive(Clone, Copy, PartialEq, Eq, Hash)]
26pub struct MessageType(u16);
27
28macro_rules! message_types {
29    (
30        $(
31            $(#[$meta:meta])*
32            $constant:ident / $compat:ident = $value:literal => $display:literal;
33        )+
34    ) => {
35        impl MessageType {
36            $(
37                $(#[$meta])*
38                pub const $constant: Self = Self($value);
39            )+
40
41            #[inline]
42            const fn display_name(self) -> Option<&'static str> {
43                match self {
44                    $(
45                        Self::$constant => Some($display),
46                    )+
47                    _ => None,
48                }
49            }
50
51            /// Returns whether this crate recognizes the message type.
52            ///
53            /// Recognition means that the identifier has an associated constant and a symbolic
54            /// name in this module. An identifier can be defined by the HDF5 specification without
55            /// being recognized by this crate.
56            #[inline]
57            pub const fn is_known(self) -> bool {
58                matches!(self, $(Self::$constant)|+)
59            }
60        }
61
62        impl fmt::Debug for MessageType {
63            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64                match *self {
65                    $(
66                        Self::$constant => f.write_str(stringify!($compat)),
67                    )+
68                    other => f.debug_tuple("Unknown").field(&other.0).finish(),
69                }
70            }
71        }
72
73        // Duplicate discriminants are rejected by the compiler, so this enum validates that every
74        // recognized message in the registry has a distinct raw identifier.
75        #[allow(dead_code)]
76        #[repr(u16)]
77        enum KnownMessageTypeDiscriminant {
78            $(
79                $compat = $value,
80            )+
81        }
82
83        #[allow(non_upper_case_globals)]
84        impl MessageType {
85            $(
86                #[doc(hidden)]
87                #[deprecated(note = "use the uppercase MessageType associated constant")]
88                $(#[$meta])*
89                pub const $compat: Self = Self::$constant;
90            )+
91        }
92
93        #[cfg(test)]
94        const KNOWN: &[(u16, MessageType, &'static str, &'static str)] = &[
95            $(
96                (
97                    $value,
98                    MessageType::$constant,
99                    $display,
100                    stringify!($compat),
101                ),
102            )+
103        ];
104    };
105}
106
107message_types! {
108    /// Identifies an object header message that the reader ignores.
109    NIL / Nil = 0x0000 => "NIL";
110
111    /// Identifies the dimensionality and extents of a dataset.
112    DATASPACE / Dataspace = 0x0001 => "dataspace";
113
114    /// Identifies the storage and indexing information for a group's links.
115    LINK_INFO / LinkInfo = 0x0002 => "link info";
116
117    /// Identifies the datatype of a dataset or committed datatype.
118    DATATYPE / Datatype = 0x0003 => "datatype";
119
120    /// Identifies a fill value encoded in the original fill-value message format.
121    FILL_VALUE_OLD / FillValueOld = 0x0004 => "fill value (old)";
122
123    /// Identifies the value used to fill unwritten dataset elements.
124    FILL_VALUE / FillValue = 0x0005 => "fill value";
125
126    /// Identifies a link stored in an object header.
127    LINK / Link = 0x0006 => "link";
128
129    /// Identifies dataset data stored in files outside the HDF5 file.
130    ///
131    /// The message lists the external files and the portions of those files used for the dataset.
132    /// This crate does not follow external files and rejects a dataset that uses them.
133    ///
134    /// `H5Pset_external` configures this storage in the C library. The message is defined in
135    /// "The Data Storage - External Data Files Message" of the
136    /// [format specification, version 4.0][spec].
137    ///
138    /// [spec]: https://support.hdfgroup.org/documentation/hdf5/latest/_f_m_t4.html#subsubsec_fmt4_dataobject_hdr_msg_external
139    EXTERNAL_DATA_FILES / ExternalDataFiles = 0x0007 => "external data files";
140
141    /// Identifies how a dataset's raw data is stored.
142    DATA_LAYOUT / DataLayout = 0x0008 => "data layout";
143
144    // 0x0009 is the Bogus message. "The Bogus Message" of the format specification, version 4.0,
145    // reserves it for testing and says that it should never be stored in a valid file.
146
147    /// Identifies storage parameters for a group.
148    GROUP_INFO / GroupInfo = 0x000A => "group info";
149
150    /// Identifies the filter pipeline applied to a dataset's raw data.
151    FILTER_PIPELINE / FilterPipeline = 0x000B => "filter pipeline";
152
153    /// Identifies an attribute stored in an object header.
154    ATTRIBUTE / Attribute = 0x000C => "attribute";
155
156    // "Data Object Header Messages" of the format specification, version 4.0, defines 0x000D as
157    // Object Comment and 0x000E as Object Modification Time (Old). This crate does not interpret
158    // either message.
159
160    /// Identifies the table used to locate shared object header messages.
161    SHARED_MESSAGE_TABLE / SharedMessageTable = 0x000F => "shared message table";
162
163    /// Identifies a block that contains additional object header messages.
164    OBJECT_HEADER_CONTINUATION / ObjectHeaderContinuation =
165        0x0010 => "object header continuation";
166
167    /// Identifies the symbol-table storage used by an old-style group.
168    SYMBOL_TABLE / SymbolTable = 0x0011 => "symbol table";
169
170    /// Identifies the last modification time of an object.
171    OBJECT_MODIFICATION_TIME / ObjectModificationTime =
172        0x0012 => "object modification time";
173
174    /// Identifies non-default B-tree K values stored with an object.
175    B_TREE_K_VALUES / BTreeKValues = 0x0013 => "B-tree K values";
176
177    // "The Driver Info Message" of the format specification, version 4.0, defines 0x0014. This
178    // crate does not interpret that message.
179
180    /// Identifies the storage and indexing information for densely stored attributes.
181    ATTRIBUTE_INFO / AttributeInfo = 0x0015 => "attribute info";
182
183    /// Identifies the number of hard links that point to an object.
184    OBJECT_REFERENCE_COUNT / ObjectReferenceCount =
185        0x0016 => "object reference count";
186
187    /// Identifies the information used to manage free space in the file.
188    FILE_SPACE_INFO / FileSpaceInfo = 0x0017 => "file space info";
189}
190
191impl MessageType {
192    /// Constructs a message type without interpreting the raw identifier.
193    ///
194    /// Every [`u16`] is accepted. Use [`Self::is_known`] or [`Self::unknown_id`] when the
195    /// distinction between recognized and unrecognized identifiers matters.
196    #[inline]
197    pub const fn from_u16(value: u16) -> Self {
198        Self(value)
199    }
200
201    /// Returns the raw HDF5 message type identifier.
202    #[inline]
203    pub const fn to_u16(self) -> u16 {
204        self.0
205    }
206
207    /// Returns whether this crate does not recognize the message type.
208    ///
209    /// An unrecognized identifier can still be defined by the HDF5 specification. It has no
210    /// associated symbolic name in this crate.
211    #[inline]
212    pub const fn is_unknown(self) -> bool {
213        !self.is_known()
214    }
215
216    /// Returns the raw identifier when this crate does not recognize the message type.
217    ///
218    /// Returns [`None`] when [`Self::is_known`] is `true`.
219    #[inline]
220    pub const fn unknown_id(self) -> Option<u16> {
221        if self.is_known() { None } else { Some(self.0) }
222    }
223
224    /// Constructs a message type without interpreting the raw identifier.
225    ///
226    /// The value can identify either a recognized or an unrecognized message. This function is
227    /// equivalent to [`Self::from_u16`].
228    #[doc(hidden)]
229    #[deprecated(note = "use MessageType::from_u16")]
230    #[allow(non_snake_case)]
231    #[inline]
232    pub const fn Unknown(value: u16) -> Self {
233        Self(value)
234    }
235}
236
237impl From<u16> for MessageType {
238    #[inline]
239    fn from(value: u16) -> Self {
240        Self(value)
241    }
242}
243
244impl From<MessageType> for u16 {
245    #[inline]
246    fn from(value: MessageType) -> Self {
247        value.0
248    }
249}
250
251impl fmt::Display for MessageType {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        match self.display_name() {
254            Some(name) => f.write_str(name),
255            None => write!(f, "unknown message 0x{:04x}", self.0),
256        }
257    }
258}
259
260// `repr(transparent)` guarantees these properties. The assertions make the representation
261// requirement explicit at compile time.
262const _: () = {
263    assert!(core::mem::size_of::<MessageType>() == core::mem::size_of::<u16>());
264    assert!(core::mem::align_of::<MessageType>() == core::mem::align_of::<u16>());
265};
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn representation_matches_u16() {
273        assert_eq!(
274            core::mem::size_of::<MessageType>(),
275            core::mem::size_of::<u16>()
276        );
277        assert_eq!(
278            core::mem::align_of::<MessageType>(),
279            core::mem::align_of::<u16>()
280        );
281    }
282
283    #[test]
284    fn every_raw_identifier_roundtrips() {
285        for raw in u16::MIN..=u16::MAX {
286            let message = MessageType::from_u16(raw);
287
288            assert_eq!(message.to_u16(), raw);
289            assert_eq!(u16::from(MessageType::from(raw)), raw);
290        }
291    }
292
293    #[test]
294    fn known_identifiers_are_strictly_increasing() {
295        for pair in KNOWN.windows(2) {
296            let previous = pair[0].0;
297            let next = pair[1].0;
298
299            assert!(previous < next, "{previous:#06x} must precede {next:#06x}");
300        }
301    }
302
303    #[test]
304    fn known_types_roundtrip() {
305        for &(raw, expected, display, _) in KNOWN {
306            let message = MessageType::from_u16(raw);
307
308            assert_eq!(message, expected);
309            assert_eq!(message.to_u16(), raw);
310            assert!(message.is_known());
311            assert!(!message.is_unknown());
312            assert_eq!(message.unknown_id(), None);
313            assert_eq!(message.display_name(), Some(display));
314        }
315    }
316
317    #[test]
318    fn unknown_type_preserves_its_identifier() {
319        let message = MessageType::from_u16(0x00FF);
320
321        assert_eq!(message.to_u16(), 0x00FF);
322        assert!(!message.is_known());
323        assert!(message.is_unknown());
324        assert_eq!(message.unknown_id(), Some(0x00FF));
325        assert_eq!(message.display_name(), None);
326    }
327
328    #[test]
329    fn bogus_message_is_not_recognized() {
330        let message = MessageType::from_u16(0x0009);
331
332        assert!(message.is_unknown());
333        assert_eq!(message.unknown_id(), Some(0x0009));
334    }
335
336    #[test]
337    fn defined_but_unsupported_types_are_not_recognized() {
338        for raw in [0x000D, 0x000E, 0x0014] {
339            let message = MessageType::from_u16(raw);
340
341            assert!(message.is_unknown(), "{raw:#06x}");
342            assert_eq!(message.unknown_id(), Some(raw));
343            assert_eq!(message.to_u16(), raw);
344        }
345    }
346
347    #[test]
348    fn external_data_files_is_recognized() {
349        let message = MessageType::from_u16(0x0007);
350
351        assert_eq!(message, MessageType::EXTERNAL_DATA_FILES);
352        assert_eq!(message.to_u16(), 0x0007);
353        assert_eq!(message.display_name(), Some("external data files"));
354    }
355
356    #[test]
357    fn from_conversions_are_lossless() {
358        let message = MessageType::from(0x1234);
359
360        assert_eq!(message.to_u16(), 0x1234);
361        assert_eq!(u16::from(message), 0x1234);
362    }
363
364    #[test]
365    fn camel_case_constants_work_as_patterns() {
366        let message = MessageType::from_u16(0x0001);
367
368        assert!(matches!(message, MessageType::DATASPACE));
369    }
370
371    #[test]
372    fn unknown_constructor_preserves_the_identifier() {
373        assert_eq!(MessageType::from(0x00FF), MessageType::from_u16(0x00FF));
374    }
375}
376
377#[cfg(all(test, feature = "std"))]
378mod display_tests {
379    use super::*;
380
381    #[test]
382    fn every_known_message_uses_its_format_name() {
383        for &(raw, message, display, _) in KNOWN {
384            assert_eq!(message.to_string(), display, "{raw:#06x}");
385        }
386    }
387
388    #[test]
389    fn an_unknown_message_reports_its_raw_identifier() {
390        assert_eq!(
391            MessageType::from_u16(0x00FF).to_string(),
392            "unknown message 0x00ff"
393        );
394    }
395
396    #[test]
397    fn every_known_message_uses_its_symbolic_debug_name() {
398        for &(raw, message, _, debug) in KNOWN {
399            assert_eq!(format!("{message:?}"), debug, "{raw:#06x}");
400        }
401    }
402
403    #[test]
404    fn an_unknown_message_uses_the_unknown_debug_form() {
405        assert_eq!(
406            format!("{:?}", MessageType::from_u16(0x00FF)),
407            "Unknown(255)"
408        );
409    }
410}