Skip to content

Commit

Permalink
Merge pull request #97 from driftluo/docs
Browse files Browse the repository at this point in the history
docs: add rust api doc
  • Loading branch information
driftluo authored Dec 13, 2024
2 parents d924511 + 71ecc45 commit a4c8e5a
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 37 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Another serialization system: minimalist and canonicalization.
- [Encoding Spec](docs/encoding_spec.md)
- [Schema Language](docs/schema_language.md)
- [Real-World Examples](docs/real_world_examples.md)
- [API](docs/molecule_api.md)

## Features
* `default` — Default features: `std`, utilizes `faster-hex` for hexadecimal operations and enables [bytes] standard features.
Expand Down
85 changes: 85 additions & 0 deletions docs/molecule_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
## Molecule API

Molecule is based on schema serialization. The serialization format generated by any language based on the same schema should be consistent. The most basic API for multiple languages ​​should include data verification, schema compatibility reading, serialization and deserialization, and obtaining raw data (excluding the molecule header).

### Rust API

In the basic implementation of rust, the generated code uses three traits to implement three different functions of a structure:

#### Entity
```rust
pub trait Entity: fmt::Debug + Default + Clone {
type Builder: Builder;
const NAME: &'static str;
fn new_unchecked(data: Bytes) -> Self;
fn as_bytes(&self) -> Bytes;
fn as_slice(&self) -> &[u8];
fn from_slice(slice: &[u8]) -> VerificationResult<Self>;
fn from_compatible_slice(slice: &[u8]) -> VerificationResult<Self>;
fn new_builder() -> Self::Builder;
fn as_builder(self) -> Self::Builder;
}
```
Entity corresponds to a serialized data structure. There are two APIs that are easily confused: `as_slice` defined in the trait and `raw_data` that comes with its own structure.

`as_slice`: Complete molecule format data
`raw_data`: Original data, i.e. without the molecule header

Molecule supports compatible reading of data. The so-called compatibility refers to a structure like table, which can dynamically add fields. The old schema can read the data generated by the schema after adding fields, but it does not support deleting fields.

```mol
vector Inner <byte>;
table Old {
a: Inner,
b: Inner,
}
table New {
a: Inner,
b: Inner,
c: Inner,
}
```

Like the scheme in the example above, `Old` can use the `from_compatible_slice` api to read `New's` data.At the same time, the structure will have APIs such as `count_extra_fields`/`has_extra_fields`/`has_extra_fields` to let users know that there is extra data in the read data. It is currently compatible with reading.

#### Reader
```rust
pub trait Reader<'r>: Sized + fmt::Debug + Clone + Copy {
type Entity: Entity;
const NAME: &'static str;
fn verify(slice: &[u8], compatible: bool) -> VerificationResult<()>;
fn new_unchecked(slice: &'r [u8]) -> Self;
fn as_slice(&self) -> &'r [u8];
fn from_slice(slice: &'r [u8]) -> VerificationResult<Self> {
Self::verify(slice, false).map(|_| Self::new_unchecked(slice))
}
fn from_compatible_slice(slice: &'r [u8]) -> VerificationResult<Self> {
Self::verify(slice, true).map(|_| Self::new_unchecked(slice))
}
fn to_entity(&self) -> Self::Entity;
}
```

Each structure will generate at least one corresponding `Reader` structure, which has the ability to obtain the field data inside the structure.


#### Builder

```rust
pub trait Builder: Default {
type Entity: Entity;
const NAME: &'static str;
fn expected_length(&self) -> usize;
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()>;
fn build(&self) -> Self::Entity;
}
```

Builder is the key to building and serializing the molecule structure. Use the builder mode to generate a builder structure, put all fields into it, and convert it into a serialized structure(Entity) through the `build` API.


#### Union

