Skip to content

Commit cd17a95

Browse files
committed
feat(baseline): support new baseline
1 parent 667666d commit cd17a95

File tree

8 files changed

+161
-72
lines changed

8 files changed

+161
-72
lines changed

crates/rari-data/src/baseline.rs

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,21 @@ impl WebFeatures {
2121
Ok(serde_json::from_str(&json_str)?)
2222
}
2323

24-
pub fn feature_status(&self, features: &[&str]) -> Option<&SupportStatus> {
25-
if features.is_empty() {
26-
return None;
27-
}
28-
24+
pub fn feature_status(&self, bcd_key: &str) -> Option<&SupportStatusWithByKey> {
2925
self.features.values().find_map(|feature_data| {
3026
if let Some(ref status) = feature_data.status {
3127
if feature_data
3228
.compat_features
3329
.iter()
34-
.any(|key| features.contains(&key.as_str()))
30+
.any(|key| key == bcd_key)
3531
{
36-
return Some(status);
32+
if let Some(by_key) = &status.by_compat_key {
33+
if let Some(key_status) = by_key.get(bcd_key) {
34+
if key_status.baseline == status.baseline {
35+
return Some(status);
36+
}
37+
}
38+
}
3739
}
3840
}
3941
None
@@ -59,14 +61,29 @@ pub struct FeatureData {
5961
pub caniuse: Vec<String>,
6062
/** Whether a feature is considered a "baseline" web platform feature and when it achieved that status */
6163
#[serde(skip_serializing_if = "Option::is_none")]
62-
pub status: Option<SupportStatus>,
64+
pub status: Option<SupportStatusWithByKey>,
6365
/** Sources of support data for this feature */
6466
#[serde(
6567
deserialize_with = "t_or_vec",
6668
default,
6769
skip_serializing_if = "Vec::is_empty"
6870
)]
6971
pub compat_features: Vec<String>,
72+
pub description: String,
73+
pub description_html: String,
74+
#[serde(
75+
deserialize_with = "t_or_vec",
76+
default,
77+
skip_serializing_if = "Vec::is_empty"
78+
)]
79+
pub group: Vec<String>,
80+
pub name: String,
81+
#[serde(
82+
deserialize_with = "t_or_vec",
83+
default,
84+
skip_serializing_if = "Vec::is_empty"
85+
)]
86+
pub snapshot: Vec<String>,
7087
}
7188

7289
#[derive(Deserialize, Serialize, Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
@@ -81,7 +98,7 @@ pub enum BrowserIdentifier {
8198
SafariIos,
8299
}
83100

