DioxusLabs / DioxusLabs/sdk

Better Control over stored data format

Open
#82 3 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Rust
Stars
302
Forks
58
PR merge metrics
No merged PRs in 30d

Description

# The Problem / Limitation

The existing storage APIs, like `use_synced_storage` do not appear to to **let the user control the encoding**.

I care a lot about ensuring my actual persisted data format is human readable and human editable so I have been using pretty printed JSON as my data format.

I also like to **fully cleanup the local storage entry** (via [removeItem](https://developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem)) when not using it.

As far as I can tell neither of these is possible with `use_synced_storage` as it appears to unconditionally encode using postcard, compressed with yazi then run through some custom logic to ensure its a valid string, and there is no way to express and empty/cleared state.

# Proposed Solutions

I think separating the storage location from the encoding would be a good way to do this.

Something like this could work:

```rust
/// A trait for a storage backing.
///
/// Unchanged.
pub trait StorageBacking: Clone + 'static {
/// The key type used to store data in storage
type Key: PartialEq + Clone + Debug + Send + Sync + 'static;
/// Gets a value from storage for the given key.
///
/// Question: Does None here indicate the storage was empty, or an error?
fn get(key: &Self::Key) -> Option;
/// Sets a value in storage for the given key
fn set(key: Self::Key, value: &T);
}

/// A trait for the persistence portion of StorageBacking.
pub trait StoragePersistence: Clone + 'static {
/// The key type used to store data in storage
type Key: PartialEq + Clone + Debug + Send + Sync + 'static;
/// The type of value which can be stored.
type Value;
/// Gets a value from storage for the given key
fn store(key: &Self::Key) -> Self::Value;
/// Sets a value in storage for the given key
fn load(key: Self::Key, value: &Self::Value);
}

/// LocalStorage stores Option.
impl StoragePersistence for LocalStorage {
type Key = String;
type Value = Option;

fn store(key: &Self::Key) -> Self::Value {
// Use existing logic from storage code here.
// Use the second half of https://github.com/DioxusLabs/sdk/blob/adf59211ea3caaaa404503660693f2cd9bb44968/packages/storage/src/client_storage/web.rs#L111
todo!()
}

fn load(key: Self::Key, value: &Self::Value) {
// Use existing logic from storage code here.
// Use the second half of https://github.com/DioxusLabs/sdk/blob/adf59211ea3caaaa404503660693f2cd9bb44968/packages/storage/src/client_storage/web.rs#L119
todo!()
}
}

/// New trait which can be implemented to define a data format for storage.
pub trait StorageEncoder: Clone + 'static {
/// The type of value which can be stored.
type Value;
fn deserialize(loaded: &Self::Value) -> T;
fn serialize(value: &T) -> Self::Value;
}

/// A way to create a StorageEncoder out of the two layers.
///
/// I'm not sure if this is the best way to abstract that.
#[derive(Clone)]
pub struct LayeredStorage {
persistence: PhantomData,
encoder: PhantomData,
}

/// StorageBacking for LayeredStorage.
impl>, E: StorageEncoder>
StorageBacking for LayeredStorage
{
type Key = P::Key;

fn get(key: &Self::Key) -> Option {
let loaded = P::store(key);
match loaded {
Some(t) => E::deserialize(&t),
None => None,
}
}

fn set(key: Self::Key, value: &T) {
P::load(key, &Some(E::serialize(value)));
}
}

/// Since StorageEncoder does not provide a way to clear, implement some options
impl>, E: StorageEncoder>
LayeredStorage
{
pub fn clear(key: P::Key) {
P::load(key, &None);
}

pub fn set_or_clear(
key: P::Key,
value: &Option,
) {
match value {
Some(t) => Self::set(key, t),
None => Self::clear(key),
}
}
}

#[derive(Clone)]
struct DefaultEncoder;

impl StorageEncoder for DefaultEncoder {
type Value = String;

fn deserialize(loaded: &Self::Value) -> T {
// Use existing logic from storage code here.
// Use the first half of https://github.com/DioxusLabs/sdk/blob/adf59211ea3caaaa404503660693f2cd9bb44968/packages/storage/src/client_storage/web.rs#L119
todo!()
}

fn serialize(value: &T) -> Self::Value {
// Use existing logic from storage code here.
// Use the first half of https://github.com/DioxusLabs/sdk/blob/adf59211ea3caaaa404503660693f2cd9bb44968/packages/storage/src/client_storage/web.rs#L111
todo!()
}
}

/// StorageBacking using default encoder: handles LocalStorage and other built in storage implementations.
impl>> StorageBacking for P {
type Key = P::Key;

fn get(key: &Self::Key) -> Option {
LayeredStorage::::get(key)
}

fn set(key: Self::Key, value: &T) {
LayeredStorage::::set(key, value)
}
}

type HumanReadableStorage =
LayeredStorage;

#[derive(Clone)]
struct HumanReadableEncoding;

impl StorageEncoder for HumanReadableEncoding {
type Value = String;

fn deserialize(loaded: &Self::Value) -> T {
let parsed: Result = serde_json::from_str(loaded);
// This design probably needs an error handling policy better than panic.
parsed.unwrap()
}

fn serialize(value: &T) -> Self::Value {
serde_json::to_string_pretty(value).unwrap()
}
}

fn example() {
let s =
use_synced_storage::, isize>("demo".to_string(), || 0);
}
```

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.