Skip to content

Commit 25cc6e8

Browse files
authored
Add wrappers for web storage API (#125)
* Add wrappers for web storage API * Update README
1 parent ebcce02 commit 25cc6e8

File tree

11 files changed

+328
-2
lines changed

11 files changed

+328
-2
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ gloo-console-timer = { version = "0.1.0", path = "crates/console-timer" }
1616
gloo-events = { version = "0.1.0", path = "crates/events" }
1717
gloo-file = { version = "0.1.0", path = "crates/file" }
1818
gloo-dialogs = { version = "0.1.0", path = "crates/dialogs" }
19+
gloo-storage = { version = "0.1.0", path = "crates/storage" }
1920

2021
[features]
2122
default = []

crates/file/tests/web.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,5 @@ const PNG_FILE: &'static [u8] = &[
102102
];
103103

104104
#[cfg(feature = "futures")]
105-
const PNG_FILE_DATA: &'static str =
106-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAA\
105+
const PNG_FILE_DATA: &'static str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAA\
107106
Al21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=";

crates/storage/Cargo.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "gloo-storage"
3+
description = "Convenience crate for working with local and session storage in browser"
4+
version = "0.1.0"
5+
authors = ["Rust and WebAssembly Working Group"]
6+
edition = "2018"
7+
license = "MIT OR Apache-2.0"
8+
readme = "README.md"
9+
repository = "https://github.com/rustwasm/gloo/tree/master/crates/storage"
10+
homepage = "https://github.com/rustwasm/gloo"
11+
categories = ["api-bindings", "storage", "wasm"]
12+
13+
[dependencies]
14+
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
15+
serde = "1.0"
16+
serde_json = "1.0"
17+
thiserror = "1.0"
18+
js-sys = "0.3"
19+
20+
[dependencies.web-sys]
21+
version = "0.3"
22+
features = [
23+
"Storage",
24+
"Window",
25+
]
26+
27+
[dev-dependencies]
28+
wasm-bindgen-test = "0.3"
29+
serde = { version = "1.0", features = ["derive"] }

