Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion serac/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "serac"
version = "0.2.0"
version = "0.2.1"
edition = "2024"
description = "A static, modular, and light-weight serialization framework."
license = "CC-BY-NC-SA-4.0"
Expand Down
80 changes: 80 additions & 0 deletions serac/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Serac

A static, modular, and light-weight serialization framework.

*Encoders* specify what kind of serialization medium they can target, and define the
serialization or deserialization process for that medium.

Serac comes with one built-in encoder *Vanilla*.

## Why not serde?

Serac's value proposition is its ability to perform static analysis on serialization
participants to either refine the failure space, or guarantee infallibility.

## Examples

### Serialize a number

```rust
use serac::{buf, SerializeIter};

let number = 0xdeadbeefu32;

let mut buf = buf!(u32); // [0; 4] in this case.
number.serialize_iter(&mut buf).unwrap();

let readback = SerializeIter::deserialize_iter(&buf).unwrap();
assert_eq(number, readback);
```
> The "Vanilla" encoding is used by default unless otherwise specified.

In the above example, there was an `unwrap` used on the result of `serialize_iter`
because the buffer *could* have been too short.

Using `SerializeBuf`, we can avoid this:

```rust
use serac::{buf, SerializeBuf};

let number = 0xdeadbeefu32;

let mut buf = buf!(u32);
number.serialize_buf(&mut buf); // it is statically known that "u32" fits in "[u8; 4]"

// it is *not* statically known that all values of "[u8; 4]" produce a valid "u32",
// and only that failure mode is expressed
let readback = SerializeBuf::deserialize_buf(&buf).unwrap();
assert_eq(number, readback);
```

Many built in types like numbers, tuples, and arrays implement both `SerializeIter`
and `SerializeBuf`.

### Serialize a custom type

```rust
use serac::{buf, encoding::vanilla, SerializeBuf};

const BE: u8 = 0xbe;

#[repr(u8)]
#[derive(Debug, PartialEq, vanilla::SerializeIter, vanilla::SerializeBuf)]
enum Foo {
A,
B(u8, i16) = 0xde,
C,
D { bar: u16, t: i8 } = BE,
}

let foo = Foo::D { bar: 0xaa, t: -1 };

let mut buf = buf!(Foo);
foo.serialize_buf(&mut buf);

let readback = SerializeBuf::deserialize_buf(&buf).unwrap();
assert_eq(foo, readback);
```

This example shows a crazy enum with lots of fancy things going on, which is able
to be serialized by serac.
2 changes: 1 addition & 1 deletion serac/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ pub trait Encoding {
/// i.e. `u8` for `[u8; ...]` mediums.
type Word;

/// The serialized form converted to and from by this encoding scheme.
/// The serialized form targeted by this encoding scheme.
type Serialized<const SIZE: usize>;
}
38 changes: 20 additions & 18 deletions serac/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,28 @@ pub mod error {
use crate as serac;
use serac::encoding::vanilla;

/// The encoder reached the end of the input before serialization/deserialization
/// was complete.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EndOfInput;

/// The contents of the serialization medium produced an invalid deserialized
/// value.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Invalid;

/// Deserialization failed.
#[repr(u8)]
#[derive(Debug, Clone, Copy, vanilla::SerializeIter, vanilla::SerializeBuf)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// The encoder reached the end of the input before deserialization was
/// complete.
EndOfInput,
/// The contents of the serialization medium produced an invalid deserialized
/// value.
Invalid,
}

Expand All @@ -42,45 +51,38 @@ pub mod error {
}
}

/// This trait defines a highly adaptable interface
/// for serializing and deserializing types to and
/// from a serialization medium via iterators.
/// This trait defines a highly adaptable interface for serializing and deserializing
/// types to and from a serialization medium via iterators.
pub trait SerializeIter<E: Encoding = Vanilla>: Sized {
/// Serialize the implementer type to a
/// serialization medium via an iterator.
/// Serialize the implementer type to a serialization medium via an iterator.
fn serialize_iter<'a>(
&self,
dst: impl IntoIterator<Item = &'a mut E::Word>,
) -> Result<usize, error::EndOfInput>
where
E::Word: 'a;

/// Deserialize the implementer type from a
/// serialization medium via an iterator.
/// Deserialize the implementer type from a serialization medium via an iterator.
fn deserialize_iter<'a>(
src: impl IntoIterator<Item = &'a E::Word>,
) -> Result<Self, error::Error>
where
E::Word: 'a;
}

/// This trait defines a more rigid/static serialization
/// interface.
/// This trait defines a more rigid/static serialization interface.
///
/// Types that implement this trait can be serialized to
/// and from buffers with an exact length. This length
/// being the maximum needed for any value of the
/// Types that implement this trait can be serialized to and from buffers with an
/// exact length. This length being the minimum needed for any value of the
/// implementer type.
///
/// To implement this trait, the type must already implement
/// `SerializeIter` and the implementer must compute
/// the necessary length of the serialization medium.
/// To implement this trait, the type must already implement `SerializeIter` and the
/// implementer must compute the necessary length of the serialization medium.
///
/// # Safety
///
/// The length of the associated `Serialized` type is critical.
/// An insufficient length *will* result in UB. Best to leave
/// this implementation to the procedural macro.
/// The value of the associated `SIZE` constant is critical. An insufficient size
/// *will* result in UB. Best to leave this implementation to the procedural macro.
pub unsafe trait SerializeBuf<E: Encoding = Vanilla>: SerializeIter<E> {
/// The size of the implementor when serialized, according to the encoding
/// scheme.
Expand Down
8 changes: 2 additions & 6 deletions serac/src/medium.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
use crate::encoding::{vanilla::Vanilla, Encoding};
use crate::encoding::{Encoding, vanilla::Vanilla};

// TODO: iters should be associated types defined
// by implementors

/// Types implement this trait to be used
/// as serialization mediums.
/// Types implement this trait to be used as serialization mediums.
pub trait Medium<E: Encoding = Vanilla> {
fn default() -> Self;
}
Loading