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