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 Framebuffer,
23 Gralloc,
24 Kgsl,
25 Magma,
26 MagmaSupportedVendors,
27 Nanohub,
28 NetstackMark,
29 NetworkManager,
30 Gfxstream,
31 Bpf,
32 EnableSuid,
33 IoUring,
34 ErrorOnFailedReboot,
35 Perfetto,
36 PerfettoProducer,
37 RootfsRw,
38 RtnetlinkAssumeIfb0Existence,
39 SelfProfile,
40 Selinux,
41 SelinuxTestSuite,
42 TestData,
43 Thermal,
44 HvdcpOpti,
45}
46
47#[derive(Debug, Error)]
49#[error("unsupported feature: {0}")]
50pub struct UnsupportedFeatureError(String);
51
52impl Feature {
53 pub fn try_parse(s: &str) -> Result<Feature, UnsupportedFeatureError> {
55 Feature::from_str(s).map_err(|_| UnsupportedFeatureError(s.to_string()))
56 }
57
58 pub fn try_parse_feature_and_args(
60 s: &str,
61 ) -> Result<(Feature, Option<String>), UnsupportedFeatureError> {
62 let (raw_flag, raw_args) =
63 s.split_once(':').map(|(f, a)| (f, Some(a.to_string()))).unwrap_or((s, None));
64 Self::try_parse(raw_flag).map(|feature| (feature, raw_args))
65 }
66}
67
68#[derive(Debug, Clone, PartialEq)]
70pub struct FeatureAndArgs {
71 pub feature: Feature,
73 pub raw_args: Option<String>,
75}
76
77impl FeatureAndArgs {
78 pub fn try_parse(s: &str) -> Result<FeatureAndArgs, UnsupportedFeatureError> {
82 let (raw_flag, raw_args) =
83 s.split_once(':').map(|(f, a)| (f, Some(a.to_string()))).unwrap_or((s, None));
84 let feature = Feature::try_parse(raw_flag)?;
85 Ok(FeatureAndArgs { feature, raw_args })
86 }
87}
88
89impl Display for FeatureAndArgs {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
91 let FeatureAndArgs { feature, raw_args } = self;
92 match raw_args {
93 None => feature.fmt(f),
94 Some(raw_args) => format_args!("{feature}:{raw_args}").fmt(f),
95 }
96 }
97}
98
99#[cfg(test)]
100mod test {
101 use super::*;
102
103 #[test]
104 fn feature_serde() {
105 for (feature, expected_str) in [
106 (Feature::AndroidFdr, "android_fdr"),
107 (Feature::AndroidSerialno, "android_serialno"),
108 (Feature::AndroidBootreason, "android_bootreason"),
109 (Feature::AspectRatio, "aspect_ratio"),
110 (Feature::Container, "container"),
111 (Feature::CustomArtifacts, "custom_artifacts"),
112 (Feature::Ashmem, "ashmem"),
113 (Feature::Framebuffer, "framebuffer"),
114 (Feature::Gralloc, "gralloc"),
115 (Feature::Kgsl, "kgsl"),
116 (Feature::Magma, "magma"),
117 (Feature::MagmaSupportedVendors, "magma_supported_vendors"),
118 (Feature::Nanohub, "nanohub"),
119 (Feature::NetstackMark, "netstack_mark"),
120 (Feature::NetworkManager, "network_manager"),
121 (Feature::Gfxstream, "gfxstream"),
122 (Feature::Bpf, "bpf"),
123 (Feature::EnableSuid, "enable_suid"),
124 (Feature::IoUring, "io_uring"),
125 (Feature::ErrorOnFailedReboot, "error_on_failed_reboot"),
126 (Feature::Perfetto, "perfetto"),
127 (Feature::PerfettoProducer, "perfetto_producer"),
128 (Feature::RootfsRw, "rootfs_rw"),
129 (Feature::RtnetlinkAssumeIfb0Existence, "rtnetlink_assume_ifb0_existence"),
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}