1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3use std::path::PathBuf;
4use std::str::FromStr;
5
6use anyhow::{Result, bail};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct WorkspaceManifest {
12 pub root_manifest_path: PathBuf,
14 pub packages: Vec<FeatureManifest>,
16}
17
18impl WorkspaceManifest {
19 pub fn is_single_package(&self) -> bool {
21 self.packages.len() == 1
22 }
23
24 pub fn package_names(&self) -> Vec<&str> {
26 self.packages
27 .iter()
28 .filter_map(|package| package.package_name.as_deref())
29 .collect()
30 }
31}
32
33#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
35pub struct FeatureManifest {
36 pub manifest_path: PathBuf,
38 pub package_name: Option<String>,
40 pub metadata_table: Option<String>,
42 pub metadata_layout: MetadataLayout,
44 pub features: BTreeMap<String, Feature>,
46 pub metadata_only: BTreeMap<String, FeatureMetadata>,
48 pub default_members: Vec<FeatureRef>,
50 pub default_features: BTreeSet<String>,
52 pub groups: Vec<FeatureGroup>,
54 pub dependencies: BTreeMap<String, DependencyInfo>,
56 pub lint_overrides: BTreeMap<String, LintLevel>,
58 pub lint_preset: Option<LintPreset>,
60}
61
62impl FeatureManifest {
63 pub fn ordered_features(&self) -> Vec<&Feature> {
65 self.features.values().collect()
66 }
67
68 pub fn groups_for_feature(&self, feature_name: &str) -> Vec<&FeatureGroup> {
70 self.groups
71 .iter()
72 .filter(|group| group.members.iter().any(|member| member == feature_name))
73 .collect()
74 }
75
76 pub fn reverse_dependencies(&self, feature_name: &str) -> Vec<&Feature> {
78 self.features
79 .values()
80 .filter(|feature| {
81 feature
82 .enables
83 .iter()
84 .any(|reference| reference.local_feature_name() == Some(feature_name))
85 })
86 .collect()
87 }
88}
89
90#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
92pub struct Feature {
93 pub name: String,
95 pub metadata: FeatureMetadata,
97 pub has_metadata: bool,
99 pub enables: Vec<FeatureRef>,
101 pub default_enabled: bool,
103}
104
105#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
107pub struct DependencyInfo {
108 pub key: String,
110 pub package: String,
112 pub optional: bool,
114}
115
116#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
118#[serde(rename_all = "snake_case")]
119pub enum MetadataLayout {
120 Flat,
122 Structured,
124}
125
126impl fmt::Display for MetadataLayout {
127 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match self {
129 Self::Flat => formatter.write_str("flat"),
130 Self::Structured => formatter.write_str("structured"),
131 }
132 }
133}
134
135impl FromStr for MetadataLayout {
136 type Err = anyhow::Error;
137
138 fn from_str(value: &str) -> Result<Self> {
139 match value {
140 "flat" => Ok(Self::Flat),
141 "structured" => Ok(Self::Structured),
142 _ => bail!("expected `flat` or `structured`, found `{value}`"),
143 }
144 }
145}
146
147#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
149#[serde(rename_all = "lowercase")]
150pub enum LintLevel {
151 Allow,
153 Warn,
155 Deny,
157}
158
159impl fmt::Display for LintLevel {
160 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
161 match self {
162 Self::Allow => formatter.write_str("allow"),
163 Self::Warn => formatter.write_str("warn"),
164 Self::Deny => formatter.write_str("deny"),
165 }
166 }
167}
168
169impl FromStr for LintLevel {
170 type Err = anyhow::Error;
171
172 fn from_str(value: &str) -> Result<Self> {
173 match value {
174 "allow" => Ok(Self::Allow),
175 "warn" | "warning" => Ok(Self::Warn),
176 "deny" | "error" => Ok(Self::Deny),
177 _ => bail!("expected `allow`, `warn`, or `deny`, found `{value}`"),
178 }
179 }
180}
181
182#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
184#[serde(rename_all = "lowercase")]
185pub enum LintPreset {
186 Adopt,
188 Strict,
190}
191
192impl fmt::Display for LintPreset {
193 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
194 match self {
195 Self::Adopt => formatter.write_str("adopt"),
196 Self::Strict => formatter.write_str("strict"),
197 }
198 }
199}
200
201impl FromStr for LintPreset {
202 type Err = anyhow::Error;
203
204 fn from_str(value: &str) -> Result<Self> {
205 match value {
206 "adopt" => Ok(Self::Adopt),
207 "strict" => Ok(Self::Strict),
208 _ => bail!("expected `adopt` or `strict`, found `{value}`"),
209 }
210 }
211}
212
213#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
215#[serde(tag = "kind", rename_all = "snake_case")]
216pub enum FeatureRef {
217 Feature {
219 name: String,
221 },
222 Dependency {
224 name: String,
226 },
227 DependencyFeature {
229 dependency: String,
231 feature: String,
233 weak: bool,
235 },
236 Unknown {
238 raw: String,
240 },
241}
242
243impl FeatureRef {
244 pub fn parse(raw: &str) -> Self {
246 if let Some(name) = raw.strip_prefix("dep:") {
247 return Self::Dependency {
248 name: name.to_owned(),
249 };
250 }
251
252 if let Some((dependency, feature)) = raw.split_once("?/") {
253 return Self::DependencyFeature {
254 dependency: dependency.to_owned(),
255 feature: feature.to_owned(),
256 weak: true,
257 };
258 }
259
260 if let Some((dependency, feature)) = raw.split_once('/') {
261 return Self::DependencyFeature {
262 dependency: dependency.to_owned(),
263 feature: feature.to_owned(),
264 weak: false,
265 };
266 }
267
268 if raw.trim().is_empty() {
269 return Self::Unknown {
270 raw: raw.to_owned(),
271 };
272 }
273
274 Self::Feature {
275 name: raw.to_owned(),
276 }
277 }
278
279 pub fn local_feature_name(&self) -> Option<&str> {
281 match self {
282 Self::Feature { name } => Some(name.as_str()),
283 _ => None,
284 }
285 }
286
287 pub fn dependency_name(&self) -> Option<&str> {
289 match self {
290 Self::Dependency { name } => Some(name.as_str()),
291 Self::DependencyFeature { dependency, .. } => Some(dependency.as_str()),
292 _ => None,
293 }
294 }
295
296 pub fn raw(&self) -> String {
298 self.to_string()
299 }
300}
301
302impl fmt::Display for FeatureRef {
303 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
304 match self {
305 Self::Feature { name } => formatter.write_str(name),
306 Self::Dependency { name } => write!(formatter, "dep:{name}"),
307 Self::DependencyFeature {
308 dependency,
309 feature,
310 weak,
311 } => {
312 if *weak {
313 write!(formatter, "{dependency}?/{feature}")
314 } else {
315 write!(formatter, "{dependency}/{feature}")
316 }
317 }
318 Self::Unknown { raw } => formatter.write_str(raw),
319 }
320 }
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
325#[serde(deny_unknown_fields)]
326pub struct FeatureGroup {
327 pub name: String,
329 pub members: Vec<String>,
331 #[serde(default)]
332 pub mutually_exclusive: bool,
334 pub description: Option<String>,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
340#[serde(deny_unknown_fields)]
341pub struct FeatureMetadata {
342 pub description: Option<String>,
344 pub category: Option<String>,
346 pub since: Option<String>,
348 pub docs: Option<String>,
350 pub tracking_issue: Option<String>,
352 #[serde(default)]
353 pub requires: Vec<String>,
355 #[serde(default = "default_public")]
356 pub public: bool,
358 #[serde(default)]
359 pub unstable: bool,
361 #[serde(default)]
362 pub deprecated: bool,
364 #[serde(default)]
365 pub allow_default: bool,
367 pub note: Option<String>,
369}
370
371impl Default for FeatureMetadata {
372 fn default() -> Self {
373 Self {
374 description: None,
375 category: None,
376 since: None,
377 docs: None,
378 tracking_issue: None,
379 requires: Vec::new(),
380 public: true,
381 unstable: false,
382 deprecated: false,
383 allow_default: false,
384 note: None,
385 }
386 }
387}
388
389impl FeatureMetadata {
390 pub fn status_labels(&self) -> Vec<&'static str> {
392 let mut labels = Vec::new();
393 if self.deprecated {
394 labels.push("deprecated");
395 }
396 if self.unstable {
397 labels.push("unstable");
398 }
399 if !self.public {
400 labels.push("private");
401 }
402 if labels.is_empty() {
403 labels.push("stable");
404 }
405 labels
406 }
407}
408
409fn default_public() -> bool {
410 true
411}