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}