1use strum_macros::EnumString;
6use thiserror::Error;
7
8use std::fmt::Display;
9use std::str::FromStr;
10
11#[derive(Debug, Clone, Copy, PartialEq, EnumString, strum_macros::Display)]
16#[strum(serialize_all = "snake_case")]
17pub enum Feature {
18 AndroidUsb,
22 AspectRatio,
23 Ashmem,
24 BootNotifier,
25 BootNotifierCpuBoost,
26 Cooling,
27 DataCollectionConsentSync,
28 Fastrpc,
29 Framebuffer,
30 Gfxstream,
31 GoogleOdpm,
35 Gralloc,
36 HvdcpOpti,
40 Kgsl,
41 Magma,
42 MagmaSupportedVendors,
43 Nanohub,
44 NetworkManager,
45 Perfetto,
46 Thermal,
47 WakeupTest,
48
49 AdditionalMounts,
51 AndroidBootreason,
52 AndroidSerialno,
53 Bpf,
54 Container,
55 CustomArtifacts,
56 EnableSuid,
57 ErrorOnFailedReboot,
58 FakeIon,
60 IoUring,
61 LogSysfsLookupMisses,
62 MmcblkStub,
63 PingGroupRange,
64 RemotevolInProcess,
65 RootfsRw,
66 Selinux,
67 SelinuxTestSuite,
68 TestData,
69 UnifiedTracing,
70 Wifi,
71}
72
73#[derive(Debug, Error)]
75#[error("unsupported feature: {0}")]
76pub struct UnsupportedFeatureError(String);
77
78impl Feature {
79 pub fn try_parse(s: &str) -> Result<Feature, UnsupportedFeatureError> {
81 Feature::from_str(s).map_err(|_| UnsupportedFeatureError(s.to_string()))
82 }
83
84 pub fn try_parse_feature_and_args(
86 s: &str,
87 ) -> Result<(Feature, Option<String>), UnsupportedFeatureError> {
88 let (raw_flag, raw_args) =
89 s.split_once(':').map(|(f, a)| (f, Some(a.to_string()))).unwrap_or((s, None));
90 Self::try_parse(raw_flag).map(|feature| (feature, raw_args))
91 }
92}
93
94#[derive(Debug, Clone, PartialEq)]
96pub struct FeatureAndArgs {
97 pub feature: Feature,
99 pub raw_args: Option<String>,
101}
102
103impl FeatureAndArgs {
104 pub fn try_parse(s: &str) -> Result<FeatureAndArgs, UnsupportedFeatureError> {
108 let (raw_flag, raw_args) =
109 s.split_once(':').map(|(f, a)| (f, Some(a.to_string()))).unwrap_or((s, None));
110 let feature = Feature::try_parse(raw_flag)?;
111 Ok(FeatureAndArgs { feature, raw_args })
112 }
113}
114
115impl Display for FeatureAndArgs {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
117 let FeatureAndArgs { feature, raw_args } = self;
118 match raw_args {
119 None => feature.fmt(f),
120 Some(raw_args) => format_args!("{feature}:{raw_args}").fmt(f),
121 }
122 }
123}
124
125#[cfg(test)]
126mod test {
127 use super::*;
128
129 #[test]
130 fn feature_serde() {
131 for (feature, expected_str) in [
132 (Feature::AndroidSerialno, "android_serialno"),
133 (Feature::AndroidBootreason, "android_bootreason"),
134 (Feature::AspectRatio, "aspect_ratio"),
135 (Feature::Container, "container"),
136 (Feature::CustomArtifacts, "custom_artifacts"),
137 (Feature::Ashmem, "ashmem"),
138 (Feature::BootNotifier, "boot_notifier"),
139 (Feature::BootNotifierCpuBoost, "boot_notifier_cpu_boost"),
140 (Feature::Framebuffer, "framebuffer"),
141 (Feature::Gralloc, "gralloc"),
142 (Feature::Kgsl, "kgsl"),
143 (Feature::Magma, "magma"),
144 (Feature::MagmaSupportedVendors, "magma_supported_vendors"),
145 (Feature::Nanohub, "nanohub"),
146 (Feature::NetworkManager, "network_manager"),
147 (Feature::Gfxstream, "gfxstream"),
148 (Feature::Bpf, "bpf"),
149 (Feature::EnableSuid, "enable_suid"),
150 (Feature::IoUring, "io_uring"),
151 (Feature::ErrorOnFailedReboot, "error_on_failed_reboot"),
152 (Feature::Perfetto, "perfetto"),
153 (Feature::PingGroupRange, "ping_group_range"),
154 (Feature::RootfsRw, "rootfs_rw"),
155 (Feature::Selinux, "selinux"),
156 (Feature::SelinuxTestSuite, "selinux_test_suite"),
157 (Feature::TestData, "test_data"),
158 (Feature::Thermal, "thermal"),
159 (Feature::Cooling, "cooling"),
160 (Feature::DataCollectionConsentSync, "data_collection_consent_sync"),
161 (Feature::HvdcpOpti, "hvdcp_opti"),
162 (Feature::Wifi, "wifi"),
163 (Feature::AdditionalMounts, "additional_mounts"),
164 (Feature::WakeupTest, "wakeup_test"),
165 (Feature::MmcblkStub, "mmcblk_stub"),
166 (Feature::AndroidUsb, "android_usb"),
167 (Feature::GoogleOdpm, "google_odpm"),
168 (Feature::FakeIon, "fake_ion"),
170 (Feature::UnifiedTracing, "unified_tracing"),
171 (Feature::RemotevolInProcess, "remotevol_in_process"),
172 (Feature::LogSysfsLookupMisses, "log_sysfs_lookup_misses"),
173 ] {
174 let string = feature.to_string();
175 assert_eq!(string.as_str(), expected_str);
176 assert_eq!(Feature::try_parse(&string).expect("should parse"), feature);
177 }
178 }
179
180 #[test]
181 fn deserialize_feature_and_args() {
182 let FeatureAndArgs { feature, raw_args } =
183 FeatureAndArgs::try_parse("bpf:v2").expect("should parse successfully");
184 assert_eq!(feature, Feature::Bpf);
185 assert_eq!(raw_args.as_ref().expect("should be populated"), "v2");
186 }
187}