psh_webdb/
lib.rs

1//! This crate provides alias database backend for [`psh`] using LocalStorage Web API
2//!
3//! This crate is intended to be used along with [`psh`] when building for WASM targets.
4//!
5//! [`psh`]: https://docs.rs/psh/latest/psh
6
7use std::fmt;
8
9use anyhow::{bail, Result};
10use web_sys::{self, Storage};
11
12use psh::{PshStore, ZeroizingString};
13
14/// WASM compatible `psh` alias database.
15pub struct PshWebDb {
16    local_storage: Storage,
17}
18
19impl PshWebDb {
20    pub fn new() -> Self {
21        let window = web_sys::window().expect("Window not available");
22        let local_storage = window.local_storage()
23            .expect("LocalStorage not available")
24            .expect("LocalStorage not defined");
25
26        Self { local_storage }
27    }
28}
29
30impl PshStore for PshWebDb {
31    fn exists(&self) -> bool {
32        self.records().next().is_some()
33    }
34
35    fn records(&self) -> Box<dyn Iterator<Item=ZeroizingString>> {
36        Box::new(PshWebDbIter::new(self.local_storage.clone()))
37    }
38
39    fn append(&mut self, record: &ZeroizingString) -> Result<()> {
40        match self.local_storage.set_item(record, "psh") {
41            Ok(_) => Ok(()),
42            Err(js_err) => bail!(Error::AliasAppendError(
43                    ZeroizingString::new(js_err.as_string().unwrap())))
44        }
45    }
46
47    fn delete(&mut self, record: &ZeroizingString) -> Result<()> {
48        match self.local_storage.remove_item(record) {
49            Ok(_) => Ok(()),
50            Err(js_err) => bail!(Error::AliasRemoveError(
51                    ZeroizingString::new(js_err.as_string().unwrap())))
52        }
53    }
54}
55
56struct PshWebDbIter {
57    store: Storage,
58    index: u32,
59}
60
61impl PshWebDbIter {
62    pub fn new(store: Storage) -> Self {
63        Self {
64            store,
65            index: 0,
66        }
67    }
68}
69
70impl Iterator for PshWebDbIter {
71    type Item = ZeroizingString;
72
73    fn next(&mut self) -> Option<Self::Item> {
74        if let Ok(ls_length) = self.store.length() {
75            if ls_length > self.index {
76                while let Ok(Some(key)) = self.store.key(self.index) {
77                    self.index += 1;
78                    if let Ok(Some(value)) = self.store.get_item(&key) {
79                        if value == "psh" {
80                            return Some(ZeroizingString::new(key));
81                        }
82                    }
83                    if self.index == ls_length {
84                        break;
85                    }
86                }
87            }
88        }
89        None
90    }
91}
92
93/// Error type.
94#[derive(Debug)]
95enum Error {
96    /// Error appending alias to database.
97    AliasAppendError(ZeroizingString),
98
99    /// Error removing alias from database.
100    AliasRemoveError(ZeroizingString),
101}
102
103impl fmt::Display for Error {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            Error::AliasAppendError(err_string) => write!(
107                f, "Cannot add alias to DB: {}", err_string
108            ),
109            Error::AliasRemoveError(err_string) => write!(
110                f, "Cannot remove alias from DB: {}", err_string
111            ),
112        }
113    }
114}
115
116impl std::error::Error for Error {}