use crate::arch::NodeJSArch;
use crate::error::NodeJSRelInfoError;
use crate::ext::NodeJSPkgExt;
use crate::os::NodeJSOS;
use crate::url::NodeJSURLFormatter;
use semver::Version;
use std::str::FromStr;
pub fn validate_version<T: AsRef<str>>(semver: T) -> Result<String, NodeJSRelInfoError> {
match Version::parse(semver.as_ref()) {
Ok(v) => Ok(v.to_string()),
Err(_) => {
return Err(NodeJSRelInfoError::InvalidVersion(
semver.as_ref().to_owned(),
))
}
}
}
pub async fn fetch(
version: &String,
url_fmt: &NodeJSURLFormatter,
) -> Result<String, NodeJSRelInfoError> {
let info_url = url_fmt.info(version);
let res = match reqwest::get(info_url.as_str()).await {
Err(e) => return Err(NodeJSRelInfoError::HttpError(e)),
Ok(r) => r,
};
if res.status().as_u16() >= 400 {
return Err(NodeJSRelInfoError::UnrecognizedVersion(version.clone()));
}
match res.text().await {
Err(e) => Err(NodeJSRelInfoError::HttpError(e)),
Ok(b) => Ok(b),
}
}
pub type ParsedSpecs = Vec<(NodeJSOS, NodeJSArch, NodeJSPkgExt, String, String)>;
pub fn parse(version: &String, specs: String) -> Option<ParsedSpecs> {
let mut all: ParsedSpecs = vec![];
for line in specs.lines() {
let (sha256, filename) = match line.trim().split_once(' ') {
Some((s, f)) => (s.trim(), f.trim()),
None => ("", ""),
};
if sha256.is_empty() || filename.is_empty() {
continue;
}
if !filename.starts_with(format!("node-v{}", &version).as_str()) {
continue;
}
let parts: Vec<&str> = filename.split('-').collect();
let last = parts.last().unwrap(); let is_msi = last.ends_with(".msi");
if parts.len() < 4 && !is_msi {
continue;
}
let os = if is_msi { "win" } else { parts[2] };
let os = match NodeJSOS::from_str(os) {
Ok(os) => os,
Err(_) => {
continue;
}
};
let (arch, ext) = match last.split_once('.') {
Some((a, e)) => (a.trim(), e.trim()),
None => {
continue;
}
};
let arch = match NodeJSArch::from_str(arch) {
Ok(a) => a,
Err(_) => {
continue;
}
};
let ext = match NodeJSPkgExt::from_str(ext) {
Ok(ext) => ext,
Err(_) => {
continue;
}
};
let filename = filename.to_string();
let sha256 = sha256.to_string();
all.push((os, arch, ext, sha256, filename));
}
if all.is_empty() {
return None;
}
Some(all)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_validates_a_version_string() {
let version = validate_version("20.6.1").unwrap();
assert_eq!(version, String::from("20.6.1"));
let error = validate_version("NOPE").unwrap_err();
assert_eq!(
format!("{error}"),
"Error: Invalid Version! Received: 'NOPE'"
);
let error = validate_version("").unwrap_err();
assert_eq!(format!("{error}"), "Error: Invalid Version! Received: ''");
}
#[test]
fn it_parses_node_js_specs() {
let version = String::from("20.6.1");
let specs_raw = get_fake_specs().to_string();
let specs = parse(&version, specs_raw).unwrap();
assert_eq!(specs.len(), 24);
let (os, arch, ext, sha256, filename) = &specs[2];
assert_eq!(*os, NodeJSOS::Darwin);
assert_eq!(*arch, NodeJSArch::ARM64);
assert_eq!(*ext, NodeJSPkgExt::Targz);
assert_eq!(filename, "node-v20.6.1-darwin-arm64.tar.gz");
assert_eq!(
sha256,
"d8ba8018d45b294429b1a7646ccbeaeb2af3cdf45b5c91dabbd93e2a2035cb46"
);
}
#[test]
fn it_handles_empty_data_when_parsing_node_js_specs() {
let version = String::from("20.6.1");
let specs_raw = ["NOPE"];
assert!(parse(&version, specs_raw.join("\n").to_string()).is_none());
}
#[test]
fn it_ignores_invalid_data_when_parsing_node_js_specs() {
let version = String::from("20.6.1");
let specs_raw = ["NOPE", "FAKESHA node-v20.6.1-darwin-arm64.tar.gz"];
let specs = parse(&version, specs_raw.join("\n").to_string()).unwrap();
assert_is_darwin_arm64_targz_specs(specs);
}
#[test]
fn it_ignores_unknown_file_when_parsing_node_js_specs() {
let version = String::from("20.6.1");
let specs_raw = [
"FAKESHA win_x86/node.lib",
"FAKESHA node-v20.6.1-darwin-arm64.tar.gz",
];
let specs = parse(&version, specs_raw.join("\n").to_string()).unwrap();
assert_is_darwin_arm64_targz_specs(specs);
}
#[test]
fn it_ignores_unknown_filename_when_parsing_node_js_specs() {
let version = String::from("20.6.1");
let specs_raw = [
"FAKESHA NOPE-v20.6.1-darwin-arm64.tar.gz",
"FAKESHA node-v20.6.1-darwin-arm64.tar.gz",
];
let specs = parse(&version, specs_raw.join("\n").to_string()).unwrap();
assert_is_darwin_arm64_targz_specs(specs);
}
#[test]
fn it_ignores_malformed_filename_when_parsing_node_js_specs() {
let version = String::from("20.6.1");
let specs_raw = [
"FAKESHA node-v20.6.1-NOPE-",
"FAKESHA node-v20.6.1-darwin-arm64.tar.gz",
];
let specs = parse(&version, specs_raw.join("\n").to_string()).unwrap();
assert_is_darwin_arm64_targz_specs(specs);
}
#[test]
fn it_ignores_unknown_os_when_parsing_node_js_specs() {
let version = String::from("20.6.1");
let specs_raw = [
"FAKESHA node-v20.6.1-NOPE-arm64.tar.gz",
"FAKESHA node-v20.6.1-darwin-arm64.tar.gz",
];
let specs = parse(&version, specs_raw.join("\n").to_string()).unwrap();
assert_is_darwin_arm64_targz_specs(specs);
}
#[test]
fn it_ignores_unknown_arch_when_parsing_node_js_specs() {
let version = String::from("20.6.1");
let specs_raw = [
"FAKESHA node-v20.6.1-darwin-NOPE.tar.gz",
"FAKESHA node-v20.6.1-darwin-arm64.tar.gz",
];
let specs = parse(&version, specs_raw.join("\n").to_string()).unwrap();
assert_is_darwin_arm64_targz_specs(specs);
}
#[test]
fn it_ignores_unknown_ext_when_parsing_node_js_specs() {
let version = String::from("20.6.1");
let specs_raw = [
"FAKESHA node-v20.6.1-darwin-arm64.NOPE",
"FAKESHA node-v20.6.1-darwin-arm64.tar.gz",
];
let specs = parse(&version, specs_raw.join("\n").to_string()).unwrap();
assert_is_darwin_arm64_targz_specs(specs);
}
#[test]
fn it_ignores_missing_ext_when_parsing_node_js_specs() {
let version = String::from("20.6.1");
let specs_raw = [
"FAKESHA node-v20.6.1-darwin-arm64",
"FAKESHA node-v20.6.1-darwin-arm64.tar.gz",
];
let specs = parse(&version, specs_raw.join("\n").to_string()).unwrap();
assert_is_darwin_arm64_targz_specs(specs);
}
#[tokio::test]
async fn it_fetches_node_js_specs() {
let version = String::from("20.6.1");
let mut url_fmt = NodeJSURLFormatter::new();
let mut server = Server::new_async().await;
let mock = setup_server_mock(&version, &mut url_fmt, &mut server)
.with_body(get_fake_specs())
.create_async()
.await;
let specs = fetch(&version, &url_fmt).await.unwrap();
mock.assert_async().await;
assert_eq!(specs, get_fake_specs());
}
#[tokio::test]
#[should_panic(
expected = "called `Result::unwrap()` on an `Err` value: UnrecognizedVersion(\"1.0.0\")"
)]
async fn it_fails_to_fetch_node_js_specs_when_version_is_unrecognized() {
let version = String::from("1.0.0");
let mut url_fmt = NodeJSURLFormatter::new();
let mut server = Server::new_async().await;
let mock = setup_server_mock(&version, &mut url_fmt, &mut server)
.with_body(get_fake_specs())
.with_status(404)
.create_async()
.await;
fetch(&version, &url_fmt).await.unwrap();
mock.assert_async().await;
}
}
#[cfg(test)]
use mockito::{Mock, Server};
#[cfg(test)]
fn assert_is_darwin_arm64_targz_specs(specs: ParsedSpecs) {
assert_eq!(specs.len(), 1);
let (os, arch, ext, sha256, filename) = &specs[0];
assert_eq!(*os, NodeJSOS::Darwin);
assert_eq!(*arch, NodeJSArch::ARM64);
assert_eq!(*ext, NodeJSPkgExt::Targz);
assert_eq!(filename, "node-v20.6.1-darwin-arm64.tar.gz");
assert_eq!(sha256, "FAKESHA");
}
#[cfg(test)]
pub fn setup_server_mock(
version: &str,
url_fmt: &mut NodeJSURLFormatter,
server: &mut Server,
) -> Mock {
url_fmt.host = server.host_with_port();
url_fmt.protocol = "http:".to_string();
server.mock("GET", url_fmt.info_pathname(version).as_str())
}
#[cfg(test)]
pub fn get_fake_specs() -> &'static str {
"ea52b4feaf917e08cd2c729c1186585fcacef07c261a01310c91333b9e41d93c node-v20.6.1-aix-ppc64.tar.gz
9471bd6dc491e09c31b0f831f5953284b8a6842ed4ccb98f5c62d13e6086c471 node-v20.6.1-arm64.msi
d8ba8018d45b294429b1a7646ccbeaeb2af3cdf45b5c91dabbd93e2a2035cb46 node-v20.6.1-darwin-arm64.tar.gz
9c61b0d60fce962244d5e54549dc912e28b3c5f5e23149bfd15f66f8f7269129 node-v20.6.1-darwin-arm64.tar.xz
365ec544c6596f194afff9a613554abfc68d4a2274181b7651386d9a11cf5862 node-v20.6.1-darwin-x64.tar.gz
9b10c16670781e3a5af722656d28f264cdd8ebb3140f62692b33813100391349 node-v20.6.1-darwin-x64.tar.xz
d8271461ced2887f65af413949caee19db3e80d22bbefdaf01252ca998570052 node-v20.6.1-headers.tar.gz
60963e3ee60b6739e97e0c7b8ffb25848a82649c0c277af728400c570fd9db6d node-v20.6.1-headers.tar.xz
d38fe2e41e3fe8ae81b517b4cf49521f500e181e54f4c3d05e2b2d691a57b2ca node-v20.6.1-linux-arm64.tar.gz
6823720796b287465bb4aa8e7611143322ffd6cbdb9c6e3b149576f6d87953bf node-v20.6.1-linux-arm64.tar.xz
459510281ea51cf5d89fc666e36fbba80793ae4b90c3a7f89dd6666c65c825b3 node-v20.6.1-linux-armv7l.tar.gz
9dbd4fd7f804a28de91ffb8792df6e89bbb4f934fccd013624b3dabf8bf809ac node-v20.6.1-linux-armv7l.tar.xz
ca00f1aa8b2535fa167258cf5f2cfce4b79d83c442dd5e46f5e17d6a5749ec0f node-v20.6.1-linux-ppc64le.tar.gz
27884935b025b6676e4b8737f334673ee825947d0baef61aa0326374597aeb05 node-v20.6.1-linux-ppc64le.tar.xz
4a3f29cfc8a7ed1e9e44fcacb78e2fbaa3ce01be1efc4971a42710ad1e9e45d1 node-v20.6.1-linux-s390x.tar.gz
3968d629989b6de16b8872b6d7ee6e6cdf1204def99c43412a6ee28203ed0022 node-v20.6.1-linux-s390x.tar.xz
26dd13a6f7253f0ab9bcab561353985a297d927840771d905566735b792868da node-v20.6.1-linux-x64.tar.gz
591f9f274104f266a8cf085d2c7d5d2848ba73b98ae323d501db2d4c4b7026e5 node-v20.6.1-linux-x64.tar.xz
d9acf82d9576dd0350c8e66b55f6fc2750fa9f4aa23d6453ffc58e32af995894 node-v20.6.1.pkg
0053c09a01b1b355bca5af82927cae376124c13d74fa53567f08f4cfb085e6aa node-v20.6.1.tar.gz
3aec5e728daa38800c343b129221d3488064a2529a39bb5467bc55be226c6a2b node-v20.6.1.tar.xz
337549faf397deb0da3bccd4e27db45a619d89de4ea12830d16d9dfaded8e92c node-v20.6.1-win-arm64.7z
0e62045bfc9d7c38360bd7da152c75ed82087242d5e4b401fa23a439588d36f6 node-v20.6.1-win-arm64.zip
c6cfe7824770a266a30bee8c33f485d0e89b94254c682250a239d83adfb7ce77 node-v20.6.1-win-x64.7z
88371914f1f75d594bb367570e163cf5ecebeb514fd54cc765093819ebb0ed48 node-v20.6.1-win-x64.zip
87d631b294a25386400d0f44d227330da62a1326e2a4fbb98bda3d7c431257f1 node-v20.6.1-win-x86.7z
578cff623601aa8878a035f06edbf69190338ee3b345e7a096e804cb80c4ce24 node-v20.6.1-win-x86.zip
5c2616da46728dd1326645c7db114e78ad87138a258c0724a035269258c23509 node-v20.6.1-x64.msi
cb83586af83182187e760b7e01aa7c7b2bacb521d60ceefed3ac6fc62c222449 node-v20.6.1-x86.msi
7cc3240fd7ce7926eef1cbbad33b033f7c5d97b3f3e527d65ff1e2c3f7638a11 win-arm64/node.exe
deb027ded744371657811cfe52e774881ea928d36779924af84aa9a7a31104d2 win-arm64/node.lib
dcb6b4bc6f2a78bf0f759853b59e94ddbe9ad6b9f32d24fdcf590d74c6350bc2 win-arm64/node_pdb.7z
bdcd574e99646ec4a03bb13b3661c957f5a7ca837f5c33827075c4262d449689 win-arm64/node_pdb.zip
5b824f3a375cca06dfd7dc70fa341a6ef8bb0b2e912358d8602a0c7ad273b9a4 win-x64/node.exe
d275cfc4d637d2feaf4c39e1a5f5cd84f5b474fa713c15013e940c329feed13b win-x64/node.lib
fea6c0fcff45739a6e5af9843ec45455c97ff8677167bd649fd48cbef59ca52d win-x64/node_pdb.7z
bc13f5e63c1510cd41f82dc20725f40bbfa378252e09a00a8531cddabbf1b106 win-x64/node_pdb.zip
837db0d8fb7fa194ebe23cd34ac7bedc02d1132de67cf4f147d694574be5cc4e win-x86/node.exe
a0738dec64427ae73eeb1d036081652c1c0223a679a63e0459c2af667f284f58 win-x86/node.lib
516ac820f05eb8478be541ac12386c3b5b5c07624f73934bcf0b11a3fcdb1c95 win-x86/node_pdb.7z
9b68f3e1f1717a2f6a090e1679f8cc627566ed064c657c35eddd0dba9484e310 win-x86/node_pdb.zip"
}