use crate::api::version::Version;
use crate::error::Error;
use platform::Platform;
use reqwest::Url;
use rootcause::prelude::ResultExt;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
pub mod channel;
pub mod platform;
pub mod version;
pub mod known_good_versions;
pub mod last_known_good_versions;
pub static API_BASE_URL: LazyLock<Url> =
LazyLock::new(|| Url::parse("https://googlechromelabs.github.io").expect("Valid URL"));
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Download {
pub platform: Platform,
pub url: String,
}
impl Download {
pub fn parsed_url(&self) -> crate::Result<Url> {
Url::parse(&self.url).context_to::<Error>().attach_with(|| {
format!(
"while parsing Chrome for Testing download URL: '{}'",
self.url
)
})
}
}
pub trait DownloadsByPlatform {
fn for_platform(&self, platform: Platform) -> Option<&Download>;
}
impl DownloadsByPlatform for [Download] {
fn for_platform(&self, platform: Platform) -> Option<&Download> {
self.iter().find(|d| d.platform == platform)
}
}
pub trait HasVersion {
fn version(&self) -> Version;
}
impl HasVersion for known_good_versions::VersionWithoutChannel {
fn version(&self) -> Version {
self.version
}
}
impl HasVersion for last_known_good_versions::VersionInChannel {
fn version(&self) -> Version {
self.version
}
}
pub(crate) async fn fetch_endpoint<T>(
client: &reqwest::Client,
base_url: &Url,
path: &str,
endpoint_name: &str,
) -> crate::Result<T>
where
T: DeserializeOwned,
{
let url = base_url.join(path).context_to::<Error>().attach_with(|| {
format!("while joining Chrome for Testing {endpoint_name} endpoint path: {path}")
})?;
let result = client
.get(url)
.send()
.await
.context_to::<Error>()
.attach_with(|| format!("while sending Chrome for Testing {endpoint_name} request"))?
.error_for_status()
.context_to::<Error>()?
.json::<T>()
.await
.context_to::<Error>()
.attach_with(|| {
format!("while deserializing Chrome for Testing {endpoint_name} response")
})?;
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use assertr::prelude::*;
#[test]
fn parsed_url_returns_typed_url() {
let download = Download {
platform: Platform::Linux64,
url: String::from("https://example.com/chrome.zip"),
};
assert_that!(download.parsed_url().map(|url| url.to_string()))
.is_ok()
.is_equal_to("https://example.com/chrome.zip");
}
#[test]
fn parsed_url_reports_invalid_urls() {
let download = Download {
platform: Platform::Linux64,
url: String::from("not a url"),
};
let err = download.parsed_url().unwrap_err();
let Error::UrlParsing(url_error) = err.current_context() else {
panic!("expected URL parse error, got: {:?}", err.current_context());
};
assert_that!(url_error.to_string()).contains("relative URL without a base");
}
#[tokio::test]
async fn fetch_endpoint_path_is_root_relative_when_base_url_has_path_prefix() {
let mut server = mockito::Server::new_async().await;
let endpoint_path = "/chrome-for-testing/test-endpoint.json";
let _mock = server
.mock("GET", endpoint_path)
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"ok":true}"#)
.create();
let url: Url = format!("{}/prefix/", server.url()).parse().unwrap();
let data = fetch_endpoint::<serde_json::Value>(
&reqwest::Client::new(),
&url,
endpoint_path,
"TestEndpoint",
)
.await
.unwrap();
assert_that!(data["ok"].as_bool()).is_equal_to(Some(true));
}
}