Skip to main content

rakata_formats/schema/
wire.rs

1//! The GFF wire type set, and the mapping from a runtime value to it.
2//!
3//! Emitted from the one declaration in [`gff`](crate::gff) that also produces
4//! the value enum and the serde DTO, so every value has a type and no value
5//! has to be filed under a near-miss.
6
7use crate::gff::{gff_field_types, GffValue};
8
9macro_rules! emit_gff_type {
10    ($($variant:ident = $id:literal, $wire:literal, $description:literal, $value:ty,
11        $(#[$dto_attr:meta])* $dto:ty;)*) => {
12        /// Expected GFF field type, one variant per [`GffValue`] variant.
13        ///
14        /// Both come from the same declaration, so every value has a type and
15        /// no value has to be filed under a near-miss.
16        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17        pub enum GffType {
18            $(
19                #[doc = concat!("`", $wire, "`: ", $description, ".")]
20                $variant,
21            )*
22        }
23
24        impl GffType {
25            /// Every wire type, in the order the format declares them.
26            ///
27            /// Emitted from the same declaration as the variants, so a type
28            /// added to the format appears here without anyone remembering to
29            /// add it. A caller offering a user the choice of type reads this
30            /// rather than writing its own list, which is the copy that would
31            /// silently stop being all of them.
32            pub const ALL: &'static [Self] = &[$(GffType::$variant,)*];
33
34            /// The name the GFF wire format gives this type, such as `BYTE` or
35            /// `CExoLocString`.
36            pub fn name(self) -> &'static str {
37                match self {
38                    $(GffType::$variant => $wire,)*
39                }
40            }
41        }
42
43        /// Returns the [`GffType`] that a [`GffValue`] carries.
44        pub fn gff_value_type(value: &GffValue) -> GffType {
45            match value {
46                $(GffValue::$variant(_) => GffType::$variant,)*
47            }
48        }
49    };
50}
51
52gff_field_types!(emit_gff_type);
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    /// `ALL` really is all of them, checked against the value enum rather than
59    /// against a second list.
60    ///
61    /// Both come out of one declaration, so this cannot drift. What it does
62    /// catch is the macro being edited to emit one and not the other, which is
63    /// the only way they could come apart.
64    #[test]
65    fn every_wire_type_is_reachable_from_all() {
66        for wanted in GffType::ALL {
67            assert!(
68                !wanted.name().is_empty(),
69                "a wire type came through with no name"
70            );
71        }
72
73        // Distinct, so a copy-paste in the declaration shows up here rather
74        // than as two types sharing a name in a picker.
75        let mut names: Vec<&str> = GffType::ALL.iter().map(|kind| kind.name()).collect();
76        names.sort_unstable();
77        let total = names.len();
78        names.dedup();
79        assert_eq!(names.len(), total, "two wire types share a name");
80    }
81
82    /// A value's type is one of the ones a caller can be offered.
83    #[test]
84    fn a_value_reports_a_type_that_is_in_all() {
85        let value = GffValue::UInt8(0);
86        assert!(GffType::ALL.contains(&gff_value_type(&value)));
87    }
88}