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