Skip to content

Add wrappers for web storage API #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ gloo-console-timer = { version = "0.1.0", path = "crates/console-timer" }
gloo-events = { version = "0.1.0", path = "crates/events" }
gloo-file = { version = "0.1.0", path = "crates/file" }
gloo-dialogs = { version = "0.1.0", path = "crates/dialogs" }
gloo-storage = { version = "0.1.0", path = "crates/storage" }

[features]
default = []
Expand Down
3 changes: 1 addition & 2 deletions crates/file/tests/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,5 @@ const PNG_FILE: &'static [u8] = &[
];

#[cfg(feature = "futures")]
const PNG_FILE_DATA: &'static str =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAA\
const PNG_FILE_DATA: &'static str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAA\
Al21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=";
29 changes: 29 additions & 0 deletions crates/storage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "gloo-storage"
description = "Convenience crate for working with local and session storage in browser"
version = "0.1.0"
authors = ["Rust and WebAssembly Working Group"]
edition = "2018"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/rustwasm/gloo/tree/master/crates/storage"
homepage = "https://github.com/rustwasm/gloo"
categories = ["api-bindings", "storage", "wasm"]

[dependencies]
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
serde = "1.0"
serde_json = "1.0"
thiserror = "1.0"
js-sys = "0.3"

[dependencies.web-sys]
version = "0.3"
features = [
"Storage",
"Window",
]

[dev-dependencies]
wasm-bindgen-test = "0.3"
serde = { version = "1.0", features = ["derive"] }
27 changes: 27 additions & 0 deletions crates/storage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<div align="center">

<h1><code>gloo-storage</code></h1>

<p>
<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>
<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>
<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>
<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>
</p>

<h3>
<a href="https://docs.rs/gloo-storage">API Docs</a>
<span> | </span>
<a href="https://github.com/rustwasm/gloo/blob/master/CONTRIBUTING.md">Contributing</a>
<span> | </span>
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
</h3>

<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
</div>

This crate provides wrappers for the
[Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)

The data is stored in JSON form. We use [`serde`](https://serde.rs) for
serialization and deserialization.
54 changes: 54 additions & 0 deletions crates/storage/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! All the errors.

use std::fmt;

use wasm_bindgen::{JsCast, JsValue};

/// Error returned from JavaScript
pub struct JsError {
/// `name` field of JavaScript's error
pub name: String,
/// `message` field of JavaScript's error
pub message: String,
js_to_string: String,
}

impl fmt::Debug for JsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JsError")
.field("name", &self.name)
.field("message", &self.message)
.finish()
}
}

impl fmt::Display for JsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.js_to_string)
}
}