The union structure will have two more structures than other structures when generating code, corresponding to different internal data types, and ends with Union.
13 changes: 5 additions & 8 deletions examples/ci-tests/src/simple-example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,14 @@ fn display_test_data() {
let f0: types::Byte = 0x12u8.into();
let f2 = types::Byte3::new_builder().nth1(f0).build();
let f29 = types::StructB::new_builder()
.f2(0x34u8.into())
.f2(0x34u8)
.f4(f2.clone())
.build();
let f41 = {
let mut f41_builder = types::Bytes::new_builder()
.push(0x12.into())
.push(0x12.into())
.push(0x13.into());
assert_eq!(f41_builder.replace(1, 0x34.into()), Some(0x12.into()));
assert_eq!(f41_builder.replace(2, 0x56.into()), Some(0x13.into()));
assert_eq!(f41_builder.replace(3, 0x56.into()), None);
let mut f41_builder = types::Bytes::new_builder().push(0x12).push(0x12).push(0x13);
assert_eq!(f41_builder.replace(1, 0x34), Some(0x12.into()));
assert_eq!(f41_builder.replace(2, 0x56), Some(0x13.into()));
assert_eq!(f41_builder.replace(3, 0x56), None);
f41_builder.build()
};
let f43 = types::Byte3Vec::new_builder()
Expand Down
2 changes: 1 addition & 1 deletion examples/ci-tests/tests/proptest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fn arbitrary_table0() -> impl Strategy<Value = types::Table0> {
}

fn arbitrary_table1() -> impl Strategy<Value = types::Table1> {
any::<u8>().prop_map(|data| types::Table1::new_builder().f1(data.into()).build())
any::<u8>().prop_map(|data| types::Table1::new_builder().f1(data).build())
}

fn arbitrary_table5() -> impl Strategy<Value = types::Table5> {
Expand Down
31 changes: 19 additions & 12 deletions examples/ci-tests/tests/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ macro_rules! test_option_set_default {
}

macro_rules! test_vector_push_default {
($type:ident, $expected1:expr, $expected2:expr, $expected3:expr) => {
($type:ident, $item:ident, $expected1:expr, $expected2:expr, $expected3:expr) => {
let t = types::$type::default();
let t = test_vector_push_default!($type, t, $expected1);
let t = test_vector_push_default!($type, t, $expected2);
let _ = test_vector_push_default!($type, t, $expected3);
let t = test_vector_push_default!($type, $item, t, $expected1);
let t = test_vector_push_default!($type, $item, t, $expected2);
let _ = test_vector_push_default!($type, $item, t, $expected3);
};
($type:ident, $input:ident, $expected:expr) => {{
($type:ident, $item:ident, $input:ident, $expected:expr) => {{
let expected = $expected;
let builder = $input.as_builder().push(Default::default());
let builder = $input.as_builder().push(types::$item::default());
let result = builder.build();
assert_eq!(
result.as_slice(),
Expand Down Expand Up @@ -426,6 +426,7 @@ fn option_set_default() {
fn fixvec_push_default() {
test_vector_push_default!(
Bytes,
Byte,
s!("0x\
01000000\
00\
Expand All @@ -444,6 +445,7 @@ fn fixvec_push_default() {
);
test_vector_push_default!(
Words,
Word,
s!("0x\
01000000\
0000\
Expand All @@ -462,6 +464,7 @@ fn fixvec_push_default() {
);
test_vector_push_default!(
Byte3Vec,
Byte3,
s!("0x\
01000000\
000000\
Expand All @@ -480,6 +483,7 @@ fn fixvec_push_default() {
);
test_vector_push_default!(
Byte7Vec,
Byte7,
s!("0x\
01000000\
00000000_000000\
Expand All @@ -498,6 +502,7 @@ fn fixvec_push_default() {
);
test_vector_push_default!(
StructIVec,
StructI,
s!("0x\
01000000\
00000000\
Expand All @@ -516,6 +521,7 @@ fn fixvec_push_default() {
);
test_vector_push_default!(
StructJVec,
StructJ,
s!("0x\
01000000\
00000000_000000\
Expand All @@ -534,6 +540,7 @@ fn fixvec_push_default() {
);
test_vector_push_default!(
StructPVec,
StructP,
s!("0x\
01000000\
00000000_00000000\
Expand Down Expand Up @@ -581,8 +588,8 @@ fn dynvec_push_default() {
00000000\
00000000\
");
test_vector_push_default!(BytesVec, s1, s2, s3);
test_vector_push_default!(WordsVec, s1, s2, s3);
test_vector_push_default!(BytesVec, Bytes, s1, s2, s3);
test_vector_push_default!(WordsVec, Words, s1, s2, s3);
let s1 = s!("0x\
08000000\
\
Expand All @@ -601,8 +608,8 @@ fn dynvec_push_default() {
10000000\
10000000\
");
test_vector_push_default!(ByteOptVec, s1, s2, s3);
test_vector_push_default!(WordOptVec, s1, s2, s3);
test_vector_push_default!(WordsOptVec, s1, s2, s3);
test_vector_push_default!(BytesOptVec, s1, s2, s3);
test_vector_push_default!(ByteOptVec, ByteOpt, s1, s2, s3);
test_vector_push_default!(WordOptVec, WordOpt, s1, s2, s3);
test_vector_push_default!(WordsOptVec, WordsOpt, s1, s2, s3);
test_vector_push_default!(BytesOptVec, BytesOpt, s1, s2, s3);
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,12 @@ fn gen_from_iter(name: &str, item_name: &str) -> m4::TokenStream {
Self::new_builder().extend(iter.into_iter().map(Into::into)).build()
}
}

impl From<Vec<u8>> for #entity {
fn from(v: Vec<u8>) -> Self {
Self::new_builder().set(v.into_iter().map(Into::into).collect()).build()
}
}
)
} else {
quote!()
Expand All @@ -290,6 +296,13 @@ fn gen_from_iter(name: &str, item_name: &str) -> m4::TokenStream {
}
}

impl From<Vec<#item_name>> for #entity
{
fn from(v: Vec<#item_name>) -> Self {
Self::new_builder().set(v).build()
}
}

#maybe_byte_vec
)
}
Expand Down
49 changes: 33 additions & 16 deletions tools/codegen/src/generator/languages/rust/builder/setters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ impl ImplSetters for ast::Option_ {
fn impl_setters(&self) -> m4::TokenStream {
let inner = entity_name(self.item().typ().name());
quote!(
pub fn set(mut self, v: Option<#inner>) -> Self {
self.0 = v;
pub fn set<T>(mut self, v: T) -> Self
where
T: ::core::convert::Into<Option<#inner>>
{
self.0 = v.into();
self
}
)
Expand All @@ -40,8 +43,11 @@ impl ImplSetters for ast::Array {
let inner = entity_name(self.item().typ().name());
let item_count = usize_lit(self.item_count());
let entire_setter = quote!(
pub fn set(mut self, v: [#inner; #item_count]) -> Self {
self.0 = v;
pub fn set<T>(mut self, v: T) -> Self
where
T: ::core::convert::Into<[#inner; #item_count]>
{
self.0 = v.into();
self
}
);
Expand All @@ -50,8 +56,11 @@ impl ImplSetters for ast::Array {
let index = usize_lit(idx);
let func = func_name(&format!("nth{}", idx));
quote!(
pub fn #func(mut self, v: #inner) -> Self {
self.0[#index] = v;
pub fn #func<T>(mut self, v: T) -> Self
where
T: ::core::convert::Into<#inner>
{
self.0[#index] = v.into();
self
}
)
Expand Down Expand Up @@ -95,8 +104,11 @@ fn impl_setters_for_struct_or_table(inner: &[ast::FieldDecl]) -> m4::TokenStream
let field_name = field_name(f.name());
let field_type = entity_name(f.typ().name());
quote!(
pub fn #field_name(mut self, v: #field_type) -> Self {
self.#field_name = v;
pub fn #field_name<T>(mut self, v: T) -> Self
where
T: ::core::convert::Into<#field_type>
{
self.#field_name = v.into();
self
}
)
Expand All @@ -110,22 +122,27 @@ fn impl_setters_for_struct_or_table(inner: &[ast::FieldDecl]) -> m4::TokenStream
fn impl_setters_for_vector(inner_name: &str) -> m4::TokenStream {
let inner = entity_name(inner_name);
quote!(
pub fn set(mut self, v: Vec<#inner>) -> Self {
pub fn set(mut self, v: Vec<#inner>) -> Self
{
self.0 = v;
self
}
pub fn push(mut self, v: #inner) -> Self {
self.0.push(v);
pub fn push<T>(mut self, v: T) -> Self
where
T: ::core::convert::Into<#inner>
{
self.0.push(v.into());
self
}
pub fn extend<T: ::core::iter::IntoIterator<Item=#inner>>(mut self, iter: T) -> Self {
for elem in iter {
self.0.push(elem);
}
self.0.extend(iter);
self
}
pub fn replace(&mut self, index: usize, v: #inner) -> Option<#inner> {
self.0.get_mut(index).map(|item| ::core::mem::replace(item, v))
pub fn replace<T>(&mut self, index: usize, v: T) -> Option<#inner>
where
T: ::core::convert::Into<#inner>
{
self.0.get_mut(index).map(|item| ::core::mem::replace(item, v.into()))
}
)
}

0 comments on commit a4c8e5a

Please sign in to comment.