Skip to content

Commit 2d33f01

Browse files
authored
Merge pull request #9551 from Turbo87/split-features
Move `features` to `feature2` that depend on features that use new feature syntax
2 parents 9e3fc61 + 187b3c9 commit 2d33f01

File tree

5 files changed

+236
-9
lines changed

5 files changed

+236
-9
lines changed

src/models.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mod default_versions;
2424
pub mod dependency;
2525
mod download;
2626
mod email;
27+
pub mod feature;
2728
mod follow;
2829
mod keyword;
2930
pub mod krate;

src/models/feature.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use std::collections::BTreeMap;
2+
3+
pub type FeaturesMap = BTreeMap<String, Vec<String>>;
4+
5+
/// Splits the given [`FeaturesMap`] into two [`FeaturesMap`]s based on their
6+
/// values.
7+
///
8+
/// See <https://rust-lang.github.io/rfcs/3143-cargo-weak-namespaced-features.html>.
9+
pub fn split_features(features: FeaturesMap) -> (FeaturesMap, FeaturesMap) {
10+
const ITERATION_LIMIT: usize = 100;
11+
12+
// First, we partition the features into two groups: those that use the new
13+
// features syntax (`features2`) and those that don't (`features`).
14+
let (mut features, mut features2) =
15+
features
16+
.into_iter()
17+
.partition::<FeaturesMap, _>(|(_k, vals)| {
18+
!vals
19+
.iter()
20+
.any(|v| v.starts_with("dep:") || v.contains("?/"))
21+
});
22+
23+
// Then, we recursively move features from `features` to `features2` if they
24+
// depend on features in `features2`.
25+
for _ in 0..ITERATION_LIMIT {
26+
let split = features
27+
.into_iter()
28+
.partition::<FeaturesMap, _>(|(_k, vals)| {
29+
!vals.iter().any(|v| features2.contains_key(v))
30+
});
31+
32+
features = split.0;
33+
34+
if !split.1.is_empty() {
35+
features2.extend(split.1);
36+
} else {
37+
break;
38+
}
39+
}
40+
41+
(features, features2)
42+
}
43+
44+
#[cfg(test)]
45+
mod tests {
46+
use super::*;
47+
use insta::{assert_compact_debug_snapshot, assert_debug_snapshot};
48+
49+
#[test]
50+
fn test_split_features_no_deps() {
51+
let mut features = FeaturesMap::new();
52+
features.insert(
53+
"feature1".to_string(),
54+
vec!["val1".to_string(), "val2".to_string()],
55+
);
56+
features.insert("feature2".to_string(), vec!["val3".to_string()]);
57+
58+
let (features, features2) = split_features(features);
59+
60+
assert_compact_debug_snapshot!(features, @r#"{"feature1": ["val1", "val2"], "feature2": ["val3"]}"#);
61+
assert_compact_debug_snapshot!(features2, @"{}");
62+
}
63+
64+
#[test]
65+
fn test_split_features_with_deps() {
66+
let mut features = FeaturesMap::new();
67+
features.insert(
68+
"feature1".to_string(),
69+
vec!["dep:val1".to_string(), "val2".to_string()],
70+
);
71+
features.insert(
72+
"feature2".to_string(),
73+
vec!["val3".to_string(), "val4?/val5".to_string()],
74+
);
75+
76+
let (features, features2) = split_features(features);
77+
78+
assert_compact_debug_snapshot!(features, @"{}");
79+
assert_compact_debug_snapshot!(features2, @r#"{"feature1": ["dep:val1", "val2"], "feature2": ["val3", "val4?/val5"]}"#);
80+
}
81+
82+
#[test]
83+
fn test_split_features_mixed() {
84+
let mut features = FeaturesMap::new();
85+
features.insert(
86+
"feature1".to_string(),
87+
vec!["val1".to_string(), "val2".to_string()],
88+
);
89+
features.insert("feature2".to_string(), vec!["dep:val3".to_string()]);
90+
features.insert(
91+
"feature3".to_string(),
92+
vec!["val4".to_string(), "val5?/val6".to_string()],
93+
);
94+
95+
let (features, features2) = split_features(features);
96+
97+
assert_compact_debug_snapshot!(features, @r#"{"feature1": ["val1", "val2"]}"#);
98+
assert_compact_debug_snapshot!(features2, @r#"{"feature2": ["dep:val3"], "feature3": ["val4", "val5?/val6"]}"#);
99+
}
100+
101+
#[test]
102+
fn test_split_features_nested() {
103+
let mut features = FeaturesMap::new();
104+
features.insert("feature1".to_string(), vec!["feature2".to_string()]);
105+
features.insert("feature2".to_string(), vec![]);
106+
features.insert("feature3".to_string(), vec!["feature1".to_string()]);
107+
108+
let (features, features2) = split_features(features);
109+
110+
assert_compact_debug_snapshot!(features, @r#"{"feature1": ["feature2"], "feature2": [], "feature3": ["feature1"]}"#);
111+
assert_compact_debug_snapshot!(features2, @"{}");
112+
}
113+
114+
#[test]
115+
fn test_split_features_nested_mixed() {
116+
let mut features = FeaturesMap::new();
117+
features.insert("feature1".to_string(), vec!["feature2".to_string()]);
118+
features.insert("feature2".to_string(), vec!["feature3".to_string()]);
119+
features.insert("feature3".to_string(), vec!["dep:foo".to_string()]);
120+
121+
let (features, features2) = split_features(features);
122+
123+
assert_compact_debug_snapshot!(features, @"{}");
124+
assert_compact_debug_snapshot!(features2, @r#"{"feature1": ["feature2"], "feature2": ["feature3"], "feature3": ["dep:foo"]}"#);
125+
}
126+
127+
#[test]
128+
fn test_split_features_clap() {
129+
let json = json!({
130+
"env": ["clap_builder/env"],
131+
"std": ["clap_builder/std"],
132+
"help": ["clap_builder/help"],
133+
"cargo": ["clap_builder/cargo"],
134+
"color": ["clap_builder/color"],
135+
"debug": ["clap_builder/debug", "clap_derive?/debug"],
136+
"usage": ["clap_builder/usage"],
137+
"derive": ["dep:clap_derive"],
138+
"string": ["clap_builder/string"],
139+
"default": ["std", "color", "help", "usage", "error-context", "suggestions"],
140+
"unicode": ["clap_builder/unicode"],
141+
"wrap_help": ["clap_builder/wrap_help"],
142+
"deprecated": ["clap_builder/deprecated", "clap_derive?/deprecated"],
143+
"suggestions": ["clap_builder/suggestions"],
144+
"unstable-v5": ["clap_builder/unstable-v5", "clap_derive?/unstable-v5", "deprecated"],
145+
"unstable-doc": ["clap_builder/unstable-doc", "derive"],
146+
"unstable-ext": ["clap_builder/unstable-ext"],
147+
"error-context": ["clap_builder/error-context"],
148+
"unstable-styles": ["clap_builder/unstable-styles"]
149+
});
150+
151+
let features = serde_json::from_value::<FeaturesMap>(json).unwrap();
152+
assert_debug_snapshot!(split_features(features));
153+
}
154+
}

src/models/krate.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::collections::BTreeMap;
2-
31
use chrono::NaiveDateTime;
42
use diesel::associations::Identifiable;
53
use diesel::dsl;
@@ -10,6 +8,7 @@ use secrecy::SecretString;
108
use thiserror::Error;
119

1210
use crate::controllers::helpers::pagination::*;
11+
use crate::models::feature::split_features;
1312
use crate::models::helpers::with_count::*;
1413
use crate::models::version::TopVersions;
1514
use crate::models::{
@@ -484,12 +483,7 @@ impl Crate {
484483
deps.sort();
485484

486485
let features = version.features().unwrap_or_default();
487-
let (features, features2): (BTreeMap<_, _>, BTreeMap<_, _>) =
488-
features.into_iter().partition(|(_k, vals)| {
489-
!vals
490-
.iter()
491-
.any(|v| v.starts_with("dep:") || v.contains("?/"))
492-
});
486+
let (features, features2) = split_features(features);
493487

494488
let (features2, v) = if features2.is_empty() {
495489
(None, None)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
source: src/models/feature.rs
3+
expression: split_features(features)
4+
---
5+
(
6+
{
7+
"cargo": [
8+
"clap_builder/cargo",
9+
],
10+
"color": [
11+
"clap_builder/color",
12+
],
13+
"default": [
14+
"std",
15+
"color",
16+
"help",
17+
"usage",
18+
"error-context",
19+
"suggestions",
20+
],
21+
"env": [
22+
"clap_builder/env",
23+
],
24+
"error-context": [
25+
"clap_builder/error-context",
26+
],
27+
"help": [
28+
"clap_builder/help",
29+
],
30+
"std": [
31+
"clap_builder/std",
32+
],
33+
"string": [
34+
"clap_builder/string",
35+
],
36+
"suggestions": [
37+
"clap_builder/suggestions",
38+
],
39+
"unicode": [
40+
"clap_builder/unicode",
41+
],
42+
"unstable-ext": [
43+
"clap_builder/unstable-ext",
44+
],
45+
"unstable-styles": [
46+
"clap_builder/unstable-styles",
47+
],
48+
"usage": [
49+
"clap_builder/usage",
50+
],
51+
"wrap_help": [
52+
"clap_builder/wrap_help",
53+
],
54+
},
55+
{
56+
"debug": [
57+
"clap_builder/debug",
58+
"clap_derive?/debug",
59+
],
60+
"deprecated": [
61+
"clap_builder/deprecated",
62+
"clap_derive?/deprecated",
63+
],
64+
"derive": [
65+
"dep:clap_derive",
66+
],
67+
"unstable-doc": [
68+
"clap_builder/unstable-doc",
69+
"derive",
70+
],
71+
"unstable-v5": [
72+
"clap_builder/unstable-v5",
73+
"clap_derive?/unstable-v5",
74+
"deprecated",
75+
],
76+
},
77+
)

src/models/version.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use serde::Deserialize;
77

88
use crate::util::errors::{bad_request, AppResult};
99

10+
use crate::models::feature::FeaturesMap;
1011
use crate::models::{Crate, Dependency, User};
1112
use crate::schema::*;
1213
use crate::sql::split_part;
@@ -71,7 +72,7 @@ impl Version {
7172
///
7273
/// * `Ok(BTreeMap<String, Vec<String>>)` - If the deserialization was successful.
7374
/// * `Err(serde_json::Error)` - If the deserialization failed.
74-
pub fn features(&self) -> Result<BTreeMap<String, Vec<String>>, serde_json::Error> {
75+
pub fn features(&self) -> Result<FeaturesMap, serde_json::Error> {
7576
BTreeMap::<String, Vec<String>>::deserialize(&self.features)
7677
}
7778
}

0 commit comments

Comments
 (0)