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}