crates/storage/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<div align="center">
2+
3+
<h1><code>gloo-storage</code></h1>
4+
5+
<p>
6+
<a href="https://dev.azure.com/rustwasm/gloo/_build?definitionId=6"><img src="https://img.shields.io/azure-devops/build/rustwasm/gloo/6.svg?style=flat-square" alt="Build Status" /></a>
7+
<a href="https://crates.io/crates/gloo-storage"><img src="https://img.shields.io/crates/v/gloo-storage.svg?style=flat-square" alt="Crates.io version" /></a>
8+
<a href="https://crates.io/crates/gloo-storage"><img src="https://img.shields.io/crates/d/gloo-storage.svg?style=flat-square" alt="Download" /></a>
9+
<a href="https://docs.rs/gloo-storage"><img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square" alt="docs.rs docs" /></a>
10+
</p>
11+
12+
<h3>
13+
<a href="https://docs.rs/gloo-storage">API Docs</a>
14+
<span> | </span>
15+
<a href="https://github.com/rustwasm/gloo/blob/master/CONTRIBUTING.md">Contributing</a>
16+
<span> | </span>
17+
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
18+
</h3>
19+
20+
<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
21+
</div>
22+
23+
This crate provides wrappers for the
24+
[Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)
25+
26+
The data is stored in JSON form. We use [`serde`](https://serde.rs) for
27+
serialization and deserialization.

crates/storage/src/errors.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//! All the errors.
2+
3+
use std::fmt;
4+
5+
use wasm_bindgen::{JsCast, JsValue};
6+
7+
/// Error returned from JavaScript
8+
pub struct JsError {
9+
/// `name` field of JavaScript's error
10+
pub name: String,
11+
/// `message` field of JavaScript's error
12+
pub message: String,
13+
js_to_string: String,
14+
}
15+
16+
impl fmt::Debug for JsError {
17+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18+
f.debug_struct("JsError")
19+
.field("name", &self.name)
20+
.field("message", &self.message)
21+
.finish()
22+
}
23+
}
24+
25+
impl fmt::Display for JsError {
26+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27+
write!(f, "{}", self.js_to_string)
28+
}
29+
}
30+
31+
/// Error returned by this crate
32+
#[derive(Debug, thiserror::Error)]
33+
pub enum StorageError {
34+
/// Error from `serde`
35+
#[error("{0}")]
36+
SerdeError(#[from] serde_json::Error),
37+
/// Error if the requested key is not found
38+
#[error("key {0} not found")]
39+
KeyNotFound(String),
40+
/// Error returned from JavaScript
41+
#[error("{0}")]
42+
JsError(JsError),
43+
}
44+
45+
pub(crate) fn js_to_error(js_value: JsValue) -> StorageError {
46+
match js_value.dyn_into::<js_sys::Error>() {
47+
Ok(error) => StorageError::JsError(JsError {
48+
name: String::from(error.name()),
49+
message: String::from(error.message()),
50+
js_to_string: String::from(error.to_string()),
51+
}),
52+
Err(_) => unreachable!("JsValue passed is not an Error type - this is a bug"),
53+
}
54+
}

crates/storage/src/lib.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//! This crate provides wrappers for the
2+
//! [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)
3+
//!
4+
//! The data is stored in JSON form. We use [`serde`](https://serde.rs) for
5+
//! serialization and deserialization.
6+
7+
#![deny(missing_docs, missing_debug_implementations)]
8+
9+
use serde::{Deserialize, Serialize};
10+
use wasm_bindgen::prelude::*;
11+
12+
use crate::errors::js_to_error;
13+
use errors::StorageError;
14+
use serde_json::{Map, Value};
15+
16+
pub mod errors;
17+
mod local_storage;
18+
mod session_storage;
19+
pub use local_storage::LocalStorage;
20+
pub use session_storage::SessionStorage;
21+
22+
/// `gloo-storage`'s `Result`
23+
pub type Result<T> = std::result::Result<T, StorageError>;
24+
25+
/// Trait which provides implementations for managing storage in the browser.
26+
pub trait Storage {
27+
/// Get the raw [`web_sys::Storage`] instance
28+
fn raw() -> web_sys::Storage;
29+
30+
/// Get the value for the specified key
31+
fn get<T>(key: impl AsRef<str>) -> Result<T>
32+
where
33+
T: for<'de> Deserialize<'de>,
34+
{
35+
let key = key.as_ref();
36+
let item = Self::raw()
37+
.get_item(key)
38+
.expect_throw("unreachable: get_item does not throw an exception")
39+
.ok_or_else(|| StorageError::KeyNotFound(key.to_string()))?;
40+
let item = serde_json::from_str(&item)?;
41+
Ok(item)
42+
}
43+
44+
/// Get all the stored keys and their values
45+
fn get_all<T>() -> Result<T>
46+
where
47+
T: for<'a> Deserialize<'a>,
48+
{
49+
let local_storage = Self::raw();
50+
let length = Self::length();
51+
let mut map = Map::with_capacity(length as usize);
52+
for index in 0..length {
53+
let key = local_storage
54+
.key(index)
55+
.map_err(js_to_error)?
56+
.unwrap_throw();
57+
let value: Value = Self::get(&key)?;
58+
map.insert(key, value);
59+
}
60+
Ok(serde_json::from_value(Value::Object(map))?)
61+
}
62+
63+
/// Insert a value for the specified key
64+
fn set<T>(key: impl AsRef<str>, value: T) -> Result<()>
65+
where
66+
T: Serialize,
67+
{
68+
let key = key.as_ref();
69+
let value = serde_json::to_string(&value)?;
70+
Self::raw()
71+
.set_item(key, &value)
72+
.map_err(errors::js_to_error)?;
73+
Ok(())
74+
}
75+
76+
/// Remove a key and it's stored value
77+
fn delete(key: impl AsRef<str>) {
78+
let key = key.as_ref();
79+
Self::raw()
80+
.remove_item(key)
81+
.expect_throw("unreachable: remove_item does not throw an exception");
82+
}
83+
84+
/// Remove all the stored data
85+
fn clear() {
86+
Self::raw()
87+
.clear()
88+
.expect_throw("unreachable: clear does not throw an exception");
89+
}
90+
91+
/// Get the number of items stored
92+
fn length() -> u32 {
93+
Self::raw()
94+
.length()
95+
.expect_throw("unreachable: length does not throw an exception")
96+
}
97+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use wasm_bindgen::UnwrapThrowExt;
2+
3+
use crate::Storage;
4+
5+
/// Provides API to deal with `localStorage`
6+
#[derive(Debug)]
7+
pub struct LocalStorage;
8+
9+
impl Storage for LocalStorage {
10+
fn raw() -> web_sys::Storage {
11+
web_sys::window()
12+
.expect_throw("no window")
13+
.local_storage()
14+
.expect_throw("failed to get local_storage")
15+
.expect_throw("no local storage")
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use wasm_bindgen::UnwrapThrowExt;
2+
3+
use crate::Storage;
4+
5+
/// Provides API to deal with `sessionStorage`
6+
#[derive(Debug)]
7+
pub struct SessionStorage;
8+
9+
impl Storage for SessionStorage {
10+
fn raw() -> web_sys::Storage {
11+
web_sys::window()
12+
.expect_throw("no window")
13+
.session_storage()
14+
.expect_throw("failed to get session_storage")
15+
.expect_throw("no session storage")
16+
}
17+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use gloo_storage::{LocalStorage, Storage};
2+
use serde::Deserialize;
3+
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
4+
5+
wasm_bindgen_test_configure!(run_in_browser);
6+
7+
#[test]
8+
fn get() {
9+
let key = "key";
10+
let value = "value";
11+
LocalStorage::set(key, value).unwrap();
12+
13+
let obtained_value: String = LocalStorage::get(key).unwrap();
14+
15+
assert_eq!(value, obtained_value)
16+
}
17+
18+
#[derive(Deserialize)]
19+
struct Data {
20+
key1: String,
21+
key2: String,
22+
}
23+
24+
#[test]
25+
fn get_all() {
26+
LocalStorage::set("key1", "value").unwrap();
27+
LocalStorage::set("key2", "value").unwrap();
28+
29+
let data: Data = LocalStorage::get_all().unwrap();
30+
assert_eq!(data.key1, "value");
31+
assert_eq!(data.key2, "value");
32+
}
33+
34+
#[test]
35+
fn set_and_length() {
36+
LocalStorage::clear();
37+
assert_eq!(LocalStorage::length(), 0);
38+
LocalStorage::set("key", "value").unwrap();
39+
assert_eq!(LocalStorage::length(), 1);
40+
LocalStorage::clear();
41+
assert_eq!(LocalStorage::length(), 0);
42+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use gloo_storage::{SessionStorage, Storage};
2+
use serde::Deserialize;
3+
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
4+
5+
wasm_bindgen_test_configure!(run_in_browser);
6+
7+
#[test]
8+
fn get() {
9+
let key = "key";
10+
let value = "value";
11+
SessionStorage::set(key, value).unwrap();
12+
13+
let obtained_value: String = SessionStorage::get(key).unwrap();
14+
15+
assert_eq!(value, obtained_value)
16+
}
17+
18+
#[derive(Deserialize)]
19+
struct Data {
20+
key1: String,
21+
key2: String,
22+
}
23+
24+
#[test]
25+
fn get_all() {
26+
SessionStorage::set("key1", "value").unwrap();
27+
SessionStorage::set("key2", "value").unwrap();
28+
29+
let data: Data = SessionStorage::get_all().unwrap();
30+
assert_eq!(data.key1, "value");
31+
assert_eq!(data.key2, "value");
32+
}
33+
34+
#[test]
35+
fn set_and_length() {
36+
SessionStorage::clear();
37+
assert_eq!(SessionStorage::length(), 0);
38+
SessionStorage::set("key", "value").unwrap();
39+
assert_eq!(SessionStorage::length(), 1);
40+
SessionStorage::clear();
41+
assert_eq!(SessionStorage::length(), 0);
42+
}

0 commit comments

Comments
 (0)