84-
#[derive(Deserialize, Serialize, Clone, Copy, Debug)]
101+
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Eq)]
85102
#[serde(rename_all = "snake_case")]
86103
pub enum BaselineHighLow {
87104
High,
@@ -105,6 +122,23 @@ pub struct SupportStatus {
105122
pub support: BTreeMap<BrowserIdentifier, String>,
106123
}
107124

125+
#[derive(Deserialize, Serialize, Clone, Debug)]
126+
pub struct SupportStatusWithByKey {
127+
/// Whether the feature is Baseline (low substatus), Baseline (high substatus), or not (false)
128+
#[serde(skip_serializing_if = "Option::is_none")]
129+
pub baseline: Option<BaselineHighLow>,
130+
/// Date the feature achieved Baseline low status
131+
#[serde(skip_serializing_if = "Option::is_none")]
132+
pub baseline_low_date: Option<String>,
133+
/// Date the feature achieved Baseline high status
134+
#[serde(skip_serializing_if = "Option::is_none")]
135+
pub baseline_high_date: Option<String>,
136+
/// Browser versions that most-recently introduced the feature
137+
pub support: BTreeMap<BrowserIdentifier, String>,
138+
#[serde(default, skip_serializing)]
139+
pub by_compat_key: Option<BTreeMap<String, SupportStatus>>,
140+
}
141+
108142
pub fn t_or_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
109143
where
110144
D: Deserializer<'de>,

crates/rari-deps/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ pub enum DepsError {
1010
RariIoError(#[from] rari_utils::error::RariIoError),
1111
#[error(transparent)]
1212
FetchError(#[from] reqwest::Error),
13+
#[error(transparent)]
14+
HeaderError(#[from] reqwest::header::ToStrError),
1315
#[error("no version for webref")]
1416
WebRefMissingVersionError,
1517
#[error("no tarball for webref")]
1618
WebRefMissingTarballError,
19+
#[error("Invalid github version")]
20+
InvalidGitHubVersion,
1721
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use std::fs::{self, File};
2+
use std::io::{BufWriter, Read, Write};
3+
use std::path::{Path, PathBuf};
4+
5+
use chrono::{DateTime, Duration, Utc};
6+
use rari_utils::io::read_to_string;
7+
use reqwest::redirect::Policy;
8+
use serde::{Deserialize, Serialize};
9+
10+
use crate::error::DepsError;
11+
12+
#[derive(Deserialize, Serialize, Default, Debug)]
13+
pub struct Current {
14+
pub latest_last_check: Option<DateTime<Utc>>,
15+
pub version: String,
16+
}
17+
18+
/// Download and unpack an npm package for a given version (defaults to latest).
19+
pub fn get_artifact(
20+
base_url: &str,
21+
artifact: &str,
22+
name: &str,
23+
version: Option<&str>,
24+
out_path: &Path,
25+
) -> Result<Option<PathBuf>, DepsError> {
26+
let version = version.unwrap_or("latest");
27+
let package_path = out_path.join(name);
28+
let last_check_path = package_path.join("last_check.json");
29+
let now = Utc::now();
30+
let current = read_to_string(last_check_path)
31+
.ok()
32+
.and_then(|current| serde_json::from_str::<Current>(&current).ok())
33+
.unwrap_or_default();
34+
if version != current.version
35+
|| version == "latest"
36+
&& current.latest_last_check.unwrap_or_default() < now - Duration::days(1)
37+
{
38+
let prev_url = format!(
39+
"{base_url}/releases/download/{}/{artifact}",
40+
current.version
41+
);
42+
let url = if version == "latest" {
43+
let client = reqwest::blocking::ClientBuilder::default()
44+
.redirect(Policy::none())
45+
.build()?;
46+
let res = client
47+
.get(format!("{base_url}/releases/latest/download/{artifact}"))
48+
.send()?;
49+
res.headers()
50+
.get("location")
51+
.ok_or(DepsError::InvalidGitHubVersion)?
52+
.to_str()?
53+
.to_string()
54+
} else {
55+
format!("{base_url}/releases/download/{version}/{artifact}")
56+
};
57+
58+
let artifact_path = package_path.join(artifact);
59+
let download_update = if artifact_path.exists() {
60+
prev_url != url
61+
} else {
62+
true
63+
};
64+
65+
if download_update {
66+
if package_path.exists() {
67+
fs::remove_dir_all(&package_path)?;
68+
}
69+
fs::create_dir_all(&package_path)?;
70+
let mut buf = vec![];
71+
let _ = reqwest::blocking::get(url)?.read_to_end(&mut buf)?;
72+
73+
let file = File::create(artifact_path).unwrap();
74+
let mut buffed = BufWriter::new(file);
75+
76+
buffed.write_all(&buf)?;
77+
}
78+
79+
if version == "latest" {
80+
fs::write(
81+
package_path.join("last_check.json"),
82+
serde_json::to_string_pretty(&Current {
83+
version: version.to_string(),
84+
latest_last_check: Some(now),
85+
})?,
86+
)?;
87+
}
88+
if download_update {
89+
return Ok(Some(package_path));
90+
}
91+
}
92+
Ok(None)
93+
}

crates/rari-deps/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod bcd;
22
pub mod error;
33
pub mod external_json;
4+
pub mod github_release;
45
pub mod mdn_data;
56
pub mod npm;
67
pub mod web_ext_examples;

crates/rari-deps/src/web_features.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
use std::path::Path;
22

33
use crate::error::DepsError;
4-
use crate::npm::get_package;
4+
use crate::github_release::get_artifact;
55

66
pub fn update_web_features(base_path: &Path) -> Result<(), DepsError> {
7-
get_package("web-features", None, base_path)?;
7+
//get_package("web-features", None, base_path)?;
8+
get_artifact(
9+
"https://github.com/web-platform-dx/web-features",
10+
"data.extended.json",
11+
"baseline",
12+
None,
13+
base_path,
14+
)?;
815
Ok(())
916
}

crates/rari-doc/src/baseline.rs

Lines changed: 8 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
use std::sync::LazyLock;
22

3-
use rari_data::baseline::{SupportStatus, WebFeatures};
3+
use rari_data::baseline::{SupportStatusWithByKey, WebFeatures};
44
use rari_types::globals::data_dir;
55
use tracing::warn;
66

77
static WEB_FEATURES: LazyLock<Option<WebFeatures>> = LazyLock::new(|| {
8-
let web_features = WebFeatures::from_file(
9-
&data_dir()
10-
.join("web-features")
11-
.join("package")
12-
.join("data.json"),
13-
);
8+
let web_features =
9+
WebFeatures::from_file(&data_dir().join("baseline").join("data.extended.json"));
1410
match web_features {
1511
Ok(web_features) => Some(web_features),
1612
Err(e) => {
@@ -20,58 +16,12 @@ static WEB_FEATURES: LazyLock<Option<WebFeatures>> = LazyLock::new(|| {
2016
}
2117
});
2218

23-
static DISALLOW_LIST: &[&str] = &[
24-
// https://github.com/web-platform-dx/web-features/blob/cf718ad/feature-group-definitions/async-clipboard.yml
25-
"api.Clipboard.read",
26-
"api.Clipboard.readText",
27-
"api.Clipboard.write",
28-
"api.Clipboard.writeText",
29-
"api.ClipboardEvent",
30-
"api.ClipboardEvent.ClipboardEvent",
31-
"api.ClipboardEvent.clipboardData",
32-
"api.ClipboardItem",
33-
"api.ClipboardItem.ClipboardItem",
34-
"api.ClipboardItem.getType",
35-
"api.ClipboardItem.presentationStyle",
36-
"api.ClipboardItem.types",
37-
"api.Navigator.clipboard",
38-
"api.Permissions.permission_clipboard-read",
39-
// https://github.com/web-platform-dx/web-features/blob/cf718ad/feature-group-definitions/custom-elements.yml
40-
"api.CustomElementRegistry",
41-
"api.CustomElementRegistry.builtin_element_support",
42-
"api.CustomElementRegistry.define",
43-
"api.Window.customElements",
44-
"css.selectors.defined",
45-
"css.selectors.host",
46-
"css.selectors.host-context",
47-
"css.selectors.part",
48-
// https://github.com/web-platform-dx/web-features/blob/cf718ad/feature-group-definitions/input-event.yml
49-
"api.Element.input_event",
50-
"api.InputEvent.InputEvent",
51-
"api.InputEvent.data",
52-
"api.InputEvent.dataTransfer",
53-
"api.InputEvent.getTargetRanges",
54-
"api.InputEvent.inputType",
55-
// https://github.com/web-platform-dx/web-features/issues/1038
56-
// https://github.com/web-platform-dx/web-features/blob/64d2cfd/features/screen-orientation-lock.dist.yml
57-
"api.ScreenOrientation.lock",
58-
"api.ScreenOrientation.unlock",
59-
];
60-
61-
pub fn get_baseline(browser_compat: &[String]) -> Option<&'static SupportStatus> {
19+
pub fn get_baseline(browser_compat: &[String]) -> Option<&'static SupportStatusWithByKey> {
6220
if let Some(ref web_features) = *WEB_FEATURES {
63-
if browser_compat.is_empty() {
64-
return None;
65-
}
66-
let filtered_browser_compat = browser_compat.iter().filter_map(
67-
|query|
68-
// temporary blocklist while we wait for per-key baseline statuses
69-
// or another solution to the baseline/bcd table discrepancy problem
70-
if !DISALLOW_LIST.contains(&query.as_str()) {
71-
Some(query.as_str())
72-
} else {None}
73-
).collect::<Vec<&str>>();
74-
return web_features.feature_status(&filtered_browser_compat);
21+
return match &browser_compat {
22+
&[bcd_key] => web_features.feature_status(bcd_key.as_str()),
23+
_ => None,
24+
};
7525
}
7626
None
7727
}

crates/rari-doc/src/docs/json.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::path::PathBuf;
22

33
use chrono::NaiveDateTime;
4-
use rari_data::baseline::SupportStatus;
4+
use rari_data::baseline::SupportStatusWithByKey;
55
use rari_types::locale::{Locale, Native};
66
use serde::Serialize;
77

@@ -111,7 +111,7 @@ pub struct JsonDoc {
111111
pub title: String,
112112
pub toc: Vec<TocEntry>,
113113
#[serde(skip_serializing_if = "Option::is_none")]
114-
pub baseline: Option<&'static SupportStatus>,
114+
pub baseline: Option<&'static SupportStatusWithByKey>,
115115
#[serde(rename = "browserCompat", skip_serializing_if = "Vec::is_empty")]
116116
pub browser_compat: Vec<String>,
117117
}

crates/rari-doc/src/docs/title.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub fn root_doc_url(url: &str) -> Option<&str> {
5353
if url[m[1]..].starts_with("/blog") || url[m[1]..].starts_with("/curriculum") {
5454
return None;
5555
}
56-
if url[m[1]..].starts_with("/docs/Web") {
56+
if url[m[1]..].starts_with("/docs/Web/") {
5757
return Some(&url[..*m.get(4).unwrap_or(&url.len())]);
5858
}
5959
Some(&url[..*m.get(3).unwrap_or(&url.len())])

0 commit comments

Comments
 (0)