Crate serde_flow
source ·Expand description
§Serde Flow - Migration Framework
The library assists you in smoothly deserializing the earlier versions of your serialized information.
§Key Features
- Versioning of serialize/deserialize entities
- Migration of serialized bytes
- Async migration
- Zerocopy deserialization
- Data Integrity Verification
§Modes of Operation
Serde-Flow has two ways of working: File and Bytes. The File mode is good for when you want to work with files directly on your computer, while the Bytes mode is better for when you’re working with data in your computer’s memory. You can use them both at the same time if you need to.
- File Mode
- Bytes Mode
- Both (use them together
#[flow(variant = 1, file, bytes)]
)
§File Mode
The File mode helps you work with files on your computer. It can read data from a certain place on your computer, save and load information in files automatically, and can also help update files to the newest version. To use this mode, add a special instruction called #[flow(file)]
above your code. This tells Serde-Flow to treat that part of your code as working with files.
§Example Usage:
use serde::{Serialize, Deserialize};
use serde_flow::Flow;
use serde_flow::encoder::bincode;
use serde_flow::flow::{File, FileMigrate};
#[derive(Serialize, Deserialize, Flow)]
#[flow(variant = 1, file)]
struct MyStruct {
// Your struct fields here
field: String
}
let object = MyStruct { field: "Something".to_string() };
// save your object to path
object.save_to_path::<bincode::Encoder>(path).unwrap();
// load your object from the path
let object = MyStruct::load_from_path::<bincode::Encoder>(path).unwrap();
§2. Bytes Mode
The Bytes mode is for when you’re working with computer memory instead of files. It’s good for things like sending information between computers or saving data in a special way. To use this mode, add another special instruction called #[flow(bytes)] above your code. This tells Serde-Flow to treat that part of your code as working with computer memory.
§Example Usage:
use serde::{Serialize, Deserialize};
use serde_flow::Flow;
use serde_flow::encoder::bincode;
use serde_flow::flow::{Bytes};
#[derive(Serialize, Deserialize, Flow)]
#[flow(variant = 1, bytes)]
struct MyStruct {
// Your struct fields here
field: String
}
let object = MyStruct { field: "Something".to_string() };
// encode the object into bytes
let bytes = object.encode::<bincode::Encoder>().unwrap();
// decode the object from bytes
let object = MyStruct::decode::<bincode::Encoder>(&bytes).unwrap();
§Migrations
To use migrations, you need to tell the program about different ways your data can be saved (called “variants”). Migrations works well with text formats, like JSON. To do this, add a special instruction called [#[variants(StructA, StructB, ...)]
and list all the ways your data can be saved.
§Setup File Mode
for Serde serialization
Implements basic serde struct for serializing and deserializing User with version 1.
use serde::{Deserialize, Serialize};
use serde_flow::{Flow};
#[derive(Flow, Serialize, Deserialize)]
#[flow(variant = 1, file)]
struct User {
name: String
}
§Setup async File Mode
for Serde serialization
To read and write files using Serde asynchronously, add the nonblocking
option to the file
attribute in the flow instruction.
use serde::{Deserialize, Serialize};
use serde_flow::{Flow};
#[derive(Flow, Serialize, Deserialize)]
#[flow(variant = 1, file(nonblocking))]
struct User {
name: String
}
§Zerocopy
To deserialize files or bytes using SerdeFlow without copying data, use the “rkyv” library in your project. Add three special instructions called rkyv::Serialize
, rkyv::Deserialize
, and rkyv::Archive
to your code. This can also work asynchronously if needed.
use rkyv::{Archive, Deserialize, Serialize};
use serde_flow::{Flow};
#[derive(Flow, Archive, Serialize, Deserialize)]
#[flow(variant = 1, file, zerocopy)]
#[archive(check_bytes)]
struct User {
name: String
}
§Verify Write
To make sure your files are saved correctly, you can use verification. Serde-Flow will save the information in a file and then check if the data is the same when it’s loaded back. To use this, add the special instruction called verify_write
to your code.
use serde::{Deserialize, Serialize};
use serde_flow::{Flow};
#[derive(Flow, Serialize, Deserialize)]
#[flow(variant = 1, file(verify_write))]
struct User {
name: String
}
§Usage
You have to include some imports to use migrations.
§Blocking
use serde::{Deserialize, Serialize};
use serde_flow::{encoder::bincode, flow::File, flow::FileMigrate, Flow};
use serde_flow::flow::FlowResult;
#[derive(Flow, Serialize, Deserialize)]
#[flow(variant = 2, file(verify_write))]
#[variants(UserV1)]
struct User {
name: String
}
#[derive(Flow, Serialize, Deserialize)]
#[flow(variant = 1, file(verify_write))]
struct UserV1 {
value: u16
}
impl From<UserV1> for User {
fn from(object: UserV1) -> User {
User { name: object.value.to_string() }
}
}
// create an old user
let user = UserV1 { value: 123 };
user.save_to_path::<bincode::Encoder>(path)?;
// loading without updating stored User
let user = User::load_from_path::<bincode::Encoder>(path)?;
// loading with updating stored User
let user = User::load_and_migrate::<bincode::Encoder>(path)?;
// only migrating stored User
User::migrate::<bincode::Encoder>(path)?;
§Nonblocking
use serde::{Deserialize, Serialize};
use serde_flow::{encoder::bincode, flow::FileAsync, flow::FileMigrateAsync, Flow};
use serde_flow::flow::FlowResult;
// create an old user
let user = UserV1 { value: 123 };
user.save_to_path_async::<bincode::Encoder>(path).await?;
let user = User::load_from_path_async::<bincode::Encoder>(path).await?;
let user = User::load_and_migrate_async::<bincode::Encoder>(path).await?;
User::migrate_async::<bincode::Encoder>(path).await?;
§Zerocopy
This function makes a Reader<T>
that can read information from files. Also, if you’re using zero-copy, the load_from_path
method updates the saved file automatically when it reads the information. The save_to_path
method is the save.
use serde_flow::{flow::zerocopy::{File, FileMigrate}, Flow};
use rkyv::{Archive, Serialize, Deserialize};
#[derive(Flow, Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
#[flow(variant = 1, file, zerocopy)]
struct User {
name: String
}
// Reader<User>
let user = User::load_from_path(path).unwrap();
§Reader
With the Reader<T>
trait, you can do two things: map exact bytes of the loaded file into immutable object (called “archive”) or decode and copy information from a loaded file (called “deserialize”). The archive
method uses zero-copy, while deserialize
doesn’t use it.
use serde_flow::{flow::zerocopy::File, Flow};
use rkyv::{Archive, Serialize, Deserialize};
#[derive(Flow, Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
#[flow(variant = 1, file, zerocopy)]
struct User {
name: String
}
let user_reader = User::load_from_path(path)?;
let user_archived = user_reader.archive()?;
assert_eq!(user_archived.name, "Jan Janssen".to_string());
§Zerocopy Non-blocking
use serde_flow::{flow::zerocopy::FileAsync, Flow};
use rkyv::{Archive, Serialize, Deserialize};
#[derive(Flow, Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
#[flow(variant = 1, file(nonblocking), zerocopy)]
struct User {
name: String
}
let user_reader = User::load_from_path_async(path).await?;