codee/
lib.rs

1//! Easy and flexible way of encoding and decoding data into either strings or bytes.
2//!
3//! This crate provides generic traits for [`Encoder`]s and [`Decoder`]s as well as several
4//! implementations for commonly used (de)serializer crates.
5//!
6//! This makes it easily possible to abstract away the serialization and deserialization independent
7//! of the concrete crate used. You can write a function like this:
8//!
9//! ```
10//! use codee::{CodecError, Decoder, Encoder};
11//!
12//! fn store_value<T, Codec>(value: T) -> Result<(), CodecError<<Codec as Encoder<T>>::Error, <Codec as Decoder<T>>::Error>>
13//! where
14//!     Codec: Encoder<T, Encoded = String> + Decoder<T, Encoded = str>,
15//! {
16//!     let encoded = Codec::encode(&value).map_err(CodecError::Encode)?;
17//!     let decoded = Codec::decode(&encoded).map_err(CodecError::Decode)?;
18//!
19//!     Ok(())
20//! }
21//!
22//! // Then we can use it like this:
23//!
24//! use codee::string::{JsonSerdeCodec, FromToStringCodec};
25//!
26//! #[derive(serde::Serialize, serde::Deserialize)]
27//! struct MyStruct {
28//!     field: usize,
29//! }
30//!
31//! store_value::<i32, FromToStringCodec>(42);
32//! store_value::<MyStruct, JsonSerdeCodec>(MyStruct { field: 42 });
33//! ```
34//!
35//! ## Available Codecs
36//!
37//! There are two types of codecs: One that encodes as binary data (`Vec[u8]`) in the module [`binary`] and another type that encodes as
38//! strings (`String`) in the module [`string`]. There is also an adapter
39//! [`Base64`](https://github.com/Synphonyte/leptos-use/blob/main/src/utils/codecs/string/base64.rs) that can be used to
40//! wrap a binary codec and make it a string codec by representing the binary data as a base64 string.
41//!
42//! > Please note that many of the codecs need a feature flag to be enabled. Check the docs of the respective codec to be sure.
43//!
44//! ### String Codecs
45//!
46//! Please have a look at the module [`string`](crate::string).
47//!
48//! #### Adapters
49//!
50//! - [`string::Base64`] —
51//!   Wraps a binary codec and makes it a string codec by representing the binary data as a base64 string.
52//! - [`string::OptionCodec`] —
53//!   Wraps a string codec that encodes `T` to create a codec that encodes `Option<T>`.
54//!
55//! ### Binary Codecs
56//!
57//! Please have a look at the module [`binary`](crate::binary).
58//!
59//! ## Custom Codecs
60//!
61//! If you don't find a suitable codec for your needs, you can implement your own; it's straightforward!
62//! If you want to create a string codec, you can look at [`string::JsonSerdeCodec`] as a starting point.
63//! In case it's a binary codec, have a look at [`binary::BincodeSerdeCodec`].
64//!
65//! ## Versioning
66//!
67//! Versioning is the process of handling long-term data that can outlive our code.
68//!
69//! For example, we could have a settings struct whose members change over time. We might eventually
70//! add timezone support, and we might then remove support for a thousand separator for numbers.
71//! Each change results in a new possible version of the stored data. If we stored these settings
72//! in browser storage, we would need to handle all possible versions of the data format that can
73//! occur. If we don't offer versioning, then all settings could revert to the default every time we
74//! encounter an old format.
75//!
76//! How best to handle versioning depends on the codec involved:
77//!
78//! - The `FromToStringCodec` can avoid versioning entirely by keeping
79//!   to primitive types. In our example above, we could have decomposed the settings struct into
80//!   separate timezone and number separator fields. These would be encoded as strings and stored as
81//!   two separate key-value fields in the browser rather than a single field. If a field is missing,
82//!   then the value intentionally would fall back to the default without interfering with the other
83//!   field.
84//!
85//! - The `ProstCodec` uses [Protocol buffers](https://protobuf.dev/overview/)
86//!   designed to solve the problem of long-term storage. It provides semantics for versioning that
87//!   are not present in JSON or other formats.
88//!
89//! - The codecs that use serde under the hood can rely on serde or by
90//!   providing their own manual version handling. See the next sections for more details.
91//!
92//! ### Rely on `serde`
93//!
94//! A simple way to avoid complex versioning is to rely on serde's [field attributes](https://serde.rs/field-attrs.html)
95//! such as [`serde(default)`](https://serde.rs/field-attrs.html#default)
96//! and [`serde(rename = "...")`](https://serde.rs/field-attrs.html#rename).
97//!
98//! ### Manual Version Handling
99//!
100//! We look at the example of the `JsonSerdeCodec` in this section.
101//!
102//! To implement version handling, we parse the JSON generically then transform the
103//! resulting `serde_json::Value` before decoding it into our struct again.
104//!
105//! Let's look at an example.
106//!
107//! ```
108//! use serde::{Deserialize, Serialize};
109//! use serde_json::json;
110//! use codee::{Encoder, Decoder};
111//!
112//! #[derive(Serialize, Deserialize, Clone, Default, PartialEq)]
113//! pub struct MyState {
114//!     pub hello: String,
115//!     // This field was added in a later version
116//!     pub greeting: String,
117//! }
118//!
119//! pub struct MyStateCodec;
120//!
121//! impl Encoder<MyState> for MyStateCodec {
122//!     type Error = serde_json::Error;
123//!     type Encoded = String;
124//!
125//!     fn encode(val: &MyState) -> Result<Self::Encoded, Self::Error> {
126//!         serde_json::to_string(val)
127//!     }
128//! }
129//!
130//! impl Decoder<MyState> for MyStateCodec {
131//!     type Error = serde_json::Error;
132//!     type Encoded = str;
133//!
134//!     fn decode(stored_value: &Self::Encoded) -> Result<MyState, Self::Error> {
135//!         let mut val: serde_json::Value = serde_json::from_str(stored_value)?;
136//!         // add "greeting": "Hello" to the object if it's missing
137//!         if let Some(obj) = val.as_object_mut() {
138//!             if !obj.contains_key("greeting") {
139//!                obj.insert("greeting".to_string(), json!("Hello"));
140//!             }
141//!             serde_json::from_value(val)
142//!         } else {
143//!             Ok(MyState::default())
144//!         }
145//!     }
146//! }
147//!
148//! // Then use it just like any other codec.
149//! ```
150//!
151//! ## Hybrid Codecs
152//!
153//! In case you want to write code that can be used with both, binary and string codecs, there are the
154//! [`HybridDecoder`], [`HybridEncoder`] and [`IsBinary`] traits that are implemented automatically
155//! for all the codecs.
156//!
157//! To see them in action, you can have a look at [`leptos_use::use_websocket`](https://github.com/Synphonyte/leptos-use/blob/main/src/use_websocket.rs).
158
159pub mod binary;
160mod error;
161mod hybrid;
162#[cfg(feature = "serde_lite")]
163mod serde_lite;
164pub mod string;
165mod traits;
166
167pub use error::*;
168pub use hybrid::*;
169#[cfg(feature = "serde_lite")]
170pub use serde_lite::*;
171pub use traits::*;