mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-06-22 04:11:32 +10:00
Merge remote-tracking branch 'nativemodel/main' into develop
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
name: Build Test Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, next ]
|
||||
pull_request:
|
||||
branches: [ main, next ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_test_common_os:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
toolchain: [stable]
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
||||
- name: Setup Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
override: true
|
||||
- uses: extractions/setup-just@v3
|
||||
- uses: hustcer/setup-nu@v3.23
|
||||
with:
|
||||
version: '0.105.1'
|
||||
- name: Just version
|
||||
run: just --version
|
||||
- name: Build
|
||||
run: just build_all
|
||||
- name: Test
|
||||
run: just test_all
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_common_os]
|
||||
if: github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: install npm
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Semantic Release
|
||||
uses: cycjimmy/semantic-release-action@v4
|
||||
with:
|
||||
dry_run: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
extra_plugins: |
|
||||
@semantic-release/commit-analyzer
|
||||
@semantic-release/release-notes-generator
|
||||
@semantic-release/exec
|
||||
@semantic-release/github
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }}
|
||||
CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }}
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Clippy Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main, next ]
|
||||
schedule:
|
||||
- cron: '0 23 * * 4'
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
|
||||
jobs:
|
||||
clippy_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- uses: extractions/setup-just@v3
|
||||
- uses: hustcer/setup-nu@v3.23
|
||||
with:
|
||||
version: '0.105.1'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }}
|
||||
- name: Just version
|
||||
run: just --version
|
||||
- name: Clippy Check
|
||||
run: just clippy_check
|
||||
@@ -0,0 +1,13 @@
|
||||
name: Conventional Commits
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Conventional Commits
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
||||
- uses: webiny/action-conventional-commits@v1.3.1
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Fmt Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main, next ]
|
||||
schedule:
|
||||
- cron: '0 23 * * 4'
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
|
||||
jobs:
|
||||
fmt_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- uses: extractions/setup-just@v3
|
||||
- uses: hustcer/setup-nu@v3.23
|
||||
with:
|
||||
version: '0.105.1'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }}
|
||||
- name: Just version
|
||||
run: just --version
|
||||
- name: Fmt Check
|
||||
run: just fmt_check
|
||||
@@ -0,0 +1,10 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
|
||||
# TODO: remove it used by semantic-release/exec
|
||||
node_modules/
|
||||
package-lock.json
|
||||
package.json
|
||||
|
||||
/native_model_macro/target
|
||||
/native_model_macro/Cargo.lock
|
||||
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"branches": [
|
||||
"main"
|
||||
],
|
||||
"tagFormat": "${version}",
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
"releaseRules": [
|
||||
{
|
||||
"breaking": true,
|
||||
"release": "minor"
|
||||
},
|
||||
{
|
||||
"revert": true,
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"type": "feat",
|
||||
"release": "minor"
|
||||
},
|
||||
{
|
||||
"type": "fix",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"type": "perf",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"type": "docs",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"emoji": ":racehorse:",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"emoji": ":bug:",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"emoji": ":penguin:",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"emoji": ":apple:",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"emoji": ":checkered_flag:",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"tag": "BUGFIX",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"tag": "FEATURE",
|
||||
"release": "minor"
|
||||
},
|
||||
{
|
||||
"tag": "SECURITY",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"tag": "Breaking",
|
||||
"release": "minor"
|
||||
},
|
||||
{
|
||||
"tag": "Fix",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"tag": "Update",
|
||||
"release": "minor"
|
||||
},
|
||||
{
|
||||
"tag": "New",
|
||||
"release": "minor"
|
||||
},
|
||||
{
|
||||
"component": "perf",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"component": "deps",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"type": "FEAT",
|
||||
"release": "minor"
|
||||
},
|
||||
{
|
||||
"type": "FIX",
|
||||
"release": "patch"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/exec",
|
||||
{
|
||||
"prepareCmd": "bash version_update.sh ${nextRelease.version}",
|
||||
"publishCmd": "bash cargo_publish.sh"
|
||||
}
|
||||
],
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "native_model"
|
||||
version = "0.6.4"
|
||||
authors = ["Vincent Herlemont <vincent@herlemont.fr>", "quexeky <git@quexeky.dev>"]
|
||||
edition = "2021"
|
||||
description = "A thin wrapper around serialized data which add information of identity and version."
|
||||
license = "MIT"
|
||||
repository = "https://github.com/Drop-OSS/native_model"
|
||||
readme = "README.md"
|
||||
keywords = ["serialization", "interoperability", "data-consistency", "flexibility", "performance"]
|
||||
categories = ["data-structures", "encoding", "rust-patterns"]
|
||||
rust-version = "1.73.0"
|
||||
|
||||
[workspace]
|
||||
members = ["native_model_macro"]
|
||||
|
||||
[dependencies]
|
||||
zerocopy = { version = "0.8.0", features = [ "derive"] }
|
||||
thiserror = "2.0.0"
|
||||
anyhow = "1.0.82"
|
||||
native_model_macro = { version = "0.6.2", path = "native_model_macro" }
|
||||
|
||||
serde = { version = "1.0.200", features = ["derive"], optional = true }
|
||||
bincode_1_3 = { package = "bincode", version = "1.3.3", optional = true }
|
||||
bincode_2 = { package = "bincode", version = "2.0", features = ["serde"], optional = true }
|
||||
postcard_1_0 = { package = "postcard", version = "1.0.8", features = ["alloc"], optional = true }
|
||||
rmp_serde_1_3 = { package = "rmp-serde", version = "1.3", optional = true }
|
||||
doc-comment = "0.3.3"
|
||||
log = "0.4.27"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.116"
|
||||
criterion = { version = "0.8.0" }
|
||||
|
||||
[features]
|
||||
default = ["serde", "bincode_1_3"]
|
||||
|
||||
[[bench]]
|
||||
name = "overhead"
|
||||
harness = false
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Vincent Herlemont
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,327 @@
|
||||
# Native model
|
||||
|
||||
[](https://crates.io/crates/native_model)
|
||||
[](https://github.com/vincent-herlemont/native_model/actions/workflows/build_and_test_release.yml)
|
||||
[](https://docs.rs/native_model)
|
||||
[](LICENSE)
|
||||
|
||||
Add interoperability on the top of serialization formats like bincode, postcard etc.
|
||||
|
||||
See [concepts](#concepts) for more details.
|
||||
|
||||
## Goals
|
||||
|
||||
- **Interoperability**: Allows different applications to work together, even if they are using different
|
||||
versions of the data model.
|
||||
- **Data Consistency**: Ensure that we process the data expected model.
|
||||
- **Flexibility**: You can use any serialization format you want. More details [here](#setup-your-serialization-format).
|
||||
- **Performance**: A minimal overhead (encode: ~20 ns, decode: ~40 ps). More details [here](#performance).
|
||||
|
||||
## Usage
|
||||
|
||||
```text
|
||||
Application 1 (DotV1) Application 2 (DotV1 and DotV2)
|
||||
| |
|
||||
Encode DotV1 |--------------------------------> | Decode DotV1 to DotV2
|
||||
| | Modify DotV2
|
||||
Decode DotV1 | <--------------------------------| Encode DotV2 back to DotV1
|
||||
| |
|
||||
```
|
||||
|
||||
|
||||
```rust
|
||||
use native_model::native_model;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct DotV1(u32, u32);
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 2, from = DotV1)]
|
||||
struct DotV2 {
|
||||
name: String,
|
||||
x: u64,
|
||||
y: u64,
|
||||
}
|
||||
|
||||
impl From<DotV1> for DotV2 {
|
||||
fn from(dot: DotV1) -> Self {
|
||||
DotV2 {
|
||||
name: "".to_string(),
|
||||
x: dot.0 as u64,
|
||||
y: dot.1 as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DotV2> for DotV1 {
|
||||
fn from(dot: DotV2) -> Self {
|
||||
DotV1(dot.x as u32, dot.y as u32)
|
||||
}
|
||||
}
|
||||
|
||||
// Application 1
|
||||
let dot = DotV1(1, 2);
|
||||
let bytes = native_model::encode(&dot).unwrap();
|
||||
|
||||
// Application 1 sends bytes to Application 2.
|
||||
|
||||
// Application 2
|
||||
// We are able to decode the bytes directly into a new type DotV2 (upgrade).
|
||||
let (mut dot, source_version) = native_model::decode::<DotV2>(bytes).unwrap();
|
||||
assert_eq!(dot, DotV2 {
|
||||
name: "".to_string(),
|
||||
x: 1,
|
||||
y: 2
|
||||
});
|
||||
dot.name = "Dot".to_string();
|
||||
dot.x = 5;
|
||||
// For interoperability, we encode the data with the version compatible with Application 1 (downgrade).
|
||||
let bytes = native_model::encode_downgrade(dot, source_version).unwrap();
|
||||
|
||||
// Application 2 sends bytes to Application 1.
|
||||
|
||||
// Application 1
|
||||
let (dot, _) = native_model::decode::<DotV1>(bytes).unwrap();
|
||||
assert_eq!(dot, DotV1(5, 2));
|
||||
```
|
||||
|
||||
- Full example [here](./tests_crate/tests/example/example_main.rs).
|
||||
|
||||
## Serialization format
|
||||
|
||||
You can use default serialization formats via the feature flags, like:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
native_model = { version = "0.1", features = ["bincode_2"] }
|
||||
```
|
||||
|
||||
Each feature flag corresponds to a specific minor version of the serialization format. In order to avoid breaking
|
||||
changes, the default serialization format is the oldest one.
|
||||
|
||||
- `bincode_1_3`: [bincode](https://docs.rs/bincode/1.3.3/bincode/) v1.3 (default)
|
||||
- `bincode_2`: [bincode](https://docs.rs/bincode/2.0.0-rc.3/bincode/) v2.0.0-rc3
|
||||
- `postcard_1_0`: [postcard](https://docs.rs/postcard/1.0.0/postcard/) v1.0
|
||||
- `rpm_serde_1_3`: [rmp-serde](https://docs.rs/rmp-serde/1.3.0/rmp_serde/) v1.3
|
||||
|
||||
### Custom serialization format
|
||||
|
||||
Define a struct with the name you want. This struct must implement [`native_model::Encode`](https://docs.rs/native_model/latest/native_model/trait.Encode.html) and [`native_model::Decode`](https://docs.rs/native_model/latest/native_model/trait.Decode.html) traits.
|
||||
|
||||
Full examples:
|
||||
- [bincode with encode/decode](./tests_crate/tests/example/custom_codec/bincode.rs)
|
||||
- [bincode with serde](./tests_crate/tests/example/custom_codec/bincode_serde.rs)
|
||||
|
||||
Others examples, see the default implementations:
|
||||
- [bincode v1.3](./src/codec/bincode_1_3.rs)
|
||||
- [bincode v2.0 (rc)](./src/codec/bincode_2.rs)
|
||||
- [postcard v1.0](./src/codec/postcard_1_0.rs)
|
||||
- [rmp-serde v1.3](./src/codec/rmp_serde_1_3.rs)
|
||||
|
||||
### Notice
|
||||
`native_model` provides implementations that rely on metadata-less formats and `serde`.
|
||||
There are known issues with some `serde` advanced features such as:
|
||||
|
||||
- `#[serde(flatten)]`
|
||||
- `#[serde(skip)]`
|
||||
- `#[serde(skip_deserializing)]`
|
||||
- `#[serde(skip_serializing)]`
|
||||
- `#[serde(skip_serializing_if = "path")]`
|
||||
- `#[serde(tag = "...")]`
|
||||
- `#[serde(untagged)]`
|
||||
|
||||
Or types implementing similar strategies such as [`serde_json::Value`][serde_json_value].
|
||||
|
||||
The `rmp-serde` serialization format can optionally support them serializing structs as maps, the `RmpSerdeNamed` struct is provided to support this use-case.
|
||||
|
||||
[serde_json_value]: https://docs.rs/serde_json/latest/serde_json/enum.Value.html
|
||||
|
||||
## Data model
|
||||
|
||||
Define your model using the macro [`native_model`](file:///home/vincentherlemont/IdeaProjects/native_model/target/doc/native_model/attr.native_model.html).
|
||||
|
||||
Attributes:
|
||||
- `id = u32`: The unique identifier of the model.
|
||||
- `version = u32`: The version of the model.
|
||||
- `with = type`: The serialization format that you use for the Encode/Decode implementation. Setup [here](#setup-your-serialization-format).
|
||||
- `from = type`: Optional, the previous version of the model.
|
||||
- `type`: The previous version of the model that you use for the From implementation.
|
||||
- `try_from = (type, error)`: Optional, the previous version of the model with error handling.
|
||||
- `type`: The previous version of the model that you use for the TryFrom implementation.
|
||||
- `error`: The error type that you use for the TryFrom implementation.
|
||||
|
||||
```rust
|
||||
use native_model::native_model;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct DotV1(u32, u32);
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 2, from = DotV1)]
|
||||
struct DotV2 {
|
||||
name: String,
|
||||
x: u64,
|
||||
y: u64,
|
||||
}
|
||||
|
||||
// Implement the conversion between versions From<DotV1> for DotV2 and From<DotV2> for DotV1.
|
||||
|
||||
impl From<DotV1> for DotV2 {
|
||||
fn from(dot: DotV1) -> Self {
|
||||
DotV2 {
|
||||
name: "".to_string(),
|
||||
x: dot.0 as u64,
|
||||
y: dot.1 as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DotV2> for DotV1 {
|
||||
fn from(dot: DotV2) -> Self {
|
||||
DotV1(dot.x as u32, dot.y as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 3, try_from = (DotV2, anyhow::Error))]
|
||||
struct DotV3 {
|
||||
name: String,
|
||||
cord: Cord,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
||||
struct Cord {
|
||||
x: u64,
|
||||
y: u64,
|
||||
}
|
||||
|
||||
// Implement the conversion between versions From<DotV2> for DotV3 and From<DotV3> for DotV2.
|
||||
|
||||
impl TryFrom<DotV2> for DotV3 {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(dot: DotV2) -> Result<Self, Self::Error> {
|
||||
Ok(DotV3 {
|
||||
name: dot.name,
|
||||
cord: Cord { x: dot.x, y: dot.y },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DotV3> for DotV2 {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(dot: DotV3) -> Result<Self, Self::Error> {
|
||||
Ok(DotV2 {
|
||||
name: dot.name,
|
||||
x: dot.cord.x,
|
||||
y: dot.cord.y,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Codecs
|
||||
|
||||
`native_model` comes with several optional built-in serializer features available:
|
||||
|
||||
- [bincode 1.3](https://crates.io/crates/bincode/1.3.3)
|
||||
- This is the default codec.
|
||||
- **Warning: This codec may not work with all serde-derived types.**
|
||||
|
||||
- [bincode 2.0.0-rc.3](https://crates.io/crates/bincode/2.0.0-rc.3)
|
||||
- Enable the `bincode_2` feature and use the `native_model::bincode_2::Bincode` attribute to have `native_db` use this crate for serializing & deserializing.
|
||||
- **Warning: This codec may not work with all serde-derived types.**
|
||||
|
||||
- [postcard 1.0](https://crates.io/crates/postcard/1.0.8)
|
||||
- Enable the `postcard_1_0` feature and use the `native_model::postcard_1_0::PostCard` attribute.
|
||||
- **Warning: This codec may not work with all serde-derived types.**
|
||||
|
||||
- [rmp-serde 1.3](https://crates.io/crates/rmp-serde/1.3.0)
|
||||
- Enable the `rmp_serde_1_3` feature and use the `native_model::rmp_serde_1_3::RmpSerde` attribute.
|
||||
|
||||
###### Codec example:
|
||||
|
||||
As example, to use `rmp-serde`:
|
||||
|
||||
1. In your project's `Cargo.toml` file, enable the `rmp_serde_1_3` feature for the `native_model` dependency.
|
||||
- Be sure to check `crates.io` for the most recent [`native_model`](https://crates.io/crates/native_model) version number.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
native_model = { version = "0.4", features = [ "rmp_serde_1_3" ] }
|
||||
```
|
||||
|
||||
2. Assign the `rmp_serde_1_3` codec to your `struct` using the `with` attribute:
|
||||
|
||||
```rust
|
||||
use native_model::native_model;
|
||||
|
||||
#[derive(Clone, Default, serde::Deserialize, serde::Serialize)]
|
||||
#[native_model(id = 1, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
struct MyStruct {
|
||||
my_string: String,
|
||||
// etc.
|
||||
}
|
||||
```
|
||||
|
||||
###### Additional reading
|
||||
|
||||
You may also want to check out [David Koloski](https://github.com/djkoloski)'s [Rust serialization benchmarks](https://github.com/djkoloski/rust_serialization_benchmark) for help selecting the codec (i.e. `bincode_1_3`, `rmp_serde_1_3`, etc.) that's best for your project.
|
||||
|
||||
## Status
|
||||
|
||||
Early development. Not ready for production.
|
||||
|
||||
## Concepts
|
||||
|
||||
In order to understand how the native model works, you need to understand the following concepts.
|
||||
|
||||
- **Identity**(`id`): The identity is the unique identifier of the model. It is used to identify the model and
|
||||
prevent to decode a model into the wrong Rust type.
|
||||
- **Version**(`version`) The version is the version of the model. It is used to check the compatibility between two
|
||||
models.
|
||||
- **Encode**: The encode is the process of converting a model into a byte array.
|
||||
- **Decode**: The decode is the process of converting a byte array into a model.
|
||||
- **Downgrade**: The downgrade is the process of converting a model into a previous version of the model.
|
||||
- **Upgrade**: The upgrade is the process of converting a model into a newer version of the model.
|
||||
|
||||
Under the hood, the native model is a thin wrapper around serialized data. The `id` and the `version` are twice encoded with a [`little_endian::U32`](https://docs.rs/zerocopy/latest/zerocopy/byteorder/little_endian/type.U32.html). That represents 8 bytes, that are added at the beginning of the data.
|
||||
|
||||
``` text
|
||||
+------------------+------------------+------------------------------------+
|
||||
| ID (4 bytes) | Version (4 bytes)| Data (indeterminate-length bytes) |
|
||||
+------------------+------------------+------------------------------------+
|
||||
```
|
||||
|
||||
Full example [here](tests/example/example_define_model.rs).
|
||||
|
||||
## Performance
|
||||
|
||||
Native model has
|
||||
been designed to have a minimal and constant overhead. That means that the overhead is the same
|
||||
whatever the size of the data. Under the hood we use the [zerocopy](https://docs.rs/zerocopy/latest/zerocopy/) crate
|
||||
to avoid unnecessary copies.
|
||||
|
||||
👉 To know the total time of the encode/decode, you need to add the time of your serialization format.
|
||||
|
||||
Resume:
|
||||
- **Encode**: ~20 ns
|
||||
- **Decode**: ~40 ps
|
||||
|
||||
| data size | encode time (ns) | decode time (ps) |
|
||||
|:--------------------:|:---------------------:|:-----------------------:|
|
||||
| 1 B | 19.769 ns - 20.154 ns | 40.526 ps - 40.617 ps |
|
||||
| 1 KiB | 19.597 ns - 19.971 ns | 40.534 ps - 40.633 ps |
|
||||
| 1 MiB | 19.662 ns - 19.910 ns | 40.508 ps - 40.632 ps |
|
||||
| 10 MiB | 19.591 ns - 19.980 ns | 40.504 ps - 40.605 ps |
|
||||
| 100 MiB | 19.669 ns - 19.867 ns | 40.520 ps - 40.644 ps |
|
||||
|
||||
Benchmark of the native model overhead [here](benches/overhead.rs).
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use native_model::Model;
|
||||
use native_model_macro::native_model;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Data(Vec<u8>);
|
||||
|
||||
fn wrap(data: &mut Vec<u8>) {
|
||||
native_model::wrapper::native_model_encode(data, 1, 1);
|
||||
}
|
||||
|
||||
fn unwrap(data: &mut Vec<u8>) {
|
||||
native_model::wrapper::Wrapper::deserialize(&data[..]).unwrap();
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("encode");
|
||||
|
||||
// 1 byte, 1KB, 1MB, 10MB, 100MB
|
||||
for nb_bytes in [1, 1024, 1024 * 1024, 10 * 1024 * 1024, 100 * 1024 * 1024].into_iter() {
|
||||
group.throughput(criterion::Throughput::Bytes(nb_bytes as u64));
|
||||
|
||||
// encode
|
||||
let data = Data(vec![1; nb_bytes]);
|
||||
let mut encode_body = data.native_model_encode_body().unwrap();
|
||||
group.bench_function(BenchmarkId::new("encode", nb_bytes), |b| {
|
||||
b.iter(|| wrap(&mut encode_body))
|
||||
});
|
||||
|
||||
// decode
|
||||
let data = Data(vec![1; nb_bytes]);
|
||||
let mut encode_body = native_model::encode(&data).unwrap();
|
||||
group.bench_function(BenchmarkId::new("decode", nb_bytes), |b| {
|
||||
b.iter(|| unwrap(&mut encode_body))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
ARG_TOKEN="--token=$CARGO_TOKEN"
|
||||
|
||||
cd $DIR/native_model_macro
|
||||
cargo publish $ARG_TOKEN $@
|
||||
|
||||
cd $DIR
|
||||
cargo publish $ARG_TOKEN $@
|
||||
@@ -0,0 +1,79 @@
|
||||
set shell := ["nu", "-c"]
|
||||
|
||||
default:
|
||||
@just --list --unsorted;
|
||||
|
||||
build_no_default:
|
||||
cargo build --no-default-features
|
||||
|
||||
build_default:
|
||||
cargo build
|
||||
|
||||
build_serde:
|
||||
cargo build --no-default-features --features serde
|
||||
|
||||
build_bincode_1_3:
|
||||
cargo build --features bincode_1_3
|
||||
|
||||
build_no_default_bincode_1_3:
|
||||
cargo build --no-default-features --features serde --features bincode_1_3
|
||||
|
||||
build_bincode_2:
|
||||
cargo build --features bincode_2
|
||||
|
||||
build_no_default_bincode_2:
|
||||
cargo build --no-default-features --features serde --features bincode_2
|
||||
|
||||
build_postcard_1_0:
|
||||
cargo build --features postcard_1_0
|
||||
|
||||
build_no_default_postcard_1_0:
|
||||
cargo build --no-default-features --features serde --features postcard_1_0
|
||||
|
||||
build_all: build_no_default build_default build_serde build_bincode_1_3 build_no_default_bincode_1_3 build_bincode_2 build_no_default_bincode_2 build_postcard_1_0 build_no_default_postcard_1_0
|
||||
|
||||
_tests_crate args='':
|
||||
cd tests_crate; \
|
||||
cargo test {{args}}
|
||||
|
||||
test_no_default:
|
||||
@just _tests_crate '--no-default-features'
|
||||
|
||||
test_default:
|
||||
@just _tests_crate args=''
|
||||
|
||||
test_bincode_1_3:
|
||||
@just _tests_crate '--features bincode_1_3'
|
||||
|
||||
test_bincode_2:
|
||||
@just _tests_crate '--features bincode_2'
|
||||
|
||||
test_postcard_1_0:
|
||||
@just _tests_crate '--features postcard_1_0'
|
||||
|
||||
test_docs:
|
||||
cargo test --doc --all-features
|
||||
|
||||
test_all: test_docs test_no_default test_default test_bincode_1_3 test_bincode_2 test_postcard_1_0
|
||||
|
||||
bench_overhead:
|
||||
cargo bench --bench overhead
|
||||
|
||||
bench_all: bench_overhead
|
||||
|
||||
format:
|
||||
cargo clippy; \
|
||||
cargo fmt --all
|
||||
|
||||
fmt_check:
|
||||
cargo fmt --all -- --check
|
||||
|
||||
clippy_check:
|
||||
rustc --version; \
|
||||
cargo clippy --version; \
|
||||
cargo clippy -- -D warnings
|
||||
|
||||
# Format check
|
||||
fc:
|
||||
just fmt_check; \
|
||||
just clippy_check
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "native_model_macro"
|
||||
version = "0.6.4"
|
||||
authors = ["Vincent Herlemont <vincent@herlemont.fr>", "quexeky <git@quexeky.dev>"]
|
||||
edition = "2018"
|
||||
description = "A procedural macro for native_model"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/Drop-OSS/native_model"
|
||||
readme = "README.md"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2.0.60", features = ["full"] }
|
||||
quote = "1.0.36"
|
||||
proc-macro2 = "1.0.81"
|
||||
log = "0.4.27"
|
||||
@@ -0,0 +1 @@
|
||||
A procedural macro for [native_model](https://github.com/Drop-OSS/native_model).
|
||||
@@ -0,0 +1,127 @@
|
||||
extern crate proc_macro;
|
||||
extern crate log;
|
||||
|
||||
mod method;
|
||||
|
||||
use crate::method::{
|
||||
generate_native_model_decode_body, generate_native_model_decode_upgrade_body,
|
||||
generate_native_model_encode_body,
|
||||
generate_native_model_id, generate_native_model_version,
|
||||
};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::meta::ParseNestedMeta;
|
||||
use syn::parse::{Parse, Result};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::token;
|
||||
use syn::{parse_macro_input, DeriveInput, LitInt, Path, Token};
|
||||
|
||||
// Inspiration: https://docs.rs/syn/2.0.29/syn/meta/fn.parser.html#example-1
|
||||
pub(crate) struct ModelAttributes {
|
||||
pub(crate) id: Option<LitInt>,
|
||||
pub(crate) version: Option<LitInt>,
|
||||
// type
|
||||
pub(crate) with: Option<Path>,
|
||||
// type
|
||||
pub(crate) from: Option<Path>,
|
||||
// (type, try_from::Error type)
|
||||
pub(crate) try_from: Option<(Path, Path)>,
|
||||
}
|
||||
|
||||
impl Default for ModelAttributes {
|
||||
fn default() -> Self {
|
||||
ModelAttributes {
|
||||
id: None,
|
||||
version: None,
|
||||
with: Some(syn::parse_str::<Path>("native_model::bincode_1_3::Bincode").unwrap()),
|
||||
from: None,
|
||||
try_from: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelAttributes {
|
||||
fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
|
||||
if meta.path.is_ident("id") {
|
||||
self.id = Some(meta.value()?.parse()?);
|
||||
} else if meta.path.is_ident("version") {
|
||||
self.version = Some(meta.value()?.parse()?);
|
||||
} else if meta.path.is_ident("with") {
|
||||
self.with = Some(meta.value()?.parse()?);
|
||||
} else if meta.path.is_ident("from") {
|
||||
self.from = Some(meta.value()?.parse()?);
|
||||
} else if meta.path.is_ident("try_from") {
|
||||
let tuple_try_from: TupleTryFrom = meta.value()?.parse()?;
|
||||
let mut fields = tuple_try_from.fields.into_iter();
|
||||
self.try_from.replace((
|
||||
fields.next().unwrap().clone(),
|
||||
fields.next().unwrap().clone(),
|
||||
));
|
||||
} else {
|
||||
panic!("Unknown attribute: {}", meta.path.get_ident().unwrap());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TupleTryFrom {
|
||||
pub(crate) _parent_token: token::Paren,
|
||||
pub(crate) fields: Punctuated<Path, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for TupleTryFrom {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
Ok(TupleTryFrom {
|
||||
_parent_token: syn::parenthesized!(content in input),
|
||||
fields: content.parse_terminated(Path::parse, Token![,])?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro which add identity and version to your rust type.
|
||||
///
|
||||
/// Attributes:
|
||||
/// - `id = u32`: The unique identifier of the model.
|
||||
/// - `version = u32`: The version of the model.
|
||||
/// - `with` = type: Required, the serialization/deserialization library that you use. Must implement `native_model::Encode` and `native_model::Decode`.
|
||||
/// - `from = type`: Optional, the previous version of the model.
|
||||
/// - `type`: The previous version of the model that you use for the From implementation.
|
||||
/// - `try_from = (type, error)`: Optional, the previous version of the model with error handling.
|
||||
/// - `type`: The previous version of the model that you use for the TryFrom implementation.
|
||||
/// - `error`: The error type that you use for the TryFrom implementation.
|
||||
///
|
||||
/// See examples:
|
||||
/// - [Setup your data model](https://github.com/vincent-herlemont/native_model_private#setup-your-data-model).
|
||||
/// - other [examples](https://github.com/Drop-OSS/native_model/tree/master/tests/example)
|
||||
#[proc_macro_attribute]
|
||||
pub fn native_model(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let struct_name = &ast.ident;
|
||||
|
||||
let mut attrs = ModelAttributes::default();
|
||||
let model_attributes_parser = syn::meta::parser(|meta| attrs.parse(meta));
|
||||
parse_macro_input!(args with model_attributes_parser);
|
||||
|
||||
let native_model_id_fn = generate_native_model_id(&attrs);
|
||||
let native_model_version_fn = generate_native_model_version(&attrs);
|
||||
let native_model_encode_body_fn = generate_native_model_encode_body(&attrs);
|
||||
let native_model_decode_body_fn = generate_native_model_decode_body(&attrs);
|
||||
let native_model_decode_upgrade_body_fn = generate_native_model_decode_upgrade_body(&attrs, struct_name);
|
||||
|
||||
|
||||
let gen = quote! {
|
||||
#ast
|
||||
|
||||
impl native_model::Model for #struct_name {
|
||||
#native_model_id_fn
|
||||
#native_model_version_fn
|
||||
#native_model_encode_body_fn
|
||||
#native_model_decode_body_fn
|
||||
#native_model_decode_upgrade_body_fn
|
||||
}
|
||||
};
|
||||
|
||||
gen.into()
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use crate::ModelAttributes;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn generate_native_model_decode_body(attrs: &ModelAttributes) -> TokenStream {
|
||||
let id = attrs.id.clone().expect("`id` is required");
|
||||
let with = attrs.with.clone().expect("`with` is required");
|
||||
let gen = quote! {
|
||||
fn native_model_decode_body(data: Vec<u8>, id: u32) -> std::result::Result<Self, native_model::DecodeBodyError> {
|
||||
if id != #id {
|
||||
return Err(native_model::DecodeBodyError::MismatchedModelId);
|
||||
}
|
||||
|
||||
use native_model::Decode;
|
||||
#with::decode(data).map_err(|e| native_model::DecodeBodyError::DecodeError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
gen
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use crate::ModelAttributes;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
|
||||
pub(crate) fn generate_native_model_decode_upgrade_body(attrs: &ModelAttributes, struct_name: &Ident) -> TokenStream {
|
||||
let native_model_from = attrs.from.clone();
|
||||
let native_model_try_from = attrs.try_from.clone();
|
||||
|
||||
let name = struct_name.to_string();
|
||||
|
||||
let model_from_or_try_from = if let Some(from) = native_model_from {
|
||||
quote! {
|
||||
::log::info!("Upgrading database {} from version {} to version {}", #name, #from::native_model_version(), Self::native_model_version());
|
||||
|
||||
#from::native_model_decode_upgrade_body(data, id, version).map(|a| a.into())
|
||||
}
|
||||
} else if let Some((try_from, error_try_from)) = native_model_try_from {
|
||||
quote! {
|
||||
::log::info!("Attempting to upgrade database {} from version {} to version {}", #name, #try_from::native_model_version(), Self::native_model_version());
|
||||
let result = #try_from::native_model_decode_upgrade_body(data, id, version).map(|b| {
|
||||
b.try_into()
|
||||
.map_err(|e: #error_try_from| native_model::UpgradeError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
})??;
|
||||
Ok(result)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
Err(native_model::Error::UpgradeNotSupported {
|
||||
from: version,
|
||||
to: Self::native_model_version(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let gen = quote! {
|
||||
fn native_model_decode_upgrade_body(data: Vec<u8>, id: u32, version: u32) -> native_model::Result<Self> {
|
||||
if version == Self::native_model_version() {
|
||||
let result = Self::native_model_decode_body(data, id)?;
|
||||
Ok(result)
|
||||
} else if version < Self::native_model_version() {
|
||||
#model_from_or_try_from
|
||||
} else {
|
||||
Err(native_model::Error::UpgradeNotSupported {
|
||||
from: version,
|
||||
to: Self::native_model_version(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
gen
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use crate::ModelAttributes;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn generate_native_model_encode_body(attrs: &ModelAttributes) -> TokenStream {
|
||||
let with = attrs.with.clone().expect("`with` is required");
|
||||
let gen = quote! {
|
||||
fn native_model_encode_body(&self) -> std::result::Result<Vec<u8>, native_model::EncodeBodyError> {
|
||||
use native_model::Encode;
|
||||
#with::encode(self).map_err(|e| native_model::EncodeBodyError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
gen
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::ModelAttributes;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn generate_native_model_id(model_attributes: &ModelAttributes) -> TokenStream {
|
||||
let native_model_id = model_attributes.id.clone().unwrap();
|
||||
let gen = quote! {
|
||||
fn native_model_id() -> u32 {
|
||||
#native_model_id
|
||||
}
|
||||
|
||||
fn native_model_id_str() -> &'static str {
|
||||
stringify!(#native_model_id)
|
||||
}
|
||||
};
|
||||
gen
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
mod decode_body;
|
||||
mod decode_upgrade_body;
|
||||
mod encode_body;
|
||||
mod id;
|
||||
mod version;
|
||||
|
||||
pub(crate) use decode_body::*;
|
||||
pub(crate) use decode_upgrade_body::*;
|
||||
pub(crate) use encode_body::*;
|
||||
pub(crate) use id::*;
|
||||
pub(crate) use version::*;
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::ModelAttributes;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn generate_native_model_version(model_attributes: &ModelAttributes) -> TokenStream {
|
||||
let native_model_version = model_attributes.version.clone().unwrap();
|
||||
let gen = quote! {
|
||||
fn native_model_version() -> u32 {
|
||||
#native_model_version
|
||||
}
|
||||
|
||||
fn native_model_version_str() -> &'static str {
|
||||
stringify!(#native_model_version)
|
||||
}
|
||||
};
|
||||
gen
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"semanticCommits": "enabled",
|
||||
"semanticCommitType": "chore",
|
||||
"semanticCommitScope": "deps",
|
||||
"platformAutomerge": true,
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Automerge non-major updates",
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"description": "Automerge actions",
|
||||
"matchDepTypes": [
|
||||
"action"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"major",
|
||||
"minor",
|
||||
"patch"
|
||||
],
|
||||
"automerge": true
|
||||
}
|
||||
],
|
||||
"regexManagers": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"^\\.github/workflows/[^/]+\\.ya?ml$"
|
||||
],
|
||||
"matchStrings": [
|
||||
"uses: hustcer/setup-nu@.*?\\n.*?version: '\\s*(?<currentValue>.*?)'"
|
||||
],
|
||||
"depNameTemplate": "nushell",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"packageNameTemplate": "nushell/nushell"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//! [bincode 1.3](https://crates.io/crates/bincode/1.3.3) ·
|
||||
//! The default codec for serializing & deserializing.
|
||||
|
||||
/// Used to specify that the
|
||||
/// [bincode 1.3](https://crates.io/crates/bincode/1.3.3) crate is to be used
|
||||
/// for serialization & deserialization.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// `bincode` [does not implement](https://github.com/bincode-org/bincode/issues/548)
|
||||
/// all [serde](https://crates.io/crates/serde) features. Errors may be
|
||||
/// encountered when using this with some types.
|
||||
///
|
||||
/// If you are encountering errors when using this codec on your types, try
|
||||
/// using the `rmp_serde_1_3` codec instead.
|
||||
///
|
||||
/// # Basic usage
|
||||
///
|
||||
/// Use the [`with`](crate::native_model) attribute on your type to instruct
|
||||
/// `native_model` to use `Bincode` for serialization & deserialization.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use native_model::*;
|
||||
/// #[derive(Clone, Default, serde::Deserialize, serde::Serialize)]
|
||||
/// #[native_model(id = 1, version = 1, with = native_model::bincode_1_3::Bincode)]
|
||||
/// struct MyStruct {
|
||||
/// my_string: String
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Bincode;
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "bincode_1_3"))]
|
||||
impl<T: serde::Serialize> super::Encode<T> for Bincode {
|
||||
type Error = bincode_1_3::Error;
|
||||
/// Serializes a type into bytes using the `bincode` `1.3` crate.
|
||||
fn encode(obj: &T) -> Result<Vec<u8>, Self::Error> {
|
||||
bincode_1_3::serialize(obj)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "bincode_1_3"))]
|
||||
impl<T: for<'de> serde::Deserialize<'de>> super::Decode<T> for Bincode {
|
||||
type Error = bincode_1_3::Error;
|
||||
/// Deserializes a type from bytes using the `bincode` `1.3` crate.
|
||||
fn decode(data: Vec<u8>) -> Result<T, Self::Error> {
|
||||
bincode_1_3::deserialize(&data[..])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//! [bincode 2.0](https://crates.io/crates/bincode/2.0.1) ·
|
||||
//! Enable the `bincode_2` feature and annotate your type with
|
||||
//! `native_model::bincode_2::Bincode` to have `native_db` use this crate for
|
||||
//! serializing & deserializing.
|
||||
|
||||
/// Used to specify the
|
||||
/// [bincode 2.0](https://crates.io/crates/bincode/2.0.1)
|
||||
/// crate for serialization & deserialization.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// `bincode` [does not implement](https://docs.rs/bincode/2.0.1/bincode/serde/index.html#known-issues)
|
||||
/// all [serde](https://crates.io/crates/serde) features. Errors may be
|
||||
/// encountered when using this with some types.
|
||||
///
|
||||
/// If you are encountering errors when using this codec on your types, try
|
||||
/// using the `rmp_serde_1_3` codec instead.
|
||||
///
|
||||
/// # Basic usage
|
||||
///
|
||||
/// After enabling the `bincode_2` feature in your `Cargo.toml`, use the
|
||||
/// [`with`](crate::native_model) attribute on your type to instruct
|
||||
/// `native_model` to use `Bincode` for serialization & deserialization.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use native_model::*;
|
||||
/// #[derive(Clone, Default, serde::Deserialize, serde::Serialize)]
|
||||
/// #[native_model(id = 1, version = 1, with = native_model::bincode_2::Bincode)]
|
||||
/// struct MyStruct {
|
||||
/// my_string: String
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub struct Bincode;
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "bincode_2"))]
|
||||
impl<T: serde::Serialize> super::Encode<T> for Bincode {
|
||||
type Error = bincode_2::error::EncodeError;
|
||||
/// Serializes a type into bytes using the `bincode` `2.0` crate.
|
||||
fn encode(obj: &T) -> Result<Vec<u8>, Self::Error> {
|
||||
bincode_2::serde::encode_to_vec(obj, bincode_2::config::standard())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "bincode_2"))]
|
||||
impl<T: for<'de> serde::Deserialize<'de>> super::Decode<T> for Bincode {
|
||||
type Error = bincode_2::error::DecodeError;
|
||||
/// Deserializes a type from bytes using the `bincode` `2.0` crate.
|
||||
fn decode(data: Vec<u8>) -> Result<T, Self::Error> {
|
||||
Ok(bincode_2::serde::decode_from_slice(&data, bincode_2::config::standard())?.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//! Traits and implementations for encoding types into a series of bytes and
|
||||
//! decoding bytes back into types.
|
||||
|
||||
#[cfg(any(all(feature = "serde", feature = "bincode_1_3"), doc))]
|
||||
pub mod bincode_1_3;
|
||||
#[cfg(any(all(feature = "serde", feature = "bincode_2"), doc))]
|
||||
pub mod bincode_2;
|
||||
#[cfg(any(all(feature = "serde", feature = "postcard_1_0"), doc))]
|
||||
pub mod postcard_1_0;
|
||||
#[cfg(any(all(feature = "serde", feature = "rmp_serde_1_3"), doc))]
|
||||
pub mod rmp_serde_1_3;
|
||||
|
||||
/// Encode trait for your own encoding method.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use bincode_2::{error::EncodeError,serde::encode_to_vec, config::standard};
|
||||
/// use serde::Serialize;
|
||||
/// pub struct Bincode;
|
||||
///
|
||||
/// impl<T: Serialize> native_model::Encode<T> for Bincode {
|
||||
/// type Error = EncodeError;
|
||||
/// fn encode(obj: &T) -> Result<Vec<u8>, EncodeError> {
|
||||
/// Ok(encode_to_vec(&obj, standard())?)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Encode<T> {
|
||||
type Error;
|
||||
/// Encodes a `T` type into a series of bytes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The errors returned from this function depend on the trait implementor
|
||||
/// (the serializer), i.e. `bincode_2`.
|
||||
fn encode(obj: &T) -> Result<Vec<u8>, Self::Error>;
|
||||
}
|
||||
|
||||
/// Decode trait for your own decoding method.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use bincode_2::{error::DecodeError,serde::decode_from_slice, config::standard};
|
||||
/// use serde::Deserialize;
|
||||
/// pub struct Bincode;
|
||||
///
|
||||
/// impl<T: for<'a> Deserialize<'a>> native_model::Decode<T> for Bincode {
|
||||
/// type Error = DecodeError;
|
||||
/// fn decode(data: Vec<u8>) -> Result<T, DecodeError> {
|
||||
/// Ok(decode_from_slice(&data, standard())?.0)
|
||||
/// }
|
||||
/// }
|
||||
pub trait Decode<T> {
|
||||
type Error;
|
||||
/// Decodes a series of bytes back into a `T` type.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The errors returned from this function depend on the trait implementor
|
||||
/// (the deserializer), i.e. `bincode_2`.
|
||||
fn decode(data: Vec<u8>) -> Result<T, Self::Error>;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//! [postcard 1.0](https://crates.io/crates/postcard/1.0.8) ·
|
||||
//! Enable the `postcard_1_0` feature and annotate your type with
|
||||
//! `native_model::postcard_1_0::PostCard` to have `native_db` use this crate.
|
||||
|
||||
/// Used to specify the [postcard 1.0](https://crates.io/crates/postcard/1.0.8)
|
||||
/// crate for serialization & deserialization.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// `postcard` does not implement all [serde](https://crates.io/crates/serde)
|
||||
/// features. Errors may be encountered when using this with some types.
|
||||
///
|
||||
/// If you are encountering errors when using this codec on your types, try
|
||||
/// using the `rmp_serde_1_3` codec instead.
|
||||
///
|
||||
/// # Basic usage
|
||||
///
|
||||
/// After enabling the `postcard_1_0` feature in your `Cargo.toml`, use the
|
||||
/// [`with`](crate::native_model) attribute on your type to instruct
|
||||
/// `native_model` to use `PostCard` for serialization & deserialization.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use native_model::*;
|
||||
/// #[derive(Clone, Default, serde::Deserialize, serde::Serialize)]
|
||||
/// #[native_model(id = 1, version = 1, with = native_model::postcard_1_0::PostCard)]
|
||||
/// struct MyStruct {
|
||||
/// my_string: String
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub struct PostCard;
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "postcard_1_0"))]
|
||||
impl<T: serde::Serialize> super::Encode<T> for PostCard {
|
||||
type Error = postcard_1_0::Error;
|
||||
/// Serializes a type into bytes using the `postcard` `1.0` crate.
|
||||
fn encode(obj: &T) -> Result<Vec<u8>, Self::Error> {
|
||||
postcard_1_0::to_allocvec(obj)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "postcard_1_0"))]
|
||||
impl<T: for<'de> serde::Deserialize<'de>> super::Decode<T> for PostCard {
|
||||
type Error = postcard_1_0::Error;
|
||||
/// Deserializes a type from bytes using the `postcard` `1.0` crate.
|
||||
fn decode(data: Vec<u8>) -> Result<T, Self::Error> {
|
||||
postcard_1_0::from_bytes(&data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//! [rmp-serde 1.3](https://crates.io/crates/rmp-serde/1.3.0) ·
|
||||
//! Enable the `rmp_serde_1_3` feature and
|
||||
//! [`annotate your type`](crate::native_model) with
|
||||
//! `native_model::rmp_serde_1_3::RmpSerde` or `native_model::rmp_serde_1_3::RmpSerdeNamed`
|
||||
//! to have `native_db` use this crate.
|
||||
|
||||
/// Used to specify the
|
||||
/// [rmp-serde 1.3](https://crates.io/crates/rmp-serde/1.3.0)
|
||||
/// crate for serialization & deserialization, using arrays to serialize structs.
|
||||
///
|
||||
/// Do not use this if you plan to use serde features that skip serializing fields,
|
||||
/// use [RmpSerdeNamed] instead.
|
||||
///
|
||||
/// # Basic usage
|
||||
///
|
||||
/// After enabling the `rmp_serde_1_3` feature in your `Cargo.toml`, use the
|
||||
/// [`with`](crate::native_model) attribute on your type to instruct
|
||||
/// `native_model` to use `RmpSerde` for serialization & deserialization.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use native_model::*;
|
||||
/// #[derive(Clone, Default, serde::Deserialize, serde::Serialize)]
|
||||
/// #[native_model(id = 1, version = 1, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||
/// struct MyStruct {
|
||||
/// my_string: String
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub struct RmpSerde;
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "rmp_serde_1_3"))]
|
||||
impl<T: serde::Serialize> crate::Encode<T> for RmpSerde {
|
||||
type Error = rmp_serde_1_3::encode::Error;
|
||||
/// Serializes a type into bytes using the `rmp-serde` `1.3` crate.
|
||||
fn encode(obj: &T) -> Result<Vec<u8>, Self::Error> {
|
||||
rmp_serde_1_3::encode::to_vec(obj)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "rmp_serde_1_3"))]
|
||||
impl<T: for<'de> serde::Deserialize<'de>> crate::Decode<T> for RmpSerde {
|
||||
type Error = rmp_serde_1_3::decode::Error;
|
||||
/// Deserializes a type from bytes using the `rmp-serde` `1.3` crate.
|
||||
fn decode(data: Vec<u8>) -> Result<T, Self::Error> {
|
||||
rmp_serde_1_3::decode::from_slice(&data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to specify the
|
||||
/// [rmp-serde 1.3](https://crates.io/crates/rmp-serde/1.3.0)
|
||||
/// crate for serialization & deserialization, using maps to serialize structs.
|
||||
///
|
||||
/// # Basic usage
|
||||
///
|
||||
/// After enabling the `rmp_serde_1_3` feature in your `Cargo.toml`, use the
|
||||
/// [`with`](crate::native_model) attribute on your type to instruct
|
||||
/// `native_model` to use `RmpSerdeNamed` for serialization & deserialization.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use native_model::*;
|
||||
/// #[derive(Clone, Default, serde::Deserialize, serde::Serialize)]
|
||||
/// #[native_model(id = 1, version = 1, with = native_model::rmp_serde_1_3::RmpSerdeNamed)]
|
||||
/// struct MyStruct {
|
||||
/// #[serde(skip_serializing_if = "String::is_empty")]
|
||||
/// my_string: String
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
pub struct RmpSerdeNamed;
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "rmp_serde_1_3"))]
|
||||
impl<T: serde::Serialize> crate::Encode<T> for RmpSerdeNamed {
|
||||
type Error = rmp_serde_1_3::encode::Error;
|
||||
/// Serializes a type into bytes using the `rmp-serde` `1.3` crate.
|
||||
fn encode(obj: &T) -> Result<Vec<u8>, Self::Error> {
|
||||
rmp_serde_1_3::encode::to_vec_named(obj)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "rmp_serde_1_3"))]
|
||||
impl<T: for<'de> serde::Deserialize<'de>> crate::Decode<T> for RmpSerdeNamed {
|
||||
type Error = rmp_serde_1_3::decode::Error;
|
||||
/// Deserializes a type from bytes using the `rmp-serde` `1.3` crate.
|
||||
fn decode(data: Vec<u8>) -> Result<T, Self::Error> {
|
||||
rmp_serde_1_3::decode::from_slice(&data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
use zerocopy::little_endian::U32;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Header {
|
||||
pub(crate) id: U32,
|
||||
pub(crate) version: U32,
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
//! `native_model` is a Rust crate that acts as a thin wrapper around serialized data, adding identity and version information.
|
||||
//!
|
||||
//! - It aims to ensure:
|
||||
//! - **Interoperability**: Different applications can work together even if they use different data model versions.
|
||||
//! - **Data Consistency**: Ensures the data is processed as expected.
|
||||
//! - **Flexibility**: Allows the use of any serialization format. Mode details [here](https://github.com/Drop-OSS/native_model#setup-your-serialization-format).
|
||||
//! - **Minimal Performance Overhead**: Current performance has a minimal overhead see [performance](https://github.com/Drop-OSS/native_model#performance) section.
|
||||
//! - **Suitability**:
|
||||
//! - Suitable for applications that are written in Rust, evolve independently, store data locally, and require incremental upgrades.
|
||||
//! - Not suitable for non-Rust applications, systems not controlled by the user, or when human-readable formats are needed.
|
||||
//! - **Setup**:
|
||||
//! - Users must define their own serialization format and data model. Mode details [here](https://github.com/Drop-OSS/native_model#setup-your-serialization-format).
|
||||
//! - **Development Stage**:
|
||||
//! - The crate is in early development, and performance is expected to improve over time.
|
||||
//!
|
||||
//! See examples in the [README.md](https://github.com/Drop-OSS/native_model) file.
|
||||
|
||||
#[cfg(doctest)]
|
||||
#[macro_use]
|
||||
extern crate doc_comment;
|
||||
|
||||
#[cfg(doctest)]
|
||||
doc_comment! {
|
||||
include_str!("../README.md")
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "serde",
|
||||
feature = "bincode_1_3",
|
||||
feature = "bincode_2",
|
||||
feature = "postcard_1_0",
|
||||
feature = "rmp_serde_1_3",
|
||||
doc
|
||||
))]
|
||||
mod codec;
|
||||
|
||||
#[cfg(any(
|
||||
feature = "serde",
|
||||
feature = "bincode_1_3",
|
||||
feature = "bincode_2",
|
||||
feature = "postcard_1_0",
|
||||
feature = "rmp_serde_1_3",
|
||||
doc
|
||||
))]
|
||||
pub use codec::*;
|
||||
mod header;
|
||||
pub mod wrapper;
|
||||
|
||||
// Macro to generate a [`native_model`] implementation for a struct.
|
||||
pub use native_model_macro::*;
|
||||
|
||||
use wrapper::*;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Invalid header")]
|
||||
InvalidHeader,
|
||||
#[error("Failed to decode native model")]
|
||||
DecodeError,
|
||||
#[error(transparent)]
|
||||
DecodeBodyError(#[from] DecodeBodyError),
|
||||
#[error(transparent)]
|
||||
EncodeBodyError(#[from] EncodeBodyError),
|
||||
#[error(transparent)]
|
||||
UpgradeError(#[from] UpgradeError),
|
||||
#[error("Upgrade from {} to {} is not supported", from, to)]
|
||||
UpgradeNotSupported { from: u32, to: u32 },
|
||||
#[error(transparent)]
|
||||
DowngradeError(#[from] DowngradeError),
|
||||
#[error("Downgrade from {} to {} is not supported", from, to)]
|
||||
DowngradeNotSupported { from: u32, to: u32 },
|
||||
#[error("Wrong type id expected: {}, actual: {}", expected, actual)]
|
||||
WrongTypeId { expected: u32, actual: u32 },
|
||||
}
|
||||
|
||||
pub type DecodeResult<T> = std::result::Result<T, DecodeBodyError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Decode body error: {msg}")]
|
||||
pub enum DecodeBodyError {
|
||||
#[error("Mismatched model id")]
|
||||
MismatchedModelId,
|
||||
#[error("Decode error: {msg}")]
|
||||
DecodeError {
|
||||
msg: String,
|
||||
#[source]
|
||||
source: anyhow::Error,
|
||||
},
|
||||
}
|
||||
|
||||
pub type EncodeResult<T> = std::result::Result<T, EncodeBodyError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Encode body error: {msg}")]
|
||||
pub struct EncodeBodyError {
|
||||
pub msg: String,
|
||||
#[source]
|
||||
pub source: anyhow::Error,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Upgrade error: {msg}")]
|
||||
pub struct UpgradeError {
|
||||
pub msg: String,
|
||||
#[source]
|
||||
pub source: anyhow::Error,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Downgrade error: {msg}")]
|
||||
pub struct DowngradeError {
|
||||
pub msg: String,
|
||||
#[source]
|
||||
pub source: anyhow::Error,
|
||||
}
|
||||
|
||||
/// Allows to encode a [`native_model`] into a [`Vec<u8>`].
|
||||
///
|
||||
/// See examples:
|
||||
/// - [README.md](https://github.com/Drop-OSS/native_model) file.
|
||||
/// - other [examples](https://github.com/Drop-OSS/native_model/tree/master/tests/example)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The errors returned from this function depend on the [`Encode`] trait
|
||||
/// implementor (the serializer), i.e. `bincode_2`.
|
||||
pub fn encode<T: crate::Model>(model: &T) -> Result<Vec<u8>> {
|
||||
T::native_model_encode(model)
|
||||
}
|
||||
|
||||
/// Allows to decode a [`native_model`] from a [`Vec<u8>`] and returns the version ([`u32`]).
|
||||
/// See examples:
|
||||
/// - [README.md](https://github.com/Drop-OSS/native_model) file.
|
||||
/// - other [examples](https://github.com/Drop-OSS/native_model/tree/master/tests/example)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The errors returned from this function depend on the [`Decode`] trait
|
||||
/// implementor (the deserializer), i.e. `bincode_2`.
|
||||
pub fn decode<T: crate::Model>(data: Vec<u8>) -> Result<(T, u32)> {
|
||||
T::native_model_decode(data)
|
||||
}
|
||||
|
||||
pub trait Model: Sized {
|
||||
fn native_model_id() -> u32;
|
||||
fn native_model_id_str() -> &'static str;
|
||||
fn native_model_version() -> u32;
|
||||
fn native_model_version_str() -> &'static str;
|
||||
|
||||
// --------------- Decode ---------------
|
||||
fn native_model_decode_body(data: Vec<u8>, id: u32) -> DecodeResult<Self>;
|
||||
|
||||
fn native_model_decode_upgrade_body(data: Vec<u8>, id: u32, version: u32) -> Result<Self>;
|
||||
|
||||
fn native_model_decode(data: impl AsRef<[u8]>) -> Result<(Self, u32)> {
|
||||
let native_model = crate::Wrapper::deserialize(data.as_ref()).unwrap();
|
||||
let source_id = native_model.get_id();
|
||||
let source_version = native_model.get_version();
|
||||
let result = Self::native_model_decode_upgrade_body(
|
||||
native_model.value().to_vec(),
|
||||
source_id,
|
||||
source_version,
|
||||
)?;
|
||||
Ok((result, source_version))
|
||||
}
|
||||
|
||||
// --------------- Encode ---------------
|
||||
|
||||
fn native_model_encode_body(&self) -> EncodeResult<Vec<u8>>;
|
||||
|
||||
fn native_model_encode(&self) -> Result<Vec<u8>> {
|
||||
let mut data = self.native_model_encode_body()?;
|
||||
let data = crate::native_model_encode(
|
||||
&mut data,
|
||||
Self::native_model_id(),
|
||||
Self::native_model_version(),
|
||||
);
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use crate::header::Header;
|
||||
use zerocopy::little_endian::U32;
|
||||
use zerocopy::{IntoBytes, Ref, SplitByteSlice, SplitByteSliceMut};
|
||||
|
||||
pub struct Wrapper<T: SplitByteSlice> {
|
||||
header: Ref<T, Header>,
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<T: SplitByteSlice> Wrapper<T> {
|
||||
pub fn deserialize(packed: T) -> Option<Self> {
|
||||
let (header_lv, rest) = Ref::<_, Header>::from_prefix(packed).ok()?;
|
||||
let native_model = Self {
|
||||
header: header_lv,
|
||||
value: rest,
|
||||
};
|
||||
Some(native_model)
|
||||
}
|
||||
|
||||
pub const fn value(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
|
||||
pub fn get_type_id(&self) -> u32 {
|
||||
self.header.id.get()
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> u32 {
|
||||
self.header.id.get()
|
||||
}
|
||||
|
||||
pub fn get_version(&self) -> u32 {
|
||||
self.header.version.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SplitByteSliceMut> Wrapper<T> {
|
||||
pub fn set_type_id(&mut self, type_id: u32) {
|
||||
self.header.id = U32::new(type_id);
|
||||
}
|
||||
|
||||
pub fn set_version(&mut self, version: u32) {
|
||||
self.header.version = U32::new(version);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn native_model_encode(data: &mut Vec<u8>, type_id: u32, version: u32) -> Vec<u8> {
|
||||
let header = Header {
|
||||
id: U32::new(type_id),
|
||||
version: U32::new(version),
|
||||
};
|
||||
let mut header = header.as_bytes().to_vec();
|
||||
header.append(data);
|
||||
header
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{native_model_encode, Wrapper};
|
||||
|
||||
#[test]
|
||||
fn native_model_deserialize_with_body() {
|
||||
let mut data = vec![0u8; 8];
|
||||
let data = native_model_encode(&mut data, 200000, 100000);
|
||||
assert_eq!(data.len(), 16);
|
||||
let model = Wrapper::deserialize(&data[..]).unwrap();
|
||||
assert_eq!(model.get_type_id(), 200000);
|
||||
assert_eq!(model.get_version(), 100000);
|
||||
assert_eq!(model.value().len(), 8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "tests_crate"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
native_model = { path = "../", no-default-features = true }
|
||||
serde = { version = "1.0.200", features = ["derive"], optional = true }
|
||||
bincode = { version = "2.0.0-rc.3", features = ["serde"] , optional = true }
|
||||
postcard = { version = "1.0.8", features = ["alloc"], optional = true }
|
||||
anyhow = "1.0.82"
|
||||
|
||||
|
||||
[features]
|
||||
default = ["bincode_1_3"]
|
||||
bincode_1_3 = ["serde", "native_model/bincode_1_3"]
|
||||
bincode_2 = ["serde", "native_model/bincode_2", "bincode"]
|
||||
postcard_1_0 = ["serde", "native_model/postcard_1_0", "postcard"]
|
||||
@@ -0,0 +1 @@
|
||||
mod example;
|
||||
@@ -0,0 +1,34 @@
|
||||
use bincode;
|
||||
use bincode::{config, Decode, Encode};
|
||||
|
||||
pub struct Bincode;
|
||||
|
||||
impl<T: bincode::Encode> native_model::Encode<T> for Bincode {
|
||||
type Error = bincode::error::EncodeError;
|
||||
fn encode(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::encode_to_vec(obj, config::standard())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: bincode::Decode> native_model::Decode<T> for Bincode {
|
||||
type Error = bincode::error::DecodeError;
|
||||
fn decode(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {
|
||||
bincode::decode_from_slice(&data, config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
}
|
||||
|
||||
use native_model::native_model;
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1, with = Bincode)]
|
||||
struct DotV1(u32, u32);
|
||||
|
||||
#[test]
|
||||
fn test_bincode_encode_decode() {
|
||||
// Application 1
|
||||
let dot = DotV1(1, 2);
|
||||
let bytes = native_model::encode(&dot).unwrap();
|
||||
// Application 1
|
||||
let (dot, _) = native_model::decode::<DotV1>(bytes).unwrap();
|
||||
assert_eq!(dot, DotV1(1, 2));
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use bincode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub struct Bincode;
|
||||
|
||||
impl<T: Serialize> native_model::Encode<T> for Bincode {
|
||||
type Error = bincode::error::EncodeError;
|
||||
fn encode(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::serde::encode_to_vec(obj, bincode::config::standard())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: for<'a> Deserialize<'a>> native_model::Decode<T> for Bincode {
|
||||
type Error = bincode::error::DecodeError;
|
||||
fn decode(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {
|
||||
Ok(bincode::serde::decode_from_slice(&data, bincode::config::standard())?.0)
|
||||
}
|
||||
}
|
||||
|
||||
use native_model::native_model;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1, with = Bincode)]
|
||||
struct DotV1(u32, u32);
|
||||
|
||||
#[test]
|
||||
fn test_bincode_serde_serialize_deserialize() {
|
||||
// Application 1
|
||||
let dot = DotV1(1, 2);
|
||||
let bytes = native_model::encode(&dot).unwrap();
|
||||
// Application 1
|
||||
let (dot, _) = native_model::decode::<DotV1>(bytes).unwrap();
|
||||
assert_eq!(dot, DotV1(1, 2));
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
mod bincode;
|
||||
mod bincode_serde;
|
||||
@@ -0,0 +1,19 @@
|
||||
#![cfg(feature = "bincode_1_3")]
|
||||
use native_model::native_model;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1, with = native_model::bincode_1_3::Bincode)]
|
||||
struct Example {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode() {
|
||||
let example = Example { a: 1, b: 2 };
|
||||
let bytes = native_model::encode(&example).unwrap();
|
||||
let (example, _) = native_model::decode::<Example>(bytes).unwrap();
|
||||
assert_eq!(example, Example { a: 1, b: 2 });
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#![cfg(feature = "bincode_2")]
|
||||
use native_model::{native_model};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1, with = native_model::bincode_2::Bincode)]
|
||||
struct Example {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode() {
|
||||
let example = Example { a: 1, b: 2 };
|
||||
let bytes = native_model::encode(&example).unwrap();
|
||||
let (example, _) = native_model::decode::<Example>(bytes).unwrap();
|
||||
assert_eq!(example, Example { a: 1, b: 2 });
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#![cfg(feature = "bincode_1_3")]
|
||||
use native_model::native_model;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Example {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode() {
|
||||
let example = Example { a: 1, b: 2 };
|
||||
let bytes = native_model::encode(&example).unwrap();
|
||||
let (example, _) = native_model::decode::<Example>(bytes).unwrap();
|
||||
assert_eq!(example, Example { a: 1, b: 2 });
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
mod default;
|
||||
mod bincode_1_3;
|
||||
mod bincode_2;
|
||||
mod postcard_1_0;
|
||||
@@ -0,0 +1,20 @@
|
||||
#![cfg(feature = "postcard_1_0")]
|
||||
use native_model::{native_model};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Deserialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1, with = native_model::postcard_1_0::PostCard)]
|
||||
struct Example {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode() {
|
||||
let example = Example { a: 1, b: 2 };
|
||||
let bytes = native_model::encode(&example).unwrap();
|
||||
let (example, _) = native_model::decode::<Example>(bytes).unwrap();
|
||||
assert_eq!(example, Example { a: 1, b: 2 });
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#![cfg(feature = "bincode")]
|
||||
use bincode::{config, Decode, Encode};
|
||||
use native_model::native_model;
|
||||
|
||||
pub struct Bincode;
|
||||
|
||||
impl<T: bincode::Encode> native_model::Encode<T> for Bincode {
|
||||
type Error = bincode::error::EncodeError;
|
||||
fn encode(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::encode_to_vec(obj, config::standard())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: bincode::Decode<()>> native_model::Decode<T> for Bincode {
|
||||
type Error = bincode::error::DecodeError;
|
||||
fn decode(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {
|
||||
bincode::decode_from_slice(&data, config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1, with = Bincode)]
|
||||
struct DotV1(u32, u32);
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 2, with = Bincode, from = DotV1)]
|
||||
struct DotV2 {
|
||||
name: String,
|
||||
x: u64,
|
||||
y: u64,
|
||||
}
|
||||
|
||||
impl From<DotV1> for DotV2 {
|
||||
fn from(dot: DotV1) -> Self {
|
||||
DotV2 {
|
||||
name: "".to_string(),
|
||||
x: dot.0 as u64,
|
||||
y: dot.1 as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DotV2> for DotV1 {
|
||||
fn from(dot: DotV2) -> Self {
|
||||
DotV1(dot.x as u32, dot.y as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 3, with = Bincode, try_from = (DotV2, anyhow::Error))]
|
||||
struct DotV3 {
|
||||
name: String,
|
||||
cord: Cord,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
struct Cord {
|
||||
x: u64,
|
||||
y: u64,
|
||||
}
|
||||
|
||||
impl TryFrom<DotV2> for DotV3 {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(dot: DotV2) -> Result<Self, Self::Error> {
|
||||
Ok(DotV3 {
|
||||
name: dot.name,
|
||||
cord: Cord { x: dot.x, y: dot.y },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DotV3> for DotV2 {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(dot: DotV3) -> Result<Self, Self::Error> {
|
||||
Ok(DotV2 {
|
||||
name: dot.name,
|
||||
x: dot.cord.x,
|
||||
y: dot.cord.y,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_test() {
|
||||
let dot = DotV1(1, 2);
|
||||
let bytes = native_model::encode(&dot).unwrap();
|
||||
|
||||
let (dot_decoded, _) = native_model::decode::<DotV1>(bytes.clone()).unwrap();
|
||||
assert_eq!(dot, dot_decoded);
|
||||
|
||||
let (dot_decoded, _) = native_model::decode::<DotV2>(bytes.clone()).unwrap();
|
||||
assert_eq!(
|
||||
DotV2 {
|
||||
name: "".to_string(),
|
||||
x: 1,
|
||||
y: 2
|
||||
},
|
||||
dot_decoded
|
||||
);
|
||||
|
||||
let (dot_decoded, _) = native_model::decode::<DotV3>(bytes.clone()).unwrap();
|
||||
assert_eq!(
|
||||
DotV3 {
|
||||
name: "".to_string(),
|
||||
cord: Cord { x: 1, y: 2 }
|
||||
},
|
||||
dot_decoded
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#![cfg(feature = "bincode_1_3")]
|
||||
use native_model::native_model;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct DotV1(u32, u32);
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 2, from = DotV1)]
|
||||
struct DotV2 {
|
||||
name: String,
|
||||
x: u64,
|
||||
y: u64,
|
||||
}
|
||||
|
||||
impl From<DotV1> for DotV2 {
|
||||
fn from(dot: DotV1) -> Self {
|
||||
DotV2 {
|
||||
name: "".to_string(),
|
||||
x: dot.0 as u64,
|
||||
y: dot.1 as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DotV2> for DotV1 {
|
||||
fn from(dot: DotV2) -> Self {
|
||||
DotV1(dot.x as u32, dot.y as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_example() {
|
||||
// Application 1
|
||||
let dot = DotV1(1, 2);
|
||||
let bytes = native_model::encode(&dot).unwrap();
|
||||
|
||||
// Application 1 sends bytes to Application 2.
|
||||
|
||||
// Application 2
|
||||
let (mut dot, source_version) = native_model::decode::<DotV2>(bytes).unwrap();
|
||||
// Use the struct DataV2 which has more fields and a different structure.
|
||||
dot.name = "Dot".to_string();
|
||||
dot.x = 5;
|
||||
// Encode the dot with the application 1 version in order to be compatible with it.
|
||||
let bytes = native_model::encode_downgrade(dot, source_version).unwrap();
|
||||
|
||||
// Application 2 sends bytes to Application 1.
|
||||
|
||||
// Application 1
|
||||
let (dot, _) = native_model::decode::<DotV1>(bytes).unwrap();
|
||||
assert_eq!(dot, DotV1(5, 2));
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mod default_codec;
|
||||
mod example_define_model;
|
||||
mod example_main;
|
||||
@@ -0,0 +1,46 @@
|
||||
#![cfg(feature = "bincode_1_3")]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use native_model::{native_model, Model};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Foo1 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[native_model(id = 1, version = 2, from = Foo1)]
|
||||
struct Foo2 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
impl From<Foo1> for Foo2 {
|
||||
fn from(foo1: Foo1) -> Self {
|
||||
Foo2 { x: foo1.x }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Foo2> for Foo1 {
|
||||
fn from(foo2: Foo2) -> Self {
|
||||
Foo1 { x: foo2.x }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_id_version_int() {
|
||||
assert_eq!(Foo1::native_model_id(), 1);
|
||||
assert_eq!(Foo1::native_model_version(), 1);
|
||||
|
||||
assert_eq!(Foo2::native_model_id(), 1);
|
||||
assert_eq!(Foo2::native_model_version(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_id_version_str() {
|
||||
assert_eq!(Foo1::native_model_id_str(), "1");
|
||||
assert_eq!(Foo1::native_model_version_str(), "1");
|
||||
|
||||
assert_eq!(Foo2::native_model_id_str(), "1");
|
||||
assert_eq!(Foo2::native_model_version_str(), "2");
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
#![cfg(feature = "bincode_1_3")]
|
||||
use native_model::native_model;
|
||||
use native_model::Model;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Foo1 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[native_model(id = 1, version = 2, from = Foo1)]
|
||||
struct Foo2 {
|
||||
x: String,
|
||||
}
|
||||
|
||||
impl From<Foo1> for Foo2 {
|
||||
fn from(foo1: Foo1) -> Self {
|
||||
Foo2 {
|
||||
x: foo1.x.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Foo2> for Foo1 {
|
||||
fn from(foo2: Foo2) -> Self {
|
||||
Foo1 {
|
||||
x: foo2.x.parse::<i32>().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[native_model(id = 1, version = 3, from = Foo2)]
|
||||
enum Foo3 {
|
||||
X(i32),
|
||||
}
|
||||
|
||||
impl From<Foo2> for Foo3 {
|
||||
fn from(foo2: Foo2) -> Self {
|
||||
Foo3::X(foo2.x.parse::<i32>().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Foo3> for Foo2 {
|
||||
fn from(foo3: Foo3) -> Self {
|
||||
match foo3 {
|
||||
Foo3::X(x) => Foo2 { x: x.to_string() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_foo1_to_foo2() {
|
||||
let foo1 = Foo1 { x: 100 };
|
||||
let foo1_encoded = foo1.native_model_encode_body().unwrap();
|
||||
let foo2_decoded = Foo2::native_model_decode_upgrade_body(foo1_encoded, 1, 1).unwrap();
|
||||
assert_eq!(foo1.x.to_string(), foo2_decoded.x);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_foo2_to_foo3() {
|
||||
let foo2 = Foo2 {
|
||||
x: "100".to_string(),
|
||||
};
|
||||
let foo2_encoded = foo2.native_model_encode_body().unwrap();
|
||||
let foo3_decoded = Foo3::native_model_decode_upgrade_body(foo2_encoded, 1, 2).unwrap();
|
||||
assert_eq!(Foo3::X(100), foo3_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_foo1_to_foo3() {
|
||||
let foo1 = Foo1 { x: 100 };
|
||||
let foo1_encoded = foo1.native_model_encode_body().unwrap();
|
||||
let foo3_decoded = Foo3::native_model_decode_upgrade_body(foo1_encoded, 1, 1).unwrap();
|
||||
assert_eq!(Foo3::X(100), foo3_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_foo1_to_foo1() {
|
||||
let foo1 = Foo1 { x: 100 };
|
||||
let foo1_encoded = foo1.native_model_encode_body().unwrap();
|
||||
let foo1_decoded = Foo1::native_model_decode_upgrade_body(foo1_encoded, 1, 1).unwrap();
|
||||
assert_eq!(foo1, foo1_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_foo2_to_foo2() {
|
||||
let foo2 = Foo2 {
|
||||
x: "100".to_string(),
|
||||
};
|
||||
let foo2_encoded = foo2.native_model_encode_body().unwrap();
|
||||
let foo2_decoded = Foo2::native_model_decode_upgrade_body(foo2_encoded, 1, 2).unwrap();
|
||||
assert_eq!(foo2, foo2_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_foo3_to_foo3() {
|
||||
let foo3 = Foo3::X(100);
|
||||
let foo3_encoded = foo3.native_model_encode_body().unwrap();
|
||||
let foo3_decoded = Foo3::native_model_decode_upgrade_body(foo3_encoded, 1, 3).unwrap();
|
||||
assert_eq!(foo3, foo3_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_fail_decode_foo3_to_foo2() {
|
||||
let foo3 = Foo3::X(100);
|
||||
let foo3_encoded = foo3.native_model_encode_body().unwrap();
|
||||
let foo3_decoded = Foo2::native_model_decode_upgrade_body(foo3_encoded, 1, 3);
|
||||
assert!(foo3_decoded.is_err());
|
||||
assert!(matches!(
|
||||
foo3_decoded.unwrap_err(),
|
||||
native_model::Error::UpgradeNotSupported { from: 3, to: 2 }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_fail_decode_foo3_to_foo1() {
|
||||
let foo3 = Foo3::X(100);
|
||||
let foo3_encoded = foo3.native_model_encode_body().unwrap();
|
||||
let foo3_decoded = Foo1::native_model_decode_upgrade_body(foo3_encoded, 1, 3);
|
||||
assert!(foo3_decoded.is_err());
|
||||
assert!(matches!(
|
||||
foo3_decoded.unwrap_err(),
|
||||
native_model::Error::UpgradeNotSupported { from: 3, to: 1 }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_fail_decode_foo2_to_foo1() {
|
||||
let foo2 = Foo2 {
|
||||
x: "100".to_string(),
|
||||
};
|
||||
let foo2_encoded = foo2.native_model_encode_body().unwrap();
|
||||
let foo2_decoded = Foo1::native_model_decode_upgrade_body(foo2_encoded, 1, 2);
|
||||
assert!(foo2_decoded.is_err());
|
||||
assert!(matches!(
|
||||
foo2_decoded.unwrap_err(),
|
||||
native_model::Error::UpgradeNotSupported { from: 2, to: 1 }
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[native_model(id = 2, version = 1)]
|
||||
struct Foo1Bis {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prevent_to_decode_the_wrong_model() {
|
||||
let foo1 = Foo1 { x: 100 };
|
||||
let foo1_encoded = foo1.native_model_encode_body().unwrap();
|
||||
let foo1_decoded = Foo1Bis::native_model_decode_upgrade_body(foo1_encoded, 1, 1);
|
||||
dbg!(&foo1_decoded);
|
||||
// assert!(foo1_decoded.is_err());
|
||||
// assert!(matches!(
|
||||
// foo1_decoded.unwrap_err(),
|
||||
// native_model::Error::TypeIdMismatch {
|
||||
// expected: 1,
|
||||
// actual: 1
|
||||
// }
|
||||
// ));
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#![cfg(feature = "bincode_1_3")]
|
||||
|
||||
use native_model::native_model;
|
||||
use native_model::Model;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Foo1 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[native_model(id = 1, version = 2, from = Foo1)]
|
||||
struct Foo2 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
impl From<Foo1> for Foo2 {
|
||||
fn from(foo1: Foo1) -> Self {
|
||||
Foo2 { x: foo1.x }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Foo2> for Foo1 {
|
||||
fn from(foo2: Foo2) -> Self {
|
||||
Foo1 { x: foo2.x }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple() {
|
||||
let foo1 = Foo1 { x: 100 };
|
||||
let foo2 = Foo2 { x: 200 };
|
||||
let foo1_encoded = foo1.native_model_encode().unwrap();
|
||||
let foo2_encoded = foo2.native_model_encode().unwrap();
|
||||
|
||||
let (foo1_decoded, _) = Foo1::native_model_decode(foo1_encoded).unwrap();
|
||||
assert!(foo1_decoded == foo1);
|
||||
let (foo2_decoded, _) = Foo2::native_model_decode(foo2_encoded).unwrap();
|
||||
assert!(foo2_decoded == foo2);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
mod example;
|
||||
@@ -0,0 +1,96 @@
|
||||
#![cfg(feature = "bincode_1_3")]
|
||||
|
||||
use native_model::native_model;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Foo1 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[native_model(id = 1, version = 2, from = Foo1)]
|
||||
struct Foo2 {
|
||||
x: i32,
|
||||
c: char,
|
||||
}
|
||||
|
||||
impl From<Foo1> for Foo2 {
|
||||
fn from(foo1: Foo1) -> Self {
|
||||
Foo2 { x: foo1.x, c: 'a' }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Foo2> for Foo1 {
|
||||
fn from(foo2: Foo2) -> Self {
|
||||
Foo1 { x: foo2.x }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Foo1> for Foo2 {
|
||||
fn eq(&self, other: &Foo1) -> bool {
|
||||
self.x == other.x
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_foo1_to_foo1() {
|
||||
let foo1 = Foo1 { x: 100 };
|
||||
let foo1_packed = native_model::encode(&foo1).unwrap();
|
||||
let (foo1_decoded, _) = native_model::decode::<Foo1>(foo1_packed.clone()).unwrap();
|
||||
assert_eq!(foo1, foo1_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_foo1_to_foo2() {
|
||||
let foo1 = Foo1 { x: 100 };
|
||||
let foo1_packed = native_model::encode(&foo1).unwrap();
|
||||
let (foo2_decoded, _) = native_model::decode::<Foo2>(foo1_packed.clone()).unwrap();
|
||||
assert_eq!(Foo2 { x: 100, c: 'a' }, foo2_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_foo2_to_foo1() {
|
||||
let foo2 = Foo2 { x: 100, c: 'a' };
|
||||
let foo2_packed = native_model::encode(&foo2).unwrap();
|
||||
assert_eq!(foo2_packed, vec![1, 0, 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 97]);
|
||||
let (foo2_decoded, _) = native_model::decode::<Foo2>(foo2_packed.clone()).unwrap();
|
||||
assert_eq!(Foo2 { x: 100, c: 'a' }, foo2_decoded);
|
||||
let foo1_packed = native_model::encode_downgrade(foo2, 1).unwrap();
|
||||
assert_eq!(foo1_packed, vec![1, 0, 0, 0, 1, 0, 0, 0, 100, 0, 0, 0]);
|
||||
let (foo1_decoded, _) = native_model::decode::<Foo1>(foo1_packed.clone()).unwrap();
|
||||
assert_eq!(Foo1 { x: 100 }, foo1_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_foo1_to_foo1() {
|
||||
let foo1 = Foo1 { x: 100 };
|
||||
let foo1_packed = native_model::encode(&foo1).unwrap();
|
||||
assert_eq!(foo1_packed, vec![1, 0, 0, 0, 1, 0, 0, 0, 100, 0, 0, 0]);
|
||||
let (foo1_decoded, _) = native_model::decode::<Foo1>(foo1_packed.clone()).unwrap();
|
||||
assert_eq!(Foo1 { x: 100 }, foo1_decoded);
|
||||
let foo1_packed = native_model::encode_downgrade(foo1, 1).unwrap();
|
||||
assert_eq!(foo1_packed, vec![1, 0, 0, 0, 1, 0, 0, 0, 100, 0, 0, 0]);
|
||||
let (foo1_decoded, _) = native_model::decode::<Foo1>(foo1_packed.clone()).unwrap();
|
||||
assert_eq!(Foo1 { x: 100 }, foo1_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_with_same_version() {
|
||||
// Client 1
|
||||
let foo1 = Foo1 { x: 100 };
|
||||
let foo_packed = native_model::encode(&foo1).unwrap();
|
||||
// Send foo_packed to server
|
||||
|
||||
// Server
|
||||
let (mut foo2, version) = native_model::decode::<Foo2>(foo_packed.clone()).unwrap();
|
||||
// Do something with foo2
|
||||
foo2.x += 1;
|
||||
let foo_packed = native_model::encode_downgrade(foo2, version).unwrap();
|
||||
// Send foo_packed back to client
|
||||
|
||||
// Client
|
||||
let (foo1_decoded, _) = native_model::decode::<Foo1>(foo_packed.clone()).unwrap();
|
||||
assert_eq!(Foo1 { x: 101 }, foo1_decoded);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#![cfg(feature = "bincode_1_3")]
|
||||
|
||||
use native_model::native_model;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Foo1 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[native_model(id = 1, version = 2, try_from = (Foo1, anyhow::Error))]
|
||||
struct Foo2 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
impl TryFrom<Foo1> for Foo2 {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(foo1: Foo1) -> Result<Self, Self::Error> {
|
||||
if foo1.x > 10 {
|
||||
return Err(anyhow::anyhow!("x > 10"));
|
||||
}
|
||||
|
||||
Ok(Foo2 { x: foo1.x })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Foo2> for Foo1 {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(foo2: Foo2) -> Result<Self, Self::Error> {
|
||||
if foo2.x > 10 {
|
||||
return Err(anyhow::anyhow!("x > 10"));
|
||||
}
|
||||
|
||||
Ok(Foo1 { x: foo2.x })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_foo1_to_foo1() {
|
||||
let foo1 = Foo1 { x: 1 };
|
||||
let foo1_packed = native_model::encode(&foo1).unwrap();
|
||||
let (foo1_decoded, _) = native_model::decode::<Foo1>(foo1_packed.clone()).unwrap();
|
||||
assert_eq!(foo1, foo1_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_foo1_to_foo2() {
|
||||
let foo1 = Foo1 { x: 1 };
|
||||
let foo1_packed = native_model::encode(&foo1).unwrap();
|
||||
let (foo2_decoded, _) = native_model::decode::<Foo2>(foo1_packed.clone()).unwrap();
|
||||
assert_eq!(Foo2 { x: 1 }, foo2_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_foo1_to_foo2_error() {
|
||||
let foo1 = Foo1 { x: 1000 };
|
||||
let foo1_packed = native_model::encode(&foo1).unwrap();
|
||||
let foo2_decoded = native_model::decode::<Foo2>(foo1_packed.clone());
|
||||
assert!(foo2_decoded.is_err());
|
||||
assert!(matches!(
|
||||
foo2_decoded.unwrap_err(),
|
||||
native_model::Error::UpgradeError(_)
|
||||
));
|
||||
}
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
# Bash script to update version for native_model_macro
|
||||
|
||||
# Semantic release version obtained from argument
|
||||
NEW_VERSION=$1
|
||||
|
||||
# Exit if NEW_VERSION is not set
|
||||
if [ -z "$NEW_VERSION" ]; then
|
||||
echo "NEW_VERSION argument not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Directories containing Cargo.toml files to update
|
||||
declare -a directories=("." "native_model_macro")
|
||||
|
||||
for directory in "${directories[@]}"
|
||||
do
|
||||
# Check if Cargo.toml exists in the directory
|
||||
if [ -f "$directory/Cargo.toml" ]; then
|
||||
echo "Updating version in $directory/Cargo.toml to $NEW_VERSION"
|
||||
# Use sed to find and replace the version string
|
||||
sed -i -E "s/^version = \"[0-9]+\.[0-9]+\.[0-9]+\"/version = \"$NEW_VERSION\"/g" "$directory/Cargo.toml"
|
||||
|
||||
# Update the dependency version for native_model_macro in native_model_macro's Cargo.toml
|
||||
if [ "$directory" == "." ]; then
|
||||
sed -i -E "s/native_model_macro = \{ version = \"[0-9]+\.[0-9]+\.[0-9]+\", path = \"native_model_macro\" \}/native_model_macro = { version = \"$NEW_VERSION\", path = \"native_model_macro\" }/g" "$directory/Cargo.toml"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
cd "$DIR/"
|
||||
|
||||
# Commit
|
||||
git commit --all --message "chore: update version to $NEW_VERSION"
|
||||
git push
|
||||
Reference in New Issue
Block a user