Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/db/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, FromSql, ToSql)]
#[postgres(name = "feature")]
pub struct Feature {
name: String,
subfeatures: Vec<String>,
pub(crate) name: String,
pub(crate) subfeatures: Vec<String>,
}

impl Feature {
Expand Down
35 changes: 32 additions & 3 deletions src/web/crate_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ impl CrateDetails {
releases.license,
releases.documentation_url,
releases.default_target,
releases.features,
doc_coverage.total_items,
doc_coverage.documented_items,
doc_coverage.total_items_needing_examples,
Expand Down Expand Up @@ -149,7 +148,6 @@ impl CrateDetails {
default_target: krate.get("default_target"),
doc_targets: MetaData::parse_doc_targets(krate.get("doc_targets")),
yanked: krate.get("yanked"),
features: MetaData::parse_features(krate.get("features")),
};

let documented_items: Option<i32> = krate.get("documented_items");
Expand Down Expand Up @@ -817,14 +815,45 @@ mod tests {
});
}

#[test]
fn feature_flags_with_nested_default() {
wrapper(|env| {
let features = [
("default".into(), vec!["feature1".into()]),
("feature1".into(), vec!["feature2".into()]),
("feature2".into(), Vec::new()),
]
.iter()
.cloned()
.collect::<HashMap<String, Vec<String>>>();
env.fake_release()
.name("library")
.version("0.1.0")
.features(features)
.create()?;

let page = kuchiki::parse_html().one(
env.frontend()
.get("/crate/library/0.1.0/features")
.send()?
.text()?,
);
assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_err());
let def_len = page
.select_first(r#"b[data-id="default-feature-len"]"#)
.unwrap();
assert_eq!(def_len.text_contents(), "3");
Ok(())
});
}

#[test]
fn feature_flags_report_null() {
wrapper(|env| {
let id = env
.fake_release()
.name("library")
.version("0.1.0")
.features(HashMap::new())
.create()?;

env.db()
Expand Down
58 changes: 58 additions & 0 deletions src/web/features.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::db::types::Feature;
use crate::{
db::Pool,
impl_webpage,
Expand All @@ -6,10 +7,13 @@ use crate::{
use iron::{IronResult, Request, Response};
use router::Router;
use serde::Serialize;
use std::collections::{HashMap, VecDeque};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct FeaturesPage {
metadata: MetaData,
features: Option<Vec<Feature>>,
default_len: usize,
}

impl_webpage! {
Expand All @@ -22,9 +26,63 @@ pub fn build_features_handler(req: &mut Request) -> IronResult<Response> {
let version = cexpect!(req, router.find("version"));

let mut conn = extension!(req, Pool).get()?;
let rows = ctry!(
req,
conn.query(
"SELECT releases.features FROM releases
INNER JOIN crates ON crates.id = releases.crate_id
WHERE crates.name = $1 AND releases.version = $2",
&[&name, &version]
)
);

let row = cexpect!(req, rows.get(0));

let mut default_len = 0;
let features = row
.get::<'_, usize, Option<Vec<Feature>>>(0)
.map(|raw| {
raw.into_iter()
.filter(|feature| !feature.is_private())
.map(|feature| (feature.name.clone(), feature))
.collect::<HashMap<String, Feature>>()
})
.map(|mut feature_map| {
let mut features = get_tree_structure_from_default(&mut feature_map);
let mut remaining = feature_map
.into_iter()
.map(|(_, feature)| feature)
.collect::<Vec<Feature>>();
remaining.sort_by_key(|feature| feature.subfeatures.len());

default_len = features.len();

features.extend(remaining.into_iter().rev());
features
});

FeaturesPage {
metadata: cexpect!(req, MetaData::from_crate(&mut conn, &name, &version)),
features,
default_len,
}
.into_response(req)
}

fn get_tree_structure_from_default(feature_map: &mut HashMap<String, Feature>) -> Vec<Feature> {
let mut features = Vec::new();
let mut queue: VecDeque<String> = VecDeque::new();

queue.push_back("default".into());
while !queue.is_empty() {
let name = queue.pop_front().unwrap();
if let Some(feature) = feature_map.remove(&name) {
feature
.subfeatures
.iter()
.for_each(|sub| queue.push_back(sub.clone()));
features.push(feature);
}
}
features
}
18 changes: 1 addition & 17 deletions src/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ mod sitemap;
mod source;
mod statics;

use crate::db::types::Feature;
use crate::{impl_webpage, Context};
use chrono::{DateTime, Utc};
use error::Nope;
Expand Down Expand Up @@ -522,7 +521,6 @@ pub(crate) struct MetaData {
pub(crate) default_target: String,
pub(crate) doc_targets: Vec<String>,
pub(crate) yanked: bool,
pub(crate) features: Option<Vec<Feature>>,
}

impl MetaData {
Expand All @@ -536,8 +534,7 @@ impl MetaData {
releases.rustdoc_status,
releases.default_target,
releases.doc_targets,
releases.yanked,
releases.features
releases.yanked
FROM releases
INNER JOIN crates ON crates.id = releases.crate_id
WHERE crates.name = $1 AND releases.version = $2",
Expand All @@ -556,7 +553,6 @@ impl MetaData {
default_target: row.get(5),
doc_targets: MetaData::parse_doc_targets(row.get(6)),
yanked: row.get(7),
features: MetaData::parse_features(row.get(8)),
})
}

Expand All @@ -571,14 +567,6 @@ impl MetaData {
})
.unwrap_or_else(Vec::new)
}

pub(crate) fn parse_features(features: Option<Vec<Feature>>) -> Option<Vec<Feature>> {
features.map(|vec| {
vec.into_iter()
.filter(|feature| !feature.is_private())
.collect()
})
}
}

#[derive(Debug, Clone, PartialEq, Serialize)]
Expand Down Expand Up @@ -857,7 +845,6 @@ mod test {
"arm64-unknown-linux-gnu".to_string(),
],
yanked: false,
features: None,
};

let correct_json = json!({
Expand All @@ -872,7 +859,6 @@ mod test {
"arm64-unknown-linux-gnu",
],
"yanked": false,
"features": null
});

assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());
Expand All @@ -890,7 +876,6 @@ mod test {
"arm64-unknown-linux-gnu",
],
"yanked": false,
"features": null,
});

assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());
Expand All @@ -908,7 +893,6 @@ mod test {
"arm64-unknown-linux-gnu",
],
"yanked": false,
"features": null,
});

assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap());
Expand Down
4 changes: 1 addition & 3 deletions src/web/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ impl FileList {
releases.files,
releases.default_target,
releases.doc_targets,
releases.yanked,
releases.features
releases.yanked
FROM releases
LEFT OUTER JOIN crates ON crates.id = releases.crate_id
WHERE crates.name = $1 AND releases.version = $2",
Expand Down Expand Up @@ -138,7 +137,6 @@ impl FileList {
default_target: rows[0].get(6),
doc_targets: MetaData::parse_doc_targets(rows[0].get(7)),
yanked: rows[0].get(8),
features: MetaData::parse_features(rows[0].get(9)),
},
files: file_list,
})
Expand Down
20 changes: 7 additions & 13 deletions templates/crate/features.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@
<div class="pure-menu package-menu">
<ul class="pure-menu-list">
<li class="pure-menu-heading">Feature flags</li>
{%- if metadata.features -%}
{%- for feature in metadata.features -%}
{%- if features -%}
{%- for feature in features -%}
<li class="pure-menu-item">
<a href="#{{ feature.name }}" class="pure-menu-link" style="text-align:center;">
{{ feature.name }}
</a>
</li>
{%- endfor -%}
{%- elif metadata.features is iterable -%}
{%- elif features is iterable -%}
<li class="pure-menu-item">
<span style="font-size: 13px;">This release does not have any feature flags.</span>
</li>
Expand All @@ -49,15 +49,9 @@

<div class="pure-u-1 pure-u-sm-17-24 pure-u-md-19-24 package-details" id="main">
<h1>{{ metadata.name }}</h1>
{%- if metadata.features -%}
<p>This version has <b>{{ metadata.features | length }}</b> feature flags, <b data-id="default-feature-len">
{%- if metadata.features[0].name == 'default' -%}
{{ metadata.features[0].subfeatures | length }}
{%- else -%}
0
{%- endif -%}
</b> of them enabled by <b>default</b>.</p>
{%- for feature in metadata.features -%}
{%- if features -%}
<p>This version has <b>{{ features | length }}</b> feature flags, <b data-id="default-feature-len">{{ default_len }}</b> of them enabled by <b>default</b>.</p>
{%- for feature in features -%}
<h3 id="{{ feature.name }}">{{ feature.name }}</h3>
<ul class="pure-menu-list">
{%- if feature.subfeatures -%}
Expand All @@ -71,7 +65,7 @@ <h3 id="{{ feature.name }}">{{ feature.name }}</h3>
{%- endif -%}
</ul>
{%- endfor -%}
{%- elif metadata.features is iterable -%}
{%- elif features is iterable -%}
<p data-id="empty-features">This release does not have any feature flags.</p>
{%- else -%}
<p data-id="null-features">Feature flags data are not available for this release.</p>
Expand Down