/// Error returned by this crate
#[derive(Debug, thiserror::Error)]
pub enum StorageError {
/// Error from `serde`
#[error("{0}")]
SerdeError(#[from] serde_json::Error),
/// Error if the requested key is not found
#[error("key {0} not found")]
KeyNotFound(String),
/// Error returned from JavaScript
#[error("{0}")]
JsError(JsError),
}

pub(crate) fn js_to_error(js_value: JsValue) -> StorageError {
match js_value.dyn_into::<js_sys::Error>() {
Ok(error) => StorageError::JsError(JsError {
name: String::from(error.name()),
message: String::from(error.message()),
js_to_string: String::from(error.to_string()),
}),
Err(_) => unreachable!("JsValue passed is not an Error type - this is a bug"),
}
}
97 changes: 97 additions & 0 deletions crates/storage/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//! This crate provides wrappers for the
//! [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)
//!
//! The data is stored in JSON form. We use [`serde`](https://serde.rs) for
//! serialization and deserialization.

#![deny(missing_docs, missing_debug_implementations)]

use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;

use crate::errors::js_to_error;
use errors::StorageError;
use serde_json::{Map, Value};

pub mod errors;
mod local_storage;
mod session_storage;
pub use local_storage::LocalStorage;
pub use session_storage::SessionStorage;

/// `gloo-storage`'s `Result`
pub type Result<T> = std::result::Result<T, StorageError>;

/// Trait which provides implementations for managing storage in the browser.
pub trait Storage {
/// Get the raw [`web_sys::Storage`] instance
fn raw() -> web_sys::Storage;

/// Get the value for the specified key
fn get<T>(key: impl AsRef<str>) -> Result<T>
where
T: for<'de> Deserialize<'de>,
{
let key = key.as_ref();
let item = Self::raw()
.get_item(key)
.expect_throw("unreachable: get_item does not throw an exception")
.ok_or_else(|| StorageError::KeyNotFound(key.to_string()))?;
let item = serde_json::from_str(&item)?;
Ok(item)
}

/// Get all the stored keys and their values
fn get_all<T>() -> Result<T>
where
T: for<'a> Deserialize<'a>,
{
let local_storage = Self::raw();
let length = Self::length();
let mut map = Map::with_capacity(length as usize);
for index in 0..length {
let key = local_storage
.key(index)
.map_err(js_to_error)?
.unwrap_throw();
let value: Value = Self::get(&key)?;
map.insert(key, value);
}
Ok(serde_json::from_value(Value::Object(map))?)
}

/// Insert a value for the specified key
fn set<T>(key: impl AsRef<str>, value: T) -> Result<()>
where
T: Serialize,
{
let key = key.as_ref();
let value = serde_json::to_string(&value)?;
Self::raw()
.set_item(key, &value)
.map_err(errors::js_to_error)?;
Ok(())
}

/// Remove a key and it's stored value
fn delete(key: impl AsRef<str>) {
let key = key.as_ref();
Self::raw()
.remove_item(key)
.expect_throw("unreachable: remove_item does not throw an exception");
}

/// Remove all the stored data
fn clear() {
Self::raw()
.clear()
.expect_throw("unreachable: clear does not throw an exception");
}

/// Get the number of items stored
fn length() -> u32 {
Self::raw()
.length()
.expect_throw("unreachable: length does not throw an exception")
}
}
17 changes: 17 additions & 0 deletions crates/storage/src/local_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use wasm_bindgen::UnwrapThrowExt;

use crate::Storage;

/// Provides API to deal with `localStorage`
#[derive(Debug)]
pub struct LocalStorage;

impl Storage for LocalStorage {
fn raw() -> web_sys::Storage {
web_sys::window()
.expect_throw("no window")
.local_storage()
.expect_throw("failed to get local_storage")
.expect_throw("no local storage")
}
}
17 changes: 17 additions & 0 deletions crates/storage/src/session_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use wasm_bindgen::UnwrapThrowExt;

use crate::Storage;

/// Provides API to deal with `sessionStorage`
#[derive(Debug)]
pub struct SessionStorage;

impl Storage for SessionStorage {
fn raw() -> web_sys::Storage {
web_sys::window()
.expect_throw("no window")
.session_storage()
.expect_throw("failed to get session_storage")
.expect_throw("no session storage")
}
}
42 changes: 42 additions & 0 deletions crates/storage/tests/local_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use gloo_storage::{LocalStorage, Storage};
use serde::Deserialize;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};

wasm_bindgen_test_configure!(run_in_browser);

#[test]
fn get() {
let key = "key";
let value = "value";
LocalStorage::set(key, value).unwrap();

let obtained_value: String = LocalStorage::get(key).unwrap();

assert_eq!(value, obtained_value)
}

#[derive(Deserialize)]
struct Data {
key1: String,
key2: String,
}

#[test]
fn get_all() {
LocalStorage::set("key1", "value").unwrap();
LocalStorage::set("key2", "value").unwrap();

let data: Data = LocalStorage::get_all().unwrap();
assert_eq!(data.key1, "value");
assert_eq!(data.key2, "value");
}

#[test]
fn set_and_length() {
LocalStorage::clear();
assert_eq!(LocalStorage::length(), 0);
LocalStorage::set("key", "value").unwrap();
assert_eq!(LocalStorage::length(), 1);
LocalStorage::clear();
assert_eq!(LocalStorage::length(), 0);
}
42 changes: 42 additions & 0 deletions crates/storage/tests/session_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use gloo_storage::{SessionStorage, Storage};
use serde::Deserialize;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};

wasm_bindgen_test_configure!(run_in_browser);

#[test]
fn get() {
let key = "key";
let value = "value";
SessionStorage::set(key, value).unwrap();

let obtained_value: String = SessionStorage::get(key).unwrap();

assert_eq!(value, obtained_value)
}

#[derive(Deserialize)]
struct Data {
key1: String,
key2: String,
}

#[test]
fn get_all() {
SessionStorage::set("key1", "value").unwrap();
SessionStorage::set("key2", "value").unwrap();

let data: Data = SessionStorage::get_all().unwrap();
assert_eq!(data.key1, "value");
assert_eq!(data.key2, "value");
}

#[test]
fn set_and_length() {
SessionStorage::clear();
assert_eq!(SessionStorage::length(), 0);
SessionStorage::set("key", "value").unwrap();
assert_eq!(SessionStorage::length(), 1);
SessionStorage::clear();
assert_eq!(SessionStorage::length(), 0);
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
pub use gloo_console_timer as console_timer;
pub use gloo_events as events;
pub use gloo_file as file;
pub use gloo_storage as storage;
pub use gloo_timers as timers;
pub use gloo_dialogs as dialogs;