1use serde::{Deserialize, Serialize};
12
13#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
17pub struct Feature {
18 pub name: String,
19 pub implies: Vec<FeatureRef>,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(tag = "kind", rename_all = "kebab-case")]
29pub enum FeatureRef {
30 Local { name: String },
32 Namespaced { package: String, feature: String },
34 DepActivation { dep_name: String },
37}
38
39impl FeatureRef {
40 #[must_use]
43 pub fn parse(s: &str) -> Self {
44 if let Some(rest) = s.strip_prefix("dep:") {
45 Self::DepActivation {
46 dep_name: rest.to_string(),
47 }
48 } else if let Some((pkg, feat)) = s.split_once('/') {
49 Self::Namespaced {
50 package: pkg.to_string(),
51 feature: feat.to_string(),
52 }
53 } else {
54 Self::Local {
55 name: s.to_string(),
56 }
57 }
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn parse_local_feature() {
67 assert_eq!(
68 FeatureRef::parse("derive"),
69 FeatureRef::Local {
70 name: "derive".into()
71 }
72 );
73 }
74
75 #[test]
76 fn parse_namespaced_feature() {
77 assert_eq!(
78 FeatureRef::parse("serde/derive"),
79 FeatureRef::Namespaced {
80 package: "serde".into(),
81 feature: "derive".into(),
82 }
83 );
84 }
85
86 #[test]
87 fn parse_dep_activation() {
88 assert_eq!(
89 FeatureRef::parse("dep:some-opt"),
90 FeatureRef::DepActivation {
91 dep_name: "some-opt".into()
92 }
93 );
94 }
95
96 #[test]
97 fn round_trip_through_serde() {
98 let f = Feature {
99 name: "default".into(),
100 implies: vec![
101 FeatureRef::Local { name: "std".into() },
102 FeatureRef::Namespaced {
103 package: "serde".into(),
104 feature: "derive".into(),
105 },
106 ],
107 };
108 let j = serde_json::to_string(&f).unwrap();
109 let parsed: Feature = serde_json::from_str(&j).unwrap();
110 assert_eq!(f, parsed);
111 }
112}