mirror of
https://github.com/Drop-OSS/native_model.git
synced 2026-01-30 20:55:19 +01:00
feat: init
This commit is contained in:
71
.github/workflows/build_and_test_release.yml
vendored
Normal file
71
.github/workflows/build_and_test_release.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: Linux/Windows/macOS (Build/Test/Release)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, next ]
|
||||
pull_request:
|
||||
branches: [ main, next ]
|
||||
|
||||
jobs:
|
||||
build_test_common_os:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
toolchain: [stable]
|
||||
feature: [ no_feature ]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
override: true
|
||||
- name: Setup Feature Args
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ matrix.feature }}" == "no_feature" ]]; then
|
||||
echo "FEATURE_ARGS=" >> $GITHUB_ENV
|
||||
else
|
||||
echo "FEATURE_ARGS=-F ${{ matrix.feature }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: ${{ env.FEATURE_ARGS }}
|
||||
- name: Run tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: ${{ env.FEATURE_ARGS }}
|
||||
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@v3
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: install npm
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
- name: install @semantic-release/exec
|
||||
run: npm install @semantic-release/exec
|
||||
|
||||
- name: Semantic Release
|
||||
uses: cycjimmy/semantic-release-action@v3
|
||||
with:
|
||||
branch: main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }}
|
||||
CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }}
|
||||
13
.github/workflows/conventional_commits.yml
vendored
Normal file
13
.github/workflows/conventional_commits.yml
vendored
Normal file
@@ -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@v3
|
||||
- uses: webiny/action-conventional-commits@v1.1.0
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
|
||||
/native_model_macro/target
|
||||
/native_model_macro/Cargo.lock
|
||||
39
Cargo.toml
Normal file
39
Cargo.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "native_model"
|
||||
version = "0.1.0"
|
||||
authors = ["Vincent Herlemont <vincent@herlemont.fr>"]
|
||||
edition = "2021"
|
||||
description = "A thin wrapper around serialized data which add information of identity and version."
|
||||
license = "MIT"
|
||||
repository = "https://github.com/vincent-herlemont/native_model"
|
||||
readme = "README.md"
|
||||
build = "build.rs"
|
||||
keywords = ["serialization", "interoperability", "data-consistency", "flexibility", "performance"]
|
||||
categories = ["data-structures", "encoding", "rust-patterns"]
|
||||
|
||||
[workspace]
|
||||
members = ["native_model_macro"]
|
||||
|
||||
[dependencies]
|
||||
zerocopy = { version = "0.7.1", features = [ "derive"] }
|
||||
thiserror = "1.0"
|
||||
anyhow = "1.0"
|
||||
native_model_macro = { version = "0.1.0", path = "native_model_macro" }
|
||||
|
||||
[dev-dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
bincode = { version = "2.0.0-rc.3", features = ["serde"] }
|
||||
serde_json = "1.0"
|
||||
criterion = { version = "0.5.1" }
|
||||
skeptic = "0.13"
|
||||
|
||||
[[bench]]
|
||||
name = "overhead"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "overhead_on_bincode"
|
||||
harness = false
|
||||
|
||||
[build-dependencies]
|
||||
skeptic = "0.13"
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -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.
|
||||
168
README.md
Normal file
168
README.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 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)
|
||||
|
||||
A thin wrapper around serialized data which add information of identity and version.
|
||||
|
||||
## 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. More details [here](#performance).
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
Application 1 (DotV1) Application 2 (DotV1 and DotV2)
|
||||
| |
|
||||
Encode DotV1 |----------------------------------------> | Decode DotV1 to DotV2
|
||||
| | Modify DotV2
|
||||
Decode DotV1 | <----------------------------------------| Encode DotV2 back to DotV1
|
||||
| |
|
||||
```
|
||||
|
||||
|
||||
```rust,skt-main
|
||||
// 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.
|
||||
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.
|
||||
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/example/example_main.rs).
|
||||
|
||||
When use it?
|
||||
- Your applications that interact with each other are written in Rust.
|
||||
- Your applications evolve independently need to read serialized data coming from each other.
|
||||
- Your applications store data locally and need to read it later by a newer version of the application.
|
||||
- Your systems need to be upgraded incrementally. Instead of having to upgrade the entire system at once, individual
|
||||
applications can be upgraded one at a time, while still being able to communicate with each other.
|
||||
|
||||
When not use it?
|
||||
- Your applications that interact with each other are **not all** written in Rust.
|
||||
- Your applications need to communicate with other systems that you don't control.
|
||||
- You need to have a human-readable format. (You can use a human-readable format like JSON wrapped in a native model,
|
||||
but you have to unwrap it to see the data correctly.)
|
||||
|
||||
# Status
|
||||
|
||||
Early development. Not ready for production.
|
||||
|
||||
## Setup your serialization format
|
||||
|
||||
First, you need to set up your serialization format. You can use any serialization format.
|
||||
|
||||
Just define the following functions, so they must be imported in the scope where you use the native model.
|
||||
|
||||
```rust,ignore
|
||||
fn native_model_encode_body<T: Encode>(obj: &T) -> Result<Vec<u8>, dyn Error> {
|
||||
...
|
||||
}
|
||||
|
||||
fn native_model_decode_body<T: Decode>(data: Vec<u8>) -> Result<T, dyn Error> {
|
||||
...
|
||||
}
|
||||
```
|
||||
Examples:
|
||||
- [bincode with encode/decode](./tests/example/encode_decode/bincode.rs)
|
||||
- [bincode with serde](./tests/example/encode_decode/bincode_serde.rs)
|
||||
|
||||
|
||||
## Setup your 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.
|
||||
- `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,skt-define-models
|
||||
use native_model::native_model;
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct DotV1(u32, u32);
|
||||
|
||||
#[derive(Encode, Decode, 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.
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 3, try_from = (DotV2, anyhow::Error))]
|
||||
struct DotV3 {
|
||||
name: String,
|
||||
cord: Cord,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
struct Cord {
|
||||
x: u64,
|
||||
y: u64,
|
||||
}
|
||||
|
||||
// Implement the conversion between versions From<DotV2> for DotV3 and From<DotV3> for DotV2.
|
||||
```
|
||||
|
||||
Full example [here](tests/example/example_define_model.rs).
|
||||
|
||||
|
||||
# Performance
|
||||
|
||||
This crate is in an early stage of development, so the performance should be improved in the future.
|
||||
The goal is to have a minimal and constant overhead for all data sizes. It uses the [zerocopy](https://docs.rs/zerocopy/latest/zerocopy/) crate to avoid unnecessary copies.
|
||||
|
||||
Current performance:
|
||||
- Encode time: have overhead that evolves linearly with the data size.
|
||||
- Decode time: have overhead of ~162 ps for all data sizes.
|
||||
|
||||
|
||||
| data size | encode time (ns/ps/µs/ms) | decode time (ps) |
|
||||
|:---------------------:|:--------------------------:|:----------------:|
|
||||
| 1 B | 40.093 ns - 40.510 ns | 161.87 ps - 162.02 ps |
|
||||
| 1 KiB (1024 B) | 116.45 ns - 116.83 ns | 161.85 ps - 162.08 ps |
|
||||
| 1 MiB (1048576 B) | 66.697 µs - 67.634 µs | 161.87 ps - 162.18 ps |
|
||||
| 10 MiB (10485760 B) | 1.5670 ms - 1.5843 ms | 162.40 ps - 163.52 ps |
|
||||
| 100 MiB (104857600 B) | 63.778 ms - 64.132 ms | 162.71 ps - 165.10 ps |
|
||||
|
||||
Benchmark of the native model overhead [here](benches/overhead.rs).
|
||||
|
||||
To know how much time it takes to encode/decode your data, you need to add this overhead to the time of your serialization format.
|
||||
|
||||
140
README.md.skt.md
Normal file
140
README.md.skt.md
Normal file
@@ -0,0 +1,140 @@
|
||||
```rust,skt-main
|
||||
use bincode;
|
||||
use bincode::{{Decode, Encode}};
|
||||
use native_model_macro::native_model;
|
||||
|
||||
fn native_model_encode_body<T: bincode::Encode>(
|
||||
model: &T,
|
||||
) -> Result<Vec<u8>, bincode::error::EncodeError> {{
|
||||
{{
|
||||
bincode::encode_to_vec(model, bincode::config::standard())
|
||||
}}
|
||||
}}
|
||||
|
||||
fn native_model_decode_body<T: bincode::Decode>(
|
||||
data: Vec<u8>,
|
||||
) -> Result<T, bincode::error::DecodeError> {{
|
||||
{{
|
||||
bincode::decode_from_slice(&data, bincode::config::standard()).map(|(result, _)| result)
|
||||
}}
|
||||
}}
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct DotV1(u32, u32);
|
||||
|
||||
#[derive(Encode, Decode, 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)
|
||||
}}
|
||||
}}
|
||||
|
||||
|
||||
fn main() {{
|
||||
{}
|
||||
}}
|
||||
```
|
||||
|
||||
```rust,skt-define-models
|
||||
use bincode::{{config, Decode, Encode}};
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn native_model_encode_body<T: Encode>(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {{
|
||||
bincode::encode_to_vec(obj, config::standard())
|
||||
}}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn native_model_decode_body<T: Decode>(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {{
|
||||
bincode::decode_from_slice(&data, config::standard()).map(|(result, _)| result)
|
||||
}}
|
||||
|
||||
|
||||
{}
|
||||
|
||||
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)
|
||||
}}
|
||||
}}
|
||||
|
||||
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,
|
||||
}})
|
||||
}}
|
||||
}}
|
||||
|
||||
|
||||
|
||||
fn main() {{
|
||||
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
|
||||
);
|
||||
}}
|
||||
|
||||
```
|
||||
49
benches/overhead.rs
Normal file
49
benches/overhead.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use bincode::{Decode, Encode};
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use native_model_macro::native_model;
|
||||
|
||||
fn native_model_encode_body<T: Encode>(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::encode_to_vec(obj, bincode::config::standard())
|
||||
}
|
||||
|
||||
fn native_model_decode_body<T: Decode>(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {
|
||||
bincode::decode_from_slice(&data, bincode::config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Data(Vec<u8>);
|
||||
|
||||
fn wrapper(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 encode_body = native_model_encode_body(&data).unwrap();
|
||||
group.bench_function(BenchmarkId::new("encode", nb_bytes), |b| {
|
||||
b.iter(|| wrapper(&mut encode_body.clone()))
|
||||
});
|
||||
|
||||
// decode
|
||||
let data = Data(vec![1; nb_bytes]);
|
||||
let encode_body = native_model::encode(&data).unwrap();
|
||||
group.bench_function(BenchmarkId::new("decode", nb_bytes), |b| {
|
||||
b.iter(|| unwrap(&mut encode_body.clone()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
88
benches/overhead_on_bincode.rs
Normal file
88
benches/overhead_on_bincode.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use bincode::{Decode, Encode};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use native_model_macro::native_model;
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
struct DataForBincode {
|
||||
x: i32,
|
||||
string: String,
|
||||
}
|
||||
|
||||
// Encode 1 data with bincode
|
||||
fn native_model_encode_body<T: Encode>(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::encode_to_vec(obj, bincode::config::standard())
|
||||
}
|
||||
|
||||
fn native_model_decode_body<T: Decode>(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {
|
||||
bincode::decode_from_slice(&data, bincode::config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
|
||||
fn encode_with_bincode(data: &DataForBincode) -> Vec<u8> {
|
||||
native_model_encode_body(data).unwrap()
|
||||
}
|
||||
|
||||
fn decode_with_bincode(data: Vec<u8>) -> DataForBincode {
|
||||
native_model_decode_body(data).unwrap()
|
||||
}
|
||||
|
||||
fn encode_decode_with_bincode(data: &DataForBincode) -> DataForBincode {
|
||||
decode_with_bincode(encode_with_bincode(data))
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct DataForNativeModel {
|
||||
x: i32,
|
||||
string: String,
|
||||
}
|
||||
|
||||
fn encode_with_native_model(data: &DataForNativeModel) -> Vec<u8> {
|
||||
native_model::encode(data).unwrap()
|
||||
}
|
||||
|
||||
fn decode_with_native_model(data: Vec<u8>) -> DataForNativeModel {
|
||||
let (data, _) = native_model::decode::<DataForNativeModel>(data).unwrap();
|
||||
data
|
||||
}
|
||||
|
||||
fn encode_decode_with_native_model(data: &DataForNativeModel) -> DataForNativeModel {
|
||||
decode_with_native_model(encode_with_native_model(data))
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
// Bincode
|
||||
let data = DataForBincode {
|
||||
x: 1,
|
||||
// Set a very long string
|
||||
string: "Hello".repeat(10000),
|
||||
};
|
||||
c.bench_function("encode_with_bincode", |b| {
|
||||
b.iter(|| encode_with_bincode(black_box(&data)))
|
||||
});
|
||||
let encoded_data = encode_with_bincode(&data);
|
||||
c.bench_function("decode_with_bincode", |b| {
|
||||
b.iter(|| decode_with_bincode(black_box(encoded_data.clone())))
|
||||
});
|
||||
c.bench_function("encode_decode_with_bincode", |b| {
|
||||
b.iter(|| encode_decode_with_bincode(black_box(&data)))
|
||||
});
|
||||
|
||||
// Native model
|
||||
let data = DataForNativeModel {
|
||||
x: 1,
|
||||
string: "Hello".repeat(10000),
|
||||
};
|
||||
c.bench_function("encode_with_native_model", |b| {
|
||||
b.iter(|| encode_with_native_model(black_box(&data)))
|
||||
});
|
||||
let encoded_data = native_model::encode(&data).unwrap();
|
||||
c.bench_function("decode_with_native_model", |b| {
|
||||
b.iter(|| decode_with_native_model(black_box(encoded_data.clone())))
|
||||
});
|
||||
c.bench_function("encode_decode_with_native_model", |b| {
|
||||
b.iter(|| encode_decode_with_native_model(black_box(&data)))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
11
build.rs
Normal file
11
build.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
extern crate skeptic;
|
||||
|
||||
use skeptic::{generate_doc_tests, markdown_files_of_directory};
|
||||
|
||||
fn main() {
|
||||
{
|
||||
let mut mdbook_files = markdown_files_of_directory("doc/");
|
||||
mdbook_files.push("README.md".into());
|
||||
generate_doc_tests(&mdbook_files);
|
||||
}
|
||||
}
|
||||
14
cargo_publish.sh
Executable file
14
cargo_publish.sh
Executable file
@@ -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 $@
|
||||
19
native_model_macro/Cargo.toml
Normal file
19
native_model_macro/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "native_model_macro"
|
||||
version = "0.1.0"
|
||||
authors = ["Vincent Herlemont <vincent@herlemont.fr>"]
|
||||
edition = "2018"
|
||||
description = "A procedural macro for native_model"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/vincent-herlemont/native_model"
|
||||
readme = "README.md"
|
||||
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0.66"
|
||||
1
native_model_macro/README.md
Normal file
1
native_model_macro/README.md
Normal file
@@ -0,0 +1 @@
|
||||
A procedural macro for [native_model](https://github.com/vincent-herlemont/native_model).
|
||||
114
native_model_macro/src/lib.rs
Normal file
114
native_model_macro/src/lib.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
mod method;
|
||||
|
||||
use crate::method::{
|
||||
generate_native_model_decode_body, generate_native_model_decode_upgrade_body,
|
||||
generate_native_model_encode_body, generate_native_model_encode_downgrade_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
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ModelAttributes {
|
||||
pub(crate) id: Option<LitInt>,
|
||||
pub(crate) version: Option<LitInt>,
|
||||
// type
|
||||
pub(crate) from: Option<Path>,
|
||||
// (type, try_from::Error type)
|
||||
pub(crate) try_from: Option<(Path, Path)>,
|
||||
}
|
||||
|
||||
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("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().to_string()
|
||||
);
|
||||
}
|
||||
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.
|
||||
/// - `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/vincent-herlemont/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();
|
||||
let native_model_encode_downgrade_body_fn = generate_native_model_encode_downgrade_body(&attrs);
|
||||
let native_model_decode_body_fn = generate_native_model_decode_body();
|
||||
let native_model_decode_upgrade_body_fn = generate_native_model_decode_upgrade_body(&attrs);
|
||||
|
||||
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_encode_downgrade_body_fn
|
||||
#native_model_decode_body_fn
|
||||
#native_model_decode_upgrade_body_fn
|
||||
}
|
||||
};
|
||||
|
||||
gen.into()
|
||||
}
|
||||
15
native_model_macro/src/method/decode_body.rs
Normal file
15
native_model_macro/src/method/decode_body.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn generate_native_model_decode_body() -> TokenStream {
|
||||
let gen = quote! {
|
||||
fn native_model_decode_body(data: Vec<u8>) -> Result<Self, native_model::DecodeBodyError> {
|
||||
native_model_decode_body(data).map_err(|e| native_model::DecodeBodyError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
gen.into()
|
||||
}
|
||||
50
native_model_macro/src/method/decode_upgrade_body.rs
Normal file
50
native_model_macro/src/method/decode_upgrade_body.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use crate::ModelAttributes;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn generate_native_model_decode_upgrade_body(attrs: &ModelAttributes) -> TokenStream {
|
||||
let native_model_from = attrs.from.clone();
|
||||
let native_model_try_from = attrs.try_from.clone();
|
||||
|
||||
let model_from_or_try_from = if let Some(from) = native_model_from {
|
||||
quote! {
|
||||
#from::native_model_decode_upgrade_body(data, x).map(|a| a.into())
|
||||
}
|
||||
} else if let Some((try_from, error_try_from)) = native_model_try_from {
|
||||
quote! {
|
||||
let result = #try_from::native_model_decode_upgrade_body(data, x).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: x,
|
||||
to: Self::native_model_version(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let gen = quote! {
|
||||
fn native_model_decode_upgrade_body(data: Vec<u8>, x: u32) -> native_model::Result<Self> {
|
||||
if x == Self::native_model_version() {
|
||||
let result = Self::native_model_decode_body(data)?;
|
||||
Ok(result)
|
||||
} else if x < Self::native_model_version() {
|
||||
#model_from_or_try_from
|
||||
} else {
|
||||
Err(native_model::Error::UpgradeNotSupported {
|
||||
from: x,
|
||||
to: Self::native_model_version(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
gen
|
||||
}
|
||||
15
native_model_macro/src/method/encode_body.rs
Normal file
15
native_model_macro/src/method/encode_body.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn generate_native_model_encode_body() -> TokenStream {
|
||||
let gen = quote! {
|
||||
fn native_model_encode_body(&self) -> Result<Vec<u8>, native_model::EncodeBodyError> {
|
||||
native_model_encode_body(self).map_err(|e| native_model::EncodeBodyError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
gen.into()
|
||||
}
|
||||
54
native_model_macro/src/method/encode_downgrade_body.rs
Normal file
54
native_model_macro/src/method/encode_downgrade_body.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use crate::ModelAttributes;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) fn generate_native_model_encode_downgrade_body(attrs: &ModelAttributes) -> TokenStream {
|
||||
let native_model_from = attrs.from.clone();
|
||||
let native_model_try_from = attrs.try_from.clone();
|
||||
|
||||
let model_from_or_try_from = if let Some(from) = native_model_from {
|
||||
quote! {
|
||||
#from::native_model_encode_downgrade_body(self.into(), version)
|
||||
}
|
||||
} else if let Some((try_from, error_try_from)) = native_model_try_from {
|
||||
quote! {
|
||||
let result = #try_from::native_model_encode_downgrade_body(
|
||||
self.try_into()
|
||||
.map_err(|e: #error_try_from| native_model::DowngradeError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})?,
|
||||
version,
|
||||
)?;
|
||||
Ok(result)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
Err(native_model::Error::DowngradeNotSupported {
|
||||
from: version,
|
||||
to: Self::native_model_version(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let gen = quote! {
|
||||
fn native_model_encode_downgrade_body(self, version: u32) -> native_model::Result<Vec<u8>> {
|
||||
if version == Self::native_model_version() {
|
||||
let result = self.native_model_encode_body()?;
|
||||
Ok(result)
|
||||
} else if version < Self::native_model_version() {
|
||||
#model_from_or_try_from
|
||||
} else {
|
||||
Err(native_model::Error::DowngradeNotSupported {
|
||||
from: version,
|
||||
to: Self::native_model_version(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
gen
|
||||
}
|
||||
|
||||
// #[error("Wrong type id expected: {}, actual: {}", expected, actual)]
|
||||
// WrongTypeId { expected: u32, actual: u32 },
|
||||
13
native_model_macro/src/method/id.rs
Normal file
13
native_model_macro/src/method/id.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
}
|
||||
};
|
||||
gen
|
||||
}
|
||||
13
native_model_macro/src/method/mod.rs
Normal file
13
native_model_macro/src/method/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
mod decode_body;
|
||||
mod decode_upgrade_body;
|
||||
mod encode_body;
|
||||
mod encode_downgrade_body;
|
||||
mod id;
|
||||
mod version;
|
||||
|
||||
pub(crate) use decode_body::*;
|
||||
pub(crate) use decode_upgrade_body::*;
|
||||
pub(crate) use encode_body::*;
|
||||
pub(crate) use encode_downgrade_body::*;
|
||||
pub(crate) use id::*;
|
||||
pub(crate) use version::*;
|
||||
13
native_model_macro/src/method/version.rs
Normal file
13
native_model_macro/src/method/version.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
}
|
||||
};
|
||||
gen
|
||||
}
|
||||
38
release.config.js
Normal file
38
release.config.js
Normal file
@@ -0,0 +1,38 @@
|
||||
module.exports = {
|
||||
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',
|
||||
],
|
||||
};
|
||||
21
renovate.json
Normal file
21
renovate.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$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
|
||||
}
|
||||
]
|
||||
}
|
||||
9
src/header.rs
Normal file
9
src/header.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use zerocopy::little_endian::U32;
|
||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
|
||||
#[derive(FromZeroes, FromBytes, AsBytes, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Header {
|
||||
pub(crate) type_id: U32,
|
||||
pub(crate) version: U32,
|
||||
}
|
||||
114
src/lib.rs
Normal file
114
src/lib.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
//! `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.
|
||||
//! - **Minimal Performance Overhead**: Current performance shows linearly increasing encoding overhead with data size, and constant decoding overhead (~162 picoseconds) for all data sizes.
|
||||
//! - **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. Examples and a `native_model` macro are provided for this purpose.
|
||||
//! - **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/vincent-herlemont/native_model) file.
|
||||
|
||||
mod header;
|
||||
mod model;
|
||||
pub mod wrapper;
|
||||
|
||||
pub use model::*;
|
||||
|
||||
/// 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 struct DecodeBodyError {
|
||||
pub msg: String,
|
||||
#[source]
|
||||
pub 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/vincent-herlemont/native_model) file.
|
||||
/// - other [examples](https://github.com/vincent-herlemont/native_model/tree/master/tests/example)
|
||||
pub fn encode<T: Model>(model: &T) -> Result<Vec<u8>> {
|
||||
T::native_model_encode(model)
|
||||
}
|
||||
|
||||
/// Allows to encode a [`native_model`] into a [`Vec<u8>`] with a specific version.
|
||||
/// See examples:
|
||||
/// - [README.md](https://github.com/vincent-herlemont/native_model) file.
|
||||
/// - other [examples](https://github.com/vincent-herlemont/native_model/tree/master/tests/example)
|
||||
pub fn encode_downgrade<T: Model>(model: T, version: u32) -> Result<Vec<u8>> {
|
||||
T::native_model_encode_downgrade(model, version)
|
||||
}
|
||||
|
||||
/// Allows to decode a [`native_model`] from a [`Vec<u8>`] and returns the version ([`u32`]).
|
||||
/// See examples:
|
||||
/// - [README.md](https://github.com/vincent-herlemont/native_model) file.
|
||||
/// - other [examples](https://github.com/vincent-herlemont/native_model/tree/master/tests/example)
|
||||
pub fn decode<T: Model>(data: Vec<u8>) -> Result<(T, u32)> {
|
||||
T::native_model_decode(data)
|
||||
}
|
||||
60
src/model.rs
Normal file
60
src/model.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::{DecodeResult, EncodeResult, Result};
|
||||
|
||||
pub trait Model: Sized {
|
||||
fn native_model_id() -> u32;
|
||||
fn native_model_version() -> u32;
|
||||
|
||||
// --------------- Decode ---------------
|
||||
|
||||
fn native_model_decode_body(data: Vec<u8>) -> DecodeResult<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn native_model_decode_upgrade_body(data: Vec<u8>, version: u32) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn native_model_decode(data: Vec<u8>) -> Result<(Self, u32)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let native_model = crate::Wrapper::deserialize(&data[..]).unwrap();
|
||||
let source_version = native_model.get_version();
|
||||
let result =
|
||||
Self::native_model_decode_upgrade_body(native_model.value().to_vec(), source_version)?;
|
||||
Ok((result, source_version))
|
||||
}
|
||||
|
||||
// --------------- Encode ---------------
|
||||
|
||||
fn native_model_encode_body(&self) -> EncodeResult<Vec<u8>>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn native_model_encode_downgrade_body(self, version: u32) -> Result<Vec<u8>>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn native_model_encode(&self) -> Result<Vec<u8>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut data = self.native_model_encode_body()?;
|
||||
crate::native_model_encode(
|
||||
&mut data,
|
||||
Self::native_model_id(),
|
||||
Self::native_model_version(),
|
||||
);
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn native_model_encode_downgrade(self, version: u32) -> Result<Vec<u8>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let version = version.clone();
|
||||
let mut data = self.native_model_encode_downgrade_body(version)?;
|
||||
crate::native_model_encode(&mut data, Self::native_model_id(), version);
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
86
src/wrapper.rs
Normal file
86
src/wrapper.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use crate::header::Header;
|
||||
use zerocopy::little_endian::U32;
|
||||
use zerocopy::{AsBytes, ByteSlice, ByteSliceMut, Ref};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Wrapper<T: ByteSlice> {
|
||||
header: Ref<T, Header>, // Deprecated: Rename LayoutVerified to Ref #203
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<T: ByteSlice> Wrapper<T> {
|
||||
pub fn deserialize(packed: T) -> Option<Self> {
|
||||
let (header_lv, rest) = Ref::<_, Header>::new_from_prefix(packed)?;
|
||||
let native_model = Self {
|
||||
header: header_lv,
|
||||
value: rest,
|
||||
};
|
||||
Some(native_model)
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
|
||||
pub fn get_type_id(&self) -> u32 {
|
||||
self.header.type_id.get()
|
||||
}
|
||||
|
||||
pub fn get_version(&self) -> u32 {
|
||||
self.header.version.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ByteSliceMut> Wrapper<T> {
|
||||
pub fn set_type_id(&mut self, type_id: u32) {
|
||||
self.header.type_id = U32::new(type_id);
|
||||
}
|
||||
|
||||
pub fn set_version(&mut self, version: u32) {
|
||||
self.header.version = U32::new(version);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn native_model_encode(value: &mut Vec<u8>, type_id: u32, version: u32) {
|
||||
let header = Header {
|
||||
type_id: U32::new(type_id),
|
||||
version: U32::new(version),
|
||||
};
|
||||
let header = header.as_bytes();
|
||||
value.reserve(header.len());
|
||||
value.splice(..0, header.iter().cloned());
|
||||
|
||||
// Try to do with unsafe code to improve performance but benchmark shows that it's the same
|
||||
//
|
||||
// // Add header to the beginning of the vector
|
||||
// unsafe {
|
||||
// // get the raw pointer to the vector's buffer
|
||||
// let ptr = value.as_mut_ptr();
|
||||
//
|
||||
// // move the existing elements to the right
|
||||
// ptr.offset(header.len() as isize)
|
||||
// .copy_from_nonoverlapping(ptr, value.len());
|
||||
//
|
||||
// // copy the elements from the header to the beginning of the vector
|
||||
// ptr.copy_from_nonoverlapping(header.as_ptr(), header.len());
|
||||
//
|
||||
// // update the length of the vector
|
||||
// value.set_len(value.len() + header.len());
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{native_model_encode, Wrapper};
|
||||
|
||||
#[test]
|
||||
fn native_model_deserialize_with_body() {
|
||||
let mut data = vec![0u8; 8];
|
||||
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);
|
||||
}
|
||||
}
|
||||
1
tests/_example.rs
Normal file
1
tests/_example.rs
Normal file
@@ -0,0 +1 @@
|
||||
mod example;
|
||||
344
tests/_experiment.rs
Normal file
344
tests/_experiment.rs
Normal file
@@ -0,0 +1,344 @@
|
||||
use bincode::{config, Decode, Encode};
|
||||
use native_model::Result;
|
||||
use native_model::{DecodeBodyError, DecodeResult, EncodeBodyError, EncodeResult, Model};
|
||||
|
||||
// Add this function to the macro for custom serialization
|
||||
fn native_model_encode<T: Encode>(obj: &T) -> anyhow::Result<Vec<u8>> {
|
||||
let result = bincode::encode_to_vec(obj, config::standard())?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Add this function to the macro for custom deserialization
|
||||
fn native_model_decode<T: Decode>(data: Vec<u8>) -> anyhow::Result<T> {
|
||||
let (result, _) =
|
||||
bincode::decode_from_slice(&data, config::standard()).map_err(|e| EncodeBodyError {
|
||||
msg: format!("Decode error: {}", e),
|
||||
source: e.into(),
|
||||
})?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
struct A {}
|
||||
impl Model for A {
|
||||
fn native_model_id() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn native_model_version() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn native_model_decode_upgrade_body(_data: Vec<u8>, x: u32) -> Result<Self> {
|
||||
println!(
|
||||
"A::deserialization_and_upgrade({}, {})",
|
||||
x,
|
||||
Self::native_model_version()
|
||||
);
|
||||
if x == Self::native_model_version() {
|
||||
Ok(Self {})
|
||||
} else if x < Self::native_model_version() {
|
||||
panic!("The version {} not supported", x);
|
||||
} else {
|
||||
panic!("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
fn native_model_encode_body(&self) -> EncodeResult<Vec<u8>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
native_model_encode(self).map_err(|e| EncodeBodyError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn native_model_decode_body(data: Vec<u8>) -> DecodeResult<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
native_model_decode(data).map_err(|e| DecodeBodyError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn native_model_encode_downgrade_body(self, version: u32) -> Result<Vec<u8>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
println!(
|
||||
"A::serialization_and_downgrade({}, {})",
|
||||
version,
|
||||
Self::native_model_version()
|
||||
);
|
||||
if version == Self::native_model_version() {
|
||||
let result = self.native_model_encode_body()?;
|
||||
Ok(result)
|
||||
} else if version < Self::native_model_version() {
|
||||
panic!("The version {} not supported", version);
|
||||
} else {
|
||||
panic!("Not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
struct B {}
|
||||
impl Model for B {
|
||||
fn native_model_id() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn native_model_version() -> u32 {
|
||||
2
|
||||
}
|
||||
|
||||
fn native_model_decode_upgrade_body(_data: Vec<u8>, x: u32) -> Result<Self> {
|
||||
println!(
|
||||
"B::deserialization_and_upgrade({}, {})",
|
||||
x,
|
||||
Self::native_model_version()
|
||||
);
|
||||
if x == Self::native_model_version() {
|
||||
Ok(Self {})
|
||||
} else if x < Self::native_model_version() {
|
||||
A::native_model_decode_upgrade_body(_data, x).map(|a| a.into())
|
||||
} else {
|
||||
panic!("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
fn native_model_encode_body(&self) -> EncodeResult<Vec<u8>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
native_model_encode(self).map_err(|e| EncodeBodyError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn native_model_decode_body(data: Vec<u8>) -> DecodeResult<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
native_model_decode(data).map_err(|e| DecodeBodyError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn native_model_encode_downgrade_body(self, version: u32) -> Result<Vec<u8>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
println!(
|
||||
"B::serialization_and_downgrade({}, {})",
|
||||
version,
|
||||
Self::native_model_version()
|
||||
);
|
||||
if version == Self::native_model_version() {
|
||||
let result = self.native_model_encode_body()?;
|
||||
Ok(result)
|
||||
} else if version < Self::native_model_version() {
|
||||
A::native_model_encode_downgrade_body(self.into(), version)
|
||||
} else {
|
||||
panic!("Not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<B> for A {
|
||||
fn from(_: B) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<A> for B {
|
||||
fn from(_: A) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
struct C {}
|
||||
impl Model for C {
|
||||
fn native_model_id() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn native_model_version() -> u32 {
|
||||
3
|
||||
}
|
||||
|
||||
fn native_model_decode_upgrade_body(_data: Vec<u8>, x: u32) -> Result<Self> {
|
||||
println!(
|
||||
"C::deserialization_and_upgrade({}, {})",
|
||||
x,
|
||||
Self::native_model_version()
|
||||
);
|
||||
if x == Self::native_model_version() {
|
||||
Ok(Self {})
|
||||
} else if x < Self::native_model_version() {
|
||||
let result = B::native_model_decode_upgrade_body(_data, x).map(|b| {
|
||||
b.try_into()
|
||||
.map_err(|e: anyhow::Error| native_model::UpgradeError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
})??;
|
||||
Ok(result)
|
||||
} else {
|
||||
panic!("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
fn native_model_encode_body(&self) -> EncodeResult<Vec<u8>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
native_model_encode(self).map_err(|e| EncodeBodyError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn native_model_decode_body(data: Vec<u8>) -> DecodeResult<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
native_model_decode(data).map_err(|e| DecodeBodyError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn native_model_encode_downgrade_body(self, version: u32) -> Result<Vec<u8>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
println!(
|
||||
"C::serialization_and_downgrade({}, {})",
|
||||
version,
|
||||
Self::native_model_version()
|
||||
);
|
||||
if version == Self::native_model_version() {
|
||||
let result = self.native_model_encode_body()?;
|
||||
Ok(result)
|
||||
} else if version < Self::native_model_version() {
|
||||
let result = B::native_model_encode_downgrade_body(
|
||||
self.try_into()
|
||||
.map_err(|e: anyhow::Error| native_model::DowngradeError {
|
||||
msg: format!("{}", e),
|
||||
source: e.into(),
|
||||
})?,
|
||||
version,
|
||||
)?;
|
||||
Ok(result)
|
||||
} else {
|
||||
panic!("Not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<C> for B {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(_: C) -> anyhow::Result<Self> {
|
||||
Ok(Self {})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<B> for C {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(_: B) -> anyhow::Result<Self> {
|
||||
Ok(Self {})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
I want to manage the upgrade and downgrade of native types using From and Into traits.
|
||||
Let see 3 model A,B,C of a model id 1.
|
||||
A is the oldest version of the model and is the version 1.
|
||||
B is the intermediate version of the model and is the version 2.
|
||||
C is the most recent version of the model and is the version 3.
|
||||
|
||||
We need to imagine that the data are serialized as a vector of bytes. The only things that we know
|
||||
is the model id 1 and the version of the model.
|
||||
|
||||
I need to found an elegant way to deserialize the data as the most recent version of the model.
|
||||
**/
|
||||
|
||||
#[test]
|
||||
fn test_encode_downgrade() {
|
||||
let x = 3;
|
||||
let result = C::native_model_encode_downgrade_body(C {}, x);
|
||||
dbg!(&result);
|
||||
|
||||
let x = 2;
|
||||
let result = C::native_model_encode_downgrade_body(C {}, x);
|
||||
dbg!(&result);
|
||||
|
||||
let x = 1;
|
||||
let result = C::native_model_encode_downgrade_body(C {}, x);
|
||||
dbg!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_upgrade() {
|
||||
let x = 3;
|
||||
let result = C::native_model_decode_upgrade_body(vec![], x);
|
||||
dbg!(&result);
|
||||
|
||||
let x = 2;
|
||||
let result = C::native_model_decode_upgrade_body(vec![], x);
|
||||
dbg!(&result);
|
||||
|
||||
let x = 1;
|
||||
let result = C::native_model_decode_upgrade_body(vec![], x);
|
||||
dbg!(&result);
|
||||
}
|
||||
|
||||
fn native_model_decode_upgrade<T>(
|
||||
_data: Vec<u8>,
|
||||
model_id: u32,
|
||||
version: u32,
|
||||
) -> native_model::Result<T>
|
||||
where
|
||||
T: Model,
|
||||
{
|
||||
if model_id == T::native_model_id() {
|
||||
T::native_model_decode_upgrade_body(_data, version)
|
||||
} else {
|
||||
panic!("The model id {} not supported", model_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_upgrade_c() {
|
||||
let x = 3;
|
||||
let result: C = native_model_decode_upgrade(vec![], 1, x).unwrap();
|
||||
dbg!(&result);
|
||||
|
||||
let x = 2;
|
||||
let result: C = native_model_decode_upgrade(vec![], 1, x).unwrap();
|
||||
dbg!(&result);
|
||||
|
||||
let x = 1;
|
||||
let result: C = native_model_decode_upgrade(vec![], 1, x).unwrap();
|
||||
dbg!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_upgrade_b() {
|
||||
let x = 2;
|
||||
let result: B = native_model_decode_upgrade(vec![], 1, x).unwrap();
|
||||
dbg!(&result);
|
||||
|
||||
// let x = 2;
|
||||
// let result: B = native_model_decode_upgrade(vec![], 1, x).unwrap();
|
||||
// dbg!(&result);
|
||||
}
|
||||
34
tests/example/encode_decode/bincode.rs
Normal file
34
tests/example/encode_decode/bincode.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use bincode;
|
||||
use bincode::{Decode, Encode};
|
||||
|
||||
fn native_model_encode_body<T: bincode::Encode>(
|
||||
model: &T,
|
||||
) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
{
|
||||
bincode::encode_to_vec(model, bincode::config::standard())
|
||||
}
|
||||
}
|
||||
|
||||
fn native_model_decode_body<T: bincode::Decode>(
|
||||
data: Vec<u8>,
|
||||
) -> Result<T, bincode::error::DecodeError> {
|
||||
{
|
||||
bincode::decode_from_slice(&data, bincode::config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
}
|
||||
|
||||
use native_model_macro::native_model;
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
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));
|
||||
}
|
||||
34
tests/example/encode_decode/bincode_serde.rs
Normal file
34
tests/example/encode_decode/bincode_serde.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use bincode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn native_model_encode_body<T: Serialize>(
|
||||
model: &T,
|
||||
) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
{
|
||||
bincode::serde::encode_to_vec(model, bincode::config::standard())
|
||||
}
|
||||
}
|
||||
|
||||
fn native_model_decode_body<T: for<'a> Deserialize<'a>>(
|
||||
data: Vec<u8>,
|
||||
) -> Result<T, bincode::error::DecodeError> {
|
||||
{
|
||||
Ok(bincode::serde::decode_from_slice(&data, bincode::config::standard())?.0)
|
||||
}
|
||||
}
|
||||
|
||||
use native_model_macro::native_model;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
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));
|
||||
}
|
||||
2
tests/example/encode_decode/mod.rs
Normal file
2
tests/example/encode_decode/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod bincode;
|
||||
mod bincode_serde;
|
||||
104
tests/example/example_define_model.rs
Normal file
104
tests/example/example_define_model.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use bincode::{config, Decode, Encode};
|
||||
use native_model_macro::native_model;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn native_model_encode_body<T: Encode>(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::encode_to_vec(obj, config::standard())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn native_model_decode_body<T: 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)]
|
||||
struct DotV1(u32, u32);
|
||||
|
||||
#[derive(Encode, Decode, 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)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 3, 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
|
||||
);
|
||||
}
|
||||
70
tests/example/example_main.rs
Normal file
70
tests/example/example_main.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use bincode;
|
||||
use bincode::{Decode, Encode};
|
||||
use native_model::native_model;
|
||||
|
||||
fn native_model_encode_body<T: bincode::Encode>(
|
||||
model: &T,
|
||||
) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
{
|
||||
bincode::encode_to_vec(model, bincode::config::standard())
|
||||
}
|
||||
}
|
||||
|
||||
fn native_model_decode_body<T: bincode::Decode>(
|
||||
data: Vec<u8>,
|
||||
) -> Result<T, bincode::error::DecodeError> {
|
||||
{
|
||||
bincode::decode_from_slice(&data, bincode::config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, PartialEq, Debug)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct DotV1(u32, u32);
|
||||
|
||||
#[derive(Encode, Decode, 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));
|
||||
}
|
||||
3
tests/example/mod.rs
Normal file
3
tests/example/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod encode_decode;
|
||||
mod example_define_model;
|
||||
mod example_main;
|
||||
46
tests/macro.rs
Normal file
46
tests/macro.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use bincode::{config, Decode, Encode};
|
||||
use native_model::Model;
|
||||
use native_model_macro::native_model;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn native_model_encode_body<T: Encode>(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::encode_to_vec(obj, config::standard())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn native_model_decode_body<T: Decode>(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {
|
||||
bincode::decode_from_slice(&data, config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Foo1 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
#[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() {
|
||||
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);
|
||||
}
|
||||
149
tests/macro_decode_decode_upgrade.rs
Normal file
149
tests/macro_decode_decode_upgrade.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use bincode::{config, Decode, Encode};
|
||||
use native_model::Model;
|
||||
use native_model_macro::native_model;
|
||||
|
||||
fn native_model_encode_body<T: Encode>(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::encode_to_vec(obj, config::standard())
|
||||
}
|
||||
|
||||
fn native_model_decode_body<T: Decode>(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {
|
||||
bincode::decode_from_slice(&data, config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode, PartialEq)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Foo1 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode, 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, Encode, Decode, 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).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, 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).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).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, 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, 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, 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, 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, 2);
|
||||
assert!(foo2_decoded.is_err());
|
||||
assert!(matches!(
|
||||
foo2_decoded.unwrap_err(),
|
||||
native_model::Error::UpgradeNotSupported { from: 2, to: 1 }
|
||||
));
|
||||
}
|
||||
48
tests/macro_encode_decode.rs
Normal file
48
tests/macro_encode_decode.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use bincode::{config, Decode, Encode};
|
||||
use native_model::Model;
|
||||
use native_model_macro::native_model;
|
||||
|
||||
fn native_model_encode_body<T: Encode>(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::encode_to_vec(obj, config::standard())
|
||||
}
|
||||
|
||||
fn native_model_decode_body<T: Decode>(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {
|
||||
bincode::decode_from_slice(&data, config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode, PartialEq)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Foo1 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode, 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);
|
||||
}
|
||||
102
tests/native_model_from.rs
Normal file
102
tests/native_model_from.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use bincode::{config, Decode, Encode};
|
||||
use native_model_macro::native_model;
|
||||
|
||||
fn native_model_encode_body<T: Encode>(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::encode_to_vec(obj, config::standard())
|
||||
}
|
||||
|
||||
fn native_model_decode_body<T: Decode>(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {
|
||||
bincode::decode_from_slice(&data, config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode, PartialEq)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Foo1 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode, 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, 200, 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, 200]);
|
||||
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, 200]);
|
||||
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, 200]);
|
||||
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);
|
||||
}
|
||||
74
tests/native_model_try_from.rs
Normal file
74
tests/native_model_try_from.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use bincode::{config, Decode, Encode};
|
||||
use native_model_macro::native_model;
|
||||
|
||||
fn native_model_encode_body<T: Encode>(obj: &T) -> Result<Vec<u8>, bincode::error::EncodeError> {
|
||||
bincode::encode_to_vec(obj, config::standard())
|
||||
}
|
||||
|
||||
fn native_model_decode_body<T: Decode>(data: Vec<u8>) -> Result<T, bincode::error::DecodeError> {
|
||||
bincode::decode_from_slice(&data, config::standard()).map(|(result, _)| result)
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode, PartialEq)]
|
||||
#[native_model(id = 1, version = 1)]
|
||||
struct Foo1 {
|
||||
x: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Decode, 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(_)
|
||||
));
|
||||
}
|
||||
1
tests/skeptic.rs
Normal file
1
tests/skeptic.rs
Normal file
@@ -0,0 +1 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/skeptic-tests.rs"));
|
||||
39
version_update.sh
Executable file
39
version_update.sh
Executable file
@@ -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