Skip to main content

blockworx_opfs/
fault.rs

1//! A browser's refusal, in the words the store speaks.
2
3use wasm_bindgen::JsValue;
4use wasm_bindgen_futures::JsFuture;
5
6/// Settle `promise` as an I/O answer.
7///
8/// # Errors
9/// Whatever the browser rejected it with, read as [`faulted`] reads one.
10pub async fn settled(promise: js_sys::Promise) -> std::io::Result<JsValue> {
11    JsFuture::from(promise).await.map_err(faulted)
12}
13
14/// A rejection as an [`std::io::Error`].
15///
16/// The exception's own name is what decides the kind, so "nothing is
17/// there" and "this is not ours to write" reach the container in the
18/// spelling it already acts on — a read of an entry that was never written
19/// answers `None` in an origin exactly as it does in a directory.
20///
21/// By value because that is the shape `map_err` asks for, which is the only
22/// way this is ever called.
23#[expect(clippy::needless_pass_by_value)]
24pub fn faulted(why: JsValue) -> std::io::Error {
25    let named = read(&why, "name").unwrap_or_default();
26    let said = read(&why, "message").unwrap_or_else(|| format!("{why:?}"));
27    let told = if named.is_empty() {
28        said
29    } else {
30        format!("{named}: {said}")
31    };
32    match named.as_str() {
33        "NotFoundError" => std::io::Error::new(std::io::ErrorKind::NotFound, told),
34        "TypeMismatchError" | "InvalidModificationError" => {
35            std::io::Error::new(std::io::ErrorKind::InvalidInput, told)
36        }
37        "NoModificationAllowedError" | "NotAllowedError" | "SecurityError" => {
38            std::io::Error::new(std::io::ErrorKind::PermissionDenied, told)
39        }
40        _ => std::io::Error::other(told),
41    }
42}
43
44fn read(value: &JsValue, field: &str) -> Option<String> {
45    js_sys::Reflect::get(value, &JsValue::from_str(field))
46        .ok()
47        .and_then(|read| read.as_string())
48}
49
50/// A handle the origin answered with where it was asked for `asked` — the
51/// one refusal that is ours rather than the browser's.
52pub fn mistaken(asked: &'static str) -> impl FnOnce(JsValue) -> std::io::Error {
53    move |was| {
54        std::io::Error::new(
55            std::io::ErrorKind::InvalidData,
56            format!("origin-private storage answered {was:?} where {asked} was asked for"),
57        )
58    }
59}