Expand description
§shapely
shapely provides runtime reflection for Rust.
Any type that implements Shapely
trait returns a Shape
, which describes:
- The memory layout of the type
- Its innards: struct fields, underlying type for newtypes, etc.
- How to drop it in place
The Partial
type is able to allocate (or work from a &mut MaybeUninit<T>
)
any Shapely type, and gradually initialize its fields — until the fully-built
value is moved out of the partial.
It comes with a derive macro that uses unsynn for speed of compilation.
§Ecosystem
The main shapely
crate re-exports symbols from:
- shapely-core, which defines the main
Shapely
trait and theShape
struct - shapely-derive, which implements the
Shapely
derive attribute as a fast/light proc macro powered by unsynn
shapely supports deserialization from multiple data formats through dedicated crates:
- shapely-json: JSON deserialization
- shapely-yaml: YAML deserialization
- shapely-msgpack: MessagePack deserialization
- shapely-urlencoded: URL-encoded form data deserialization
Additionally:
- shapely-pretty is able to pretty-print Shapely types.
- shapely-codegen is internal and generates some of the code of
shapely-core
§Implementing Your Own Deserializer
To implement a custom deserializer for a new format, you’ll need to work with the following key components from shapely:
§Key Types
Partial
: The central type for building shapely values incrementallyShape
: Describes the memory layout and structure of a typeInnards
: Represents the internal structure (Scalar, Struct, etc.)Scalar
: Represents primitive types like String, u64, etc.
§Implementation Pattern
- Create a function that takes a
&mut
Partial
and your format’s input (string, bytes, etc.) - Examine the shape of the partial using
Partial::shape
- Handle different shapes based on
Shape::innards
:- For
Innards::Scalar
, usePartial::scalar_slot
to get and fill the slot - For
Innards::Struct
, iterate through fields, usingPartial::slot_by_name
to access each field Innards::Map
andInnards::List
come with vtables, they have helper slots as well- Create nested
Partial
instances for complex fields and fill them recursively
- For
When in doubt, refer to the shapely-json
implementation — it’s the most featureful.
§Example Implementation Skeleton
use shapely_core::{Partial, Innards, Scalar, FieldError, Shape};
use std::convert::From;
#[derive(Debug)]
pub enum MyFormatError {
UnsupportedType,
UnsupportedShape,
FieldError(FieldError),
}
impl From<FieldError> for MyFormatError {
fn from(err: FieldError) -> Self {
MyFormatError::FieldError(err)
}
}
pub fn from_my_format(partial: &mut Partial, input: &[u8]) -> Result<(), MyFormatError> {
let shape_desc = partial.shape();
let shape = shape_desc.get();
match &shape.innards {
Innards::Scalar(scalar) => {
let slot = partial.scalar_slot().expect("Scalar slot");
// Parse scalar value from input and fill slot
match scalar {
Scalar::String => slot.fill("/* parsed string */".to_string()),
Scalar::U64 => slot.fill(0u64),
// Handle other scalar types
_ => return Err(MyFormatError::UnsupportedType),
}
},
Innards::Struct { .. } => {
// Parse struct fields from input
for (field_name, field_value) in [("field1", "value1"), ("field2", "value2")].iter() {
let slot = partial.slot_by_name(field_name)?;
// Create a partial for the field and fill it recursively
let mut partial_field = Partial::alloc(slot.shape());
// Recursively deserialize field_value into partial_field
// ...
slot.fill_from_partial(partial_field);
}
},
// Handle other shapes as needed
_ => return Err(MyFormatError::UnsupportedShape),
}
Ok(())
}
For more detailed examples, examine the implementation of existing deserializers like shapely-json or shapely-msgpack.
§Funding
Thanks to Namespace for providing fast GitHub Actions workers:
§License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Modules§
- mini_
typeid - vendored straight from https://github.com/dtolnay/typeid — which is dual-licensed under MIT and Apache-2.0.
Structs§
- Array
Slot - A helper struct to fill up arrays — note that it is designed for
Vec<T>
rather than fixed-size arrays or slices, so it’s a bit of a misnomer at the moment. - Bytes
- A wrapper around
Vec<u8>
for binary data - Field
- Describes a field in a struct or tuple
- Field
Flags - Flags that can be applied to fields to modify their behavior
- Hash
MapIter - An iterator over key-value pairs in a HashMap
- Hash
MapSlot - Provides insert, length check, and iteration over a type-erased hashmap
- Init
Set64 - A bit array to keep track of which fields were initialized, up to 64 fields
- ListV
Table - Virtual table for a list-like type (like
Vec<T>
, but alsoHashSet<T>
, etc.) - MapIterV
Table - VTable for an iterator over a map
- MapV
Table - Virtual table for a Map<String, T>
- Name
Opts - Options for formatting the name of a type
- Partial
- A partially-initialized shape.
- Shape
- Schema for reflection of a type
- Shape
Desc - A function that returns a shape. There should only be one of these per concrete type in a
- Slot
- Allows writing into a struct field.
- Variant
- Describes a variant of an enum
Enums§
- Enum
Repr - All possible representations for Rust enums
- Field
Error - Errors encountered when calling
field_by_index
orfield_by_name
- Init
Mark InitMark
is used to track the initialization state of a single field within anInitSet64
. It is part of a system used to progressively initialize structs, where each field’s initialization status is represented by a bit in a 64-bit set.- Innards
- The shape of a schema: is it more map-shaped, array-shaped, scalar-shaped?
- Origin
- Origin of the partial — did we allocate it? Or is it borrowed?
- Scalar
- A scalar type in Rust, representing a single value.
- Scalar
Contents - Represents the contents of a scalar value with a lifetime. This allows safe access to the actual values stored in memory.
- Variant
Error - All possible errors when getting a variant by index or by name
- Variant
Kind - Represents the different kinds of variants that can exist in a Rust enum
Traits§
- Shapely
- Allows querying the Shape of a type, which in turn lets us inspect any fields, build a value of this type progressively, etc.
Type Aliases§
- DropFn
- A function that drops a value at a specific memory address
- NameFn
- A function that formats the name of a type.
- SetTo
Default Fn - A function that sets a value to its default at a specific memory address
Derive Macros§
- Shapely
- Derive the [
shapely_core::Shapely
] trait for structs, tuple structs, and enums.