Skip to main content

logicaffeine_web/ui/
data_fetch.rs

1//! Runtime fetch of build-staged static data.
2//!
3//! `scripts/web/stage-web-data.sh` publishes the repo's data files (benchmark results,
4//! program sources, legal HTML, lexicon) under `/data/` next to the app bundle.
5//! Native builds compile the same bytes in via `include_str!`; these helpers are the
6//! wasm side of that split, so the data never rides inside the shipped binary.
7
8#[cfg(target_arch = "wasm32")]
9pub async fn fetch_static_text(path: &str) -> Result<String, String> {
10    let response = gloo_net::http::Request::get(path)
11        .send()
12        .await
13        .map_err(|e| format!("fetching {path}: {e}"))?;
14    if !response.ok() {
15        return Err(format!("fetching {path}: HTTP {}", response.status()));
16    }
17    let content_type = response.headers().get("content-type");
18    let text = response
19        .text()
20        .await
21        .map_err(|e| format!("reading {path}: {e}"))?;
22    check_served(path, content_type.as_deref(), &text)?;
23    Ok(text)
24}
25
26#[cfg(target_arch = "wasm32")]
27pub async fn fetch_static_json<T: serde::de::DeserializeOwned>(path: &str) -> Result<T, String> {
28    let text = fetch_static_text(path).await?;
29    serde_json::from_str(&text).map_err(|e| format!("parsing {path}: {e}"))
30}
31
32/// Whether what came back for a staged `/data` file is that file. A server with an SPA
33/// fallback answers a missing file with the app's `index.html` and a 200, so a missing
34/// `lexicon.json` would otherwise surface as a JSON parse error ("expected value at line 1
35/// column 1") instead of saying the file is not deployed.
36pub fn check_served(path: &str, content_type: Option<&str>, body: &str) -> Result<(), String> {
37    if path.ends_with(".html") || path.ends_with(".htm") {
38        return Ok(());
39    }
40    let head: String = body.trim_start().chars().take(15).collect::<String>().to_ascii_lowercase();
41    if head.starts_with("<!doctype html") || head.starts_with("<html") {
42        let served_as = content_type.map(|t| format!(" as {t}")).unwrap_or_default();
43        return Err(format!(
44            "{path} is missing from this deployment — the server answered with its HTML page{served_as} (the /data bundle was not staged)"
45        ));
46    }
47    Ok(())
48}
49
50#[cfg(test)]
51#[path = "../../../../crates/logicaffeine_tests/unit/logicaffeine_web/ui/data_fetch/tests.rs"]
52mod tests;