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 AndroidSerialno,
19 AndroidBootreason,
20 AspectRatio,
21 Container,
22 CustomArtifacts,
23 Ashmem,
24 BootNotifier,
25 BootNotifierCpuBoost,
26 Framebuffer,
27 Gralloc,
28 Kgsl,
29 Magma,
30 MagmaSupportedVendors,
31 Nanohub,
32 Fastrpc,
33 NetworkManager,
34 Gfxstream,
35 Bpf,
36 EnableSuid,
37 IoUring,
38 ErrorOnFailedReboot,
39 Perfetto,
40 PingGroupRange,
41 RootfsRw,
42 Selinux,
43 SelinuxTestSuite,
44 TestData,
45 Thermal,
46 Cooling,
47 DataCollectionConsentSync,
48 HvdcpOpti,
49 Wifi,
50 AdditionalMounts,
51 WakeupTest,
52 MmcblkStub,
53 AndroidUsb,
54 FakeIon,
56}
57
58#[derive(Debug, Error)]
60#[error("unsupported feature: {0}")]
61pub struct UnsupportedFeatureError(String);
62
63impl Feature {
64 pub fn try_parse(s: &str) -> Result<Feature, UnsupportedFeatureError> {
66 Feature::from_str(s).map_err(|_| UnsupportedFeatureError(s.to_string()))
67 }
68
69 pub fn try_parse_feature_and_args(
71 s: &str,
72 ) -> Result<(Feature, Option<String>), UnsupportedFeatureError> {
73 let (raw_flag, raw_args) =
74 s.split_once(':').map(|(f, a)| (f, Some(a.to_string()))).unwrap_or((s, None));
75 Self::try_parse(raw_flag).map(|feature| (feature, raw_args))
76 }
77}
78
79#[derive(Debug, Clone, PartialEq)]
81pub struct FeatureAndArgs {
82 pub feature: Feature,
84 pub raw_args: Option<String>,
86}
87
88impl FeatureAndArgs {
89 pub fn try_parse(s: &str) -> Result<FeatureAndArgs, UnsupportedFeatureError> {
93 let (raw_flag, raw_args) =
94 s.split_once(':').map(|(f, a)| (f, Some(a.to_string()))).unwrap_or((s, None));
95 let feature = Feature::try_parse(raw_flag)?;
96 Ok(FeatureAndArgs { feature, raw_args })
97 }
98}
99
100impl Display for FeatureAndArgs {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
102 let FeatureAndArgs { feature, raw_args } = self;
103 match raw_args {
104 None => feature.fmt(f),
105 Some(raw_args) => format_args!("{feature}:{raw_args}").fmt(f),
106 }
107 }
108}
109
110#[cfg(test)]
111mod test {
112 use super::*;
113
114 #[test]
115 fn feature_serde() {
116 for (feature, expected_str) in [
117 (Feature::AndroidSerialno, "android_serialno"),
118 (Feature::AndroidBootreason, "android_bootreason"),
119 (Feature::AspectRatio, "aspect_ratio"),
120 (Feature::Container, "container"),
121 (Feature::CustomArtifacts, "custom_artifacts"),
122 (Feature::Ashmem, "ashmem"),
123 (Feature::BootNotifier, "boot_notifier"),
124 (Feature::BootNotifierCpuBoost, "boot_notifier_cpu_boost"),
125 (Feature::Framebuffer, "framebuffer"),
126 (Feature::Gralloc, "gralloc"),
127 (Feature::Kgsl, "kgsl"),
128 (Feature::Magma, "magma"),
129 (Feature::MagmaSupportedVendors, "magma_supported_vendors"),
130 (Feature::Nanohub, "nanohub"),
131 (Feature::NetworkManager, "network_manager"),
132 (Feature::Gfxstream, "gfxstream"),
133 (Feature::Bpf, "bpf"),
134 (Feature::EnableSuid, "enable_suid"),
135 (Feature::IoUring, "io_uring"),
136 (Feature::ErrorOnFailedReboot, "error_on_failed_reboot"),
137 (Feature::Perfetto, "perfetto"),
138 (Feature::PingGroupRange, "ping_group_range"),
139 (Feature::RootfsRw, "rootfs_rw"),
140 (Feature::Selinux, "selinux"),
141 (Feature::SelinuxTestSuite, "selinux_test_suite"),
142 (Feature::TestData, "test_data"),
143 (Feature::Thermal, "thermal"),
144 (Feature::Cooling, "cooling"),
145 (Feature::DataCollectionConsentSync, "data_collection_consent_sync"),
146 (Feature::HvdcpOpti, "hvdcp_opti"),
147 (Feature::Wifi, "wifi"),
148 (Feature::AdditionalMounts, "additional_mounts"),
149 (Feature::WakeupTest, "wakeup_test"),
150 (Feature::MmcblkStub, "mmcblk_stub"),
151 (Feature::AndroidUsb, "android_usb"),
152 (Feature::FakeIon, "fake_ion"),
154 ] {
155 let string = feature.to_string();
156 assert_eq!(string.as_str(), expected_str);
157 assert_eq!(Feature::try_parse(&string).expect("should parse"), feature);
158 }
159 }
160
161 #[test]
162 fn deserialize_feature_and_args() {
163 let FeatureAndArgs { feature, raw_args } =
164 FeatureAndArgs::try_parse("bpf:v2").expect("should parse successfully");
165 assert_eq!(feature, Feature::Bpf);
166 assert_eq!(raw_args.as_ref().expect("should be populated"), "v2");
167 }
168}