Skip to main content

module_info/
fields.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Module containing the enum definition for module info field types
5//!
6//! This module provides a type-safe way to access module info fields
7//! through the `get_module_info!` macro.
8
9/// Represents the available module info fields
10///
11/// This enum provides a type-safe way to specify which module info field
12/// to access when using the `get_module_info!` macro.
13///
14/// # Non-exhaustive
15///
16/// This enum is marked `#[non_exhaustive]`: additional fields may be added
17/// in future minor releases without breaking SemVer. Any external `match`
18/// on `ModuleInfoField` **must** include a wildcard arm (`_ => ...`) so
19/// downstream code keeps compiling when new variants are introduced.
20///
21/// # Example
22///
23/// `ModuleInfoField` does not need to be imported when it only appears
24/// inside `get_module_info!(ModuleInfoField::…)`: the macro pattern-matches
25/// the variant as tokens, so the bare macro + result import is enough:
26///
27/// ```rust
28/// use module_info::{get_module_info, ModuleInfoResult};
29///
30/// fn get_binary_name() -> ModuleInfoResult<String> {
31///     let binary_name = get_module_info!(ModuleInfoField::Binary)?;
32///     Ok(binary_name)
33/// }
34/// ```
35///
36/// You only need `use module_info::ModuleInfoField;` when you reference the
37/// enum outside the macro (e.g. in your own `match` or when passing a value
38/// into `ModuleInfoField::to_symbol_name`).
39///
40/// # Adding a new variant
41///
42/// Adding a variant requires synchronized updates in seven places across four
43/// files. The enum `#[non_exhaustive]` + exhaustive matches in
44/// `field_value`/`to_symbol_name`/`to_key` catch most drift as compile errors,
45/// but the `get_module_info!` macro rules are token-matched and their drift
46/// only surfaces at the *consumer's* call site. Skim this list:
47///
48/// 1. This enum declaration (add the variant)
49/// 2. [`ModuleInfoField::to_symbol_name`] match arm (compile-error on miss)
50/// 3. [`ModuleInfoField::to_key`] match arm (compile-error on miss)
51/// 4. [`ModuleInfoField::ALL`] + `EXPECTED_VARIANT_COUNT` in the drift-guard
52///    test (runtime failure on miss)
53/// 5. `PackageMetadata` field + `field_value` match arm (compile-error on miss)
54/// 6. `src/macros.rs`: per-variant rule in the Linux `get_module_info!`
55///    macro AND in the non-Linux fallback macro. **Missing a rule here is
56///    silent: `get_module_info!(ModuleInfoField::NewField)` only fails at
57///    the consumer's call site.**
58/// 7. `src/macros.rs`: an `@__add_to_map` line in the no-arg form of the
59///    Linux `get_module_info!` macro. **Missing this line silently drops
60///    the field from the no-arg HashMap.**
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62#[non_exhaustive]
63pub enum ModuleInfoField {
64    /// The binary name
65    Binary,
66    /// The version of the binary
67    Version,
68    /// The version of the module
69    ModuleVersion,
70    /// The maintainer of the binary
71    Maintainer,
72    /// The name of the module
73    Name,
74    /// The type of module
75    Type,
76    /// The repository URL
77    Repo,
78    /// The branch name
79    Branch,
80    /// The commit hash
81    Hash,
82    /// The copyright information
83    Copyright,
84    /// The operating system information
85    Os,
86    /// The operating system version
87    OsVersion,
88}
89
90impl ModuleInfoField {
91    /// Converts the enum variant to the corresponding linker symbol name
92    /// (for example, `ModuleInfoField::Binary` → `"module_info_binary"`).
93    ///
94    /// This is primarily an internal helper used by the `get_module_info!`
95    /// macro expansion and by debugging utilities; most consumers should
96    /// reach for the macro rather than calling this directly.
97    ///
98    /// # Returns
99    /// A string slice containing the symbol name for this field.
100    pub fn to_symbol_name(&self) -> &'static str {
101        match self {
102            ModuleInfoField::Binary => "module_info_binary",
103            ModuleInfoField::Version => "module_info_version",
104            ModuleInfoField::ModuleVersion => "module_info_moduleVersion",
105            ModuleInfoField::Maintainer => "module_info_maintainer",
106            ModuleInfoField::Name => "module_info_name",
107            ModuleInfoField::Type => "module_info_type",
108            ModuleInfoField::Repo => "module_info_repo",
109            ModuleInfoField::Branch => "module_info_branch",
110            ModuleInfoField::Hash => "module_info_hash",
111            ModuleInfoField::Copyright => "module_info_copyright",
112            ModuleInfoField::Os => "module_info_os",
113            ModuleInfoField::OsVersion => "module_info_osVersion",
114        }
115    }
116
117    /// Converts the enum variant to the JSON/`HashMap` key used for this field
118    /// (for example, `ModuleInfoField::ModuleVersion` → `"moduleVersion"`).
119    ///
120    /// This is primarily an internal helper used by the no-argument form of
121    /// `get_module_info!()` when it assembles the result `HashMap`. Direct
122    /// consumer use is uncommon; reach for the macro instead.
123    ///
124    /// # Returns
125    /// A string slice containing the key for this field in the HashMap.
126    pub fn to_key(&self) -> &'static str {
127        match self {
128            ModuleInfoField::Binary => "binary",
129            ModuleInfoField::Version => "version",
130            ModuleInfoField::ModuleVersion => "moduleVersion",
131            ModuleInfoField::Maintainer => "maintainer",
132            ModuleInfoField::Name => "name",
133            ModuleInfoField::Type => "type",
134            ModuleInfoField::Repo => "repo",
135            ModuleInfoField::Branch => "branch",
136            ModuleInfoField::Hash => "hash",
137            ModuleInfoField::Copyright => "copyright",
138            ModuleInfoField::Os => "os",
139            ModuleInfoField::OsVersion => "osVersion",
140        }
141    }
142
143    /// All variants of `ModuleInfoField`, in a stable declaration order.
144    ///
145    /// This single source-of-truth list is used by [`ModuleInfoField::count`]
146    /// and by the no-argument form of the `get_module_info!` macro to build
147    /// the result `HashMap`. Adding a variant above and forgetting to add it
148    /// here will be caught by the agreement test in `lib.rs`.
149    pub const ALL: &'static [ModuleInfoField] = &[
150        ModuleInfoField::Binary,
151        ModuleInfoField::Version,
152        ModuleInfoField::ModuleVersion,
153        ModuleInfoField::Maintainer,
154        ModuleInfoField::Name,
155        ModuleInfoField::Type,
156        ModuleInfoField::Repo,
157        ModuleInfoField::Branch,
158        ModuleInfoField::Hash,
159        ModuleInfoField::Copyright,
160        ModuleInfoField::Os,
161        ModuleInfoField::OsVersion,
162    ];
163
164    /// Returns the number of variants in the `ModuleInfoField` enum.
165    ///
166    /// Derived from [`ModuleInfoField::ALL`] so it stays in sync automatically
167    /// when variants are added or removed.
168    pub const fn count() -> usize {
169        Self::ALL.len()
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use std::collections::HashSet;
176
177    use super::*;
178
179    #[test]
180    fn count_matches_all_length() {
181        assert_eq!(ModuleInfoField::count(), ModuleInfoField::ALL.len());
182    }
183
184    #[test]
185    fn all_has_unique_symbol_names_and_keys() {
186        let symbols: HashSet<&str> = ModuleInfoField::ALL
187            .iter()
188            .map(|f| f.to_symbol_name())
189            .collect();
190        assert_eq!(
191            symbols.len(),
192            ModuleInfoField::ALL.len(),
193            "ModuleInfoField::ALL has duplicate entries (by symbol name)"
194        );
195
196        let keys: HashSet<&str> = ModuleInfoField::ALL.iter().map(|f| f.to_key()).collect();
197        assert_eq!(
198            keys.len(),
199            ModuleInfoField::ALL.len(),
200            "ModuleInfoField::ALL has duplicate entries (by HashMap key)"
201        );
202    }
203
204    #[test]
205    fn every_variant_is_listed_in_all() {
206        // Compile-time drift guard. This test protects the invariant
207        // `ModuleInfoField::ALL` contains every variant of `ModuleInfoField`,
208        // which several call sites (the `get_module_info!` macro, the
209        // `print_module_info` helper, and `count()`) rely on.
210        //
211        // The guard has two pieces:
212        //
213        //   1. An exhaustive match inside the crate (where
214        //      `#[non_exhaustive]` does not apply); adding a new variant
215        //      without a new arm is a compile error.
216        //   2. A hard-coded length assertion against `ALL`; adding a new
217        //      arm above without extending `ALL` fails this test at runtime
218        //      and the failure message tells the author exactly what to do.
219        //
220        // The two together close the loop: the first catches enum→match
221        // drift, the second catches match→ALL drift.
222        const EXPECTED_VARIANT_COUNT: usize = 12;
223
224        fn canonical_key(f: ModuleInfoField) -> &'static str {
225            match f {
226                ModuleInfoField::Binary => "binary",
227                ModuleInfoField::Version => "version",
228                ModuleInfoField::ModuleVersion => "moduleVersion",
229                ModuleInfoField::Maintainer => "maintainer",
230                ModuleInfoField::Name => "name",
231                ModuleInfoField::Type => "type",
232                ModuleInfoField::Repo => "repo",
233                ModuleInfoField::Branch => "branch",
234                ModuleInfoField::Hash => "hash",
235                ModuleInfoField::Copyright => "copyright",
236                ModuleInfoField::Os => "os",
237                ModuleInfoField::OsVersion => "osVersion",
238            }
239        }
240
241        assert_eq!(
242            ModuleInfoField::ALL.len(),
243            EXPECTED_VARIANT_COUNT,
244            "ModuleInfoField::ALL length changed: if you added a variant, \
245             extend ALL and bump EXPECTED_VARIANT_COUNT; if you removed one, \
246             drop it from ALL and bump EXPECTED_VARIANT_COUNT down"
247        );
248
249        // Every entry in `ALL` must hit a match arm above (dead entries
250        // would show up as a `canonical_key` returning the wrong string).
251        for f in ModuleInfoField::ALL {
252            assert_eq!(f.to_key(), canonical_key(*f));
253        }
254    }
255}