#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]
use std::collections::BTreeMap;
#[cfg(feature = "derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub use serde_shape_derive::SerdeShape;
pub trait SerdeShape {
fn shape_in(context: &mut ShapeContext) -> ShapeRef;
fn shape() -> Shape
where
Self: Sized,
{
Shape::for_type::<Self>()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Shape {
pub root: ShapeRef,
pub definitions: Vec<DefinitionShape>,
}
impl Shape {
pub fn for_type<T>() -> Self
where
T: SerdeShape + ?Sized,
{
let mut context = ShapeContext::default();
let root = T::shape_in(&mut context);
Self {
root,
definitions: context.finish(),
}
}
pub fn definition(&self, id: ShapeId) -> Option<&DefinitionShape> {
self.definitions.get(id.0)
}
}
#[derive(Debug, Default)]
pub struct ShapeContext {
definitions: Vec<Option<DefinitionShape>>,
definitions_by_rust_name: BTreeMap<&'static str, ShapeId>,
}
impl ShapeContext {
pub fn define_named_type<F>(&mut self, type_name: TypeName, build: F) -> ShapeRef
where
F: FnOnce(&mut Self) -> DefinitionKind,
{
if let Some(id) = self.definitions_by_rust_name.get(type_name.rust_name) {
return ShapeRef::Definition(*id);
}
let id = ShapeId(self.definitions.len());
self.definitions_by_rust_name
.insert(type_name.rust_name, id);
self.definitions.push(None);
let kind = build(self);
self.definitions[id.0] = Some(DefinitionShape {
id,
type_name,
kind,
});
ShapeRef::Definition(id)
}
fn finish(self) -> Vec<DefinitionShape> {
self.definitions
.into_iter()
.map(|definition| definition.expect("shape definition was reserved but not filled"))
.collect()
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ShapeId(pub usize);
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TypeName {
pub rust_name: &'static str,
pub serde_name: &'static str,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ShapeRef {
Unit,
Bool,
Char,
I8,
I16,
I32,
I64,
I128,
Isize,
U8,
U16,
U32,
U64,
U128,
Usize,
F32,
F64,
String,
Bytes,
Option(Box<ShapeRef>),
Seq(Box<ShapeRef>),
Array {
item: Box<ShapeRef>,
len: usize,
},
Map {
key: Box<ShapeRef>,
value: Box<ShapeRef>,
},
Tuple(Vec<ShapeRef>),
Definition(ShapeId),
Opaque(OpaqueShape),
}
impl ShapeRef {
pub fn is_signed_integer(&self) -> bool {
matches!(
self,
Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128 | Self::Isize
)
}
pub fn is_unsigned_integer(&self) -> bool {
matches!(
self,
Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::U128 | Self::Usize
)
}
pub fn is_integer(&self) -> bool {
self.is_signed_integer() || self.is_unsigned_integer()
}
pub fn is_float(&self) -> bool {
matches!(self, Self::F32 | Self::F64)
}
pub fn is_number(&self) -> bool {
self.is_integer() || self.is_float()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DefinitionShape {
pub id: ShapeId,
pub type_name: TypeName,
pub kind: DefinitionKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DefinitionKind {
Struct(StructShape),
Enum(EnumShape),
Opaque(OpaqueShape),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContainerAttributes {
pub tagging: Tagging,
pub deny_unknown_fields: bool,
pub default: DefaultShape,
pub has_flatten: bool,
pub transparent: bool,
pub expecting: Option<&'static str>,
pub non_exhaustive: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Tagging {
External,
Internal {
tag: &'static str,
},
Adjacent {
tag: &'static str,
content: &'static str,
},
Untagged,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StructShape {
pub style: FieldsStyle,
pub fields: Vec<FieldShape>,
pub attributes: ContainerAttributes,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EnumShape {
pub repr: Tagging,
pub variants: Vec<VariantShape>,
pub attributes: ContainerAttributes,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FieldsStyle {
Struct,
Tuple,
Newtype,
Unit,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FieldShape {
pub member: FieldMember,
pub deserialize_name: &'static str,
pub deserialize_aliases: Vec<&'static str>,
pub shape: Option<ShapeRef>,
pub default: DefaultShape,
pub flatten: bool,
pub skip_deserializing: bool,
pub custom_deserializer: bool,
pub transparent: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FieldMember {
Named(&'static str),
Unnamed(usize),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VariantShape {
pub rust_name: &'static str,
pub deserialize_name: &'static str,
pub deserialize_aliases: Vec<&'static str>,
pub style: FieldsStyle,
pub fields: Vec<FieldShape>,
pub skip_deserializing: bool,
pub custom_deserializer: bool,
pub other: bool,
pub untagged: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DefaultShape {
None,
Default,
Path(&'static str),
}
impl DefaultShape {
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OpaqueShape {
pub type_name: &'static str,
pub reason: OpaqueReason,
pub detail: Option<&'static str>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OpaqueReason {
FromType,
TryFromType,
Remote,
CustomDeserializer,
Unsupported,
}
macro_rules! primitive_shape {
($($ty:ty => $shape:expr;)+) => {
$(
impl SerdeShape for $ty {
fn shape_in(_context: &mut ShapeContext) -> ShapeRef {
$shape
}
}
)+
};
}
primitive_shape! {
() => ShapeRef::Unit;
bool => ShapeRef::Bool;
char => ShapeRef::Char;
i8 => ShapeRef::I8;
i16 => ShapeRef::I16;
i32 => ShapeRef::I32;
i64 => ShapeRef::I64;
i128 => ShapeRef::I128;
isize => ShapeRef::Isize;
u8 => ShapeRef::U8;
u16 => ShapeRef::U16;
u32 => ShapeRef::U32;
u64 => ShapeRef::U64;
u128 => ShapeRef::U128;
usize => ShapeRef::Usize;
f32 => ShapeRef::F32;
f64 => ShapeRef::F64;
str => ShapeRef::String;
String => ShapeRef::String;
std::path::Path => ShapeRef::String;
std::path::PathBuf => ShapeRef::String;
std::net::IpAddr => ShapeRef::String;
std::net::Ipv4Addr => ShapeRef::String;
std::net::Ipv6Addr => ShapeRef::String;
std::net::SocketAddr => ShapeRef::String;
std::net::SocketAddrV4 => ShapeRef::String;
std::net::SocketAddrV6 => ShapeRef::String;
std::num::NonZeroI8 => ShapeRef::I8;
std::num::NonZeroI16 => ShapeRef::I16;
std::num::NonZeroI32 => ShapeRef::I32;
std::num::NonZeroI64 => ShapeRef::I64;
std::num::NonZeroI128 => ShapeRef::I128;
std::num::NonZeroIsize => ShapeRef::Isize;
std::num::NonZeroU8 => ShapeRef::U8;
std::num::NonZeroU16 => ShapeRef::U16;
std::num::NonZeroU32 => ShapeRef::U32;
std::num::NonZeroU64 => ShapeRef::U64;
std::num::NonZeroU128 => ShapeRef::U128;
std::num::NonZeroUsize => ShapeRef::Usize;
}
#[cfg(target_has_atomic = "8")]
primitive_shape! {
std::sync::atomic::AtomicBool => ShapeRef::Bool;
std::sync::atomic::AtomicI8 => ShapeRef::I8;
std::sync::atomic::AtomicU8 => ShapeRef::U8;
}
#[cfg(target_has_atomic = "16")]
primitive_shape! {
std::sync::atomic::AtomicI16 => ShapeRef::I16;
std::sync::atomic::AtomicU16 => ShapeRef::U16;
}
#[cfg(target_has_atomic = "32")]
primitive_shape! {
std::sync::atomic::AtomicI32 => ShapeRef::I32;
std::sync::atomic::AtomicU32 => ShapeRef::U32;
}
#[cfg(target_has_atomic = "64")]
primitive_shape! {
std::sync::atomic::AtomicI64 => ShapeRef::I64;
std::sync::atomic::AtomicU64 => ShapeRef::U64;
}
#[cfg(target_has_atomic = "ptr")]
primitive_shape! {
std::sync::atomic::AtomicIsize => ShapeRef::Isize;
std::sync::atomic::AtomicUsize => ShapeRef::Usize;
}
impl SerdeShape for [u8] {
fn shape_in(_context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Bytes
}
}
impl<T> SerdeShape for &T
where
T: SerdeShape + ?Sized,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
T::shape_in(context)
}
}
impl<T> SerdeShape for &mut T
where
T: SerdeShape + ?Sized,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
T::shape_in(context)
}
}
impl<T> SerdeShape for Box<T>
where
T: SerdeShape + ?Sized,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
T::shape_in(context)
}
}
impl<'a, T> SerdeShape for std::borrow::Cow<'a, T>
where
T: ToOwned + ?Sized,
T::Owned: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
T::Owned::shape_in(context)
}
}
impl<T> SerdeShape for std::cell::Cell<T>
where
T: Copy + SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
T::shape_in(context)
}
}
impl<T> SerdeShape for std::cell::RefCell<T>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
T::shape_in(context)
}
}
impl<T> SerdeShape for std::sync::Mutex<T>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
T::shape_in(context)
}
}
impl<T> SerdeShape for std::sync::RwLock<T>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
T::shape_in(context)
}
}
impl<T> SerdeShape for std::num::Wrapping<T>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
T::shape_in(context)
}
}
impl<T> SerdeShape for std::cmp::Reverse<T>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
T::shape_in(context)
}
}
impl<T> SerdeShape for Option<T>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Option(Box::new(T::shape_in(context)))
}
}
impl<T> SerdeShape for Vec<T>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Seq(Box::new(T::shape_in(context)))
}
}
impl<T> SerdeShape for std::collections::VecDeque<T>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Seq(Box::new(T::shape_in(context)))
}
}
impl<T> SerdeShape for std::collections::LinkedList<T>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Seq(Box::new(T::shape_in(context)))
}
}
impl<T> SerdeShape for std::collections::BinaryHeap<T>
where
T: Ord + SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Seq(Box::new(T::shape_in(context)))
}
}
impl<T, const N: usize> SerdeShape for [T; N]
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Array {
item: Box::new(T::shape_in(context)),
len: N,
}
}
}
impl<K, V> SerdeShape for BTreeMap<K, V>
where
K: SerdeShape,
V: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Map {
key: Box::new(K::shape_in(context)),
value: Box::new(V::shape_in(context)),
}
}
}
impl<K, V, S> SerdeShape for std::collections::HashMap<K, V, S>
where
K: SerdeShape,
V: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Map {
key: Box::new(K::shape_in(context)),
value: Box::new(V::shape_in(context)),
}
}
}
impl<T> SerdeShape for std::collections::BTreeSet<T>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Seq(Box::new(T::shape_in(context)))
}
}
impl<T, S> SerdeShape for std::collections::HashSet<T, S>
where
T: SerdeShape,
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Seq(Box::new(T::shape_in(context)))
}
}
impl<T> SerdeShape for std::marker::PhantomData<T> {
fn shape_in(_context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Unit
}
}
macro_rules! tuple_shape {
($($name:ident),+ $(,)?) => {
impl<$($name),+> SerdeShape for ($($name,)+)
where
$($name: SerdeShape,)+
{
fn shape_in(context: &mut ShapeContext) -> ShapeRef {
ShapeRef::Tuple(vec![$($name::shape_in(context),)+])
}
}
};
}
tuple_shape!(T0);
tuple_shape!(T0, T1);
tuple_shape!(T0, T1, T2);
tuple_shape!(T0, T1, T2, T3);
tuple_shape!(T0, T1, T2, T3, T4);
tuple_shape!(T0, T1, T2, T3, T4, T5);
tuple_shape!(T0, T1, T2, T3, T4, T5, T6);
tuple_shape!(T0, T1, T2, T3, T4, T5, T6, T7);
tuple_shape!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
tuple_shape!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
tuple_shape!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
tuple_shape!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builds_map_shape() {
let shape = Shape::for_type::<BTreeMap<String, Option<u16>>>();
assert_eq!(
shape.root,
ShapeRef::Map {
key: Box::new(ShapeRef::String),
value: Box::new(ShapeRef::Option(Box::new(ShapeRef::U16))),
}
);
assert!(shape.definitions.is_empty());
}
#[test]
fn maps_common_std_shapes() {
assert_eq!(Shape::for_type::<std::path::Path>().root, ShapeRef::String);
assert_eq!(
Shape::for_type::<std::path::PathBuf>().root,
ShapeRef::String
);
assert_eq!(
Shape::for_type::<std::borrow::Cow<'static, str>>().root,
ShapeRef::String
);
assert_eq!(Shape::for_type::<std::cell::Cell<u8>>().root, ShapeRef::U8);
assert_eq!(
Shape::for_type::<std::num::Wrapping<i16>>().root,
ShapeRef::I16
);
assert_eq!(
Shape::for_type::<std::cmp::Reverse<u32>>().root,
ShapeRef::U32
);
assert_eq!(
Shape::for_type::<std::collections::VecDeque<u8>>().root,
ShapeRef::Seq(Box::new(ShapeRef::U8))
);
assert_eq!(
Shape::for_type::<std::collections::LinkedList<i32>>().root,
ShapeRef::Seq(Box::new(ShapeRef::I32))
);
assert_eq!(
Shape::for_type::<std::collections::BinaryHeap<u16>>().root,
ShapeRef::Seq(Box::new(ShapeRef::U16))
);
}
#[test]
fn classifies_flat_numeric_shapes() {
assert!(ShapeRef::I8.is_signed_integer());
assert!(ShapeRef::Usize.is_unsigned_integer());
assert!(ShapeRef::I128.is_integer());
assert!(ShapeRef::U64.is_integer());
assert!(ShapeRef::F32.is_float());
assert!(ShapeRef::F64.is_number());
assert!(!ShapeRef::String.is_number());
}
#[cfg(target_has_atomic = "ptr")]
#[test]
fn maps_atomic_shapes() {
assert_eq!(
Shape::for_type::<std::sync::atomic::AtomicUsize>().root,
ShapeRef::Usize
);
}
}