Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MaxSize as a const fn #179

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions source/postcard-schema/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ version = "0.33.0"
optional = true
default-features = false

[dev-dependencies.nalgebra_v0_33]
package = "nalgebra"
version = "0.33.0"
default-features = false
features = ["serde-serialize-no-std"]

[dependencies.postcard-derive]
path = "../postcard-derive"
version = "0.2.0"
Expand Down
192 changes: 192 additions & 0 deletions source/postcard-schema/src/const_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//! Const shims for operating on schema types

//////////////////////////////////////////////////////////////////////////////
// STAGE 0 - HELPERS
//////////////////////////////////////////////////////////////////////////////

use crate::schema::{DataModelType, DataModelVariant, NamedType, NamedValue, NamedVariant};

/// `is_prim` returns whether the type is a *primitive*, or a built-in type that
/// does not need to be sent over the wire.
pub const fn is_prim(dmt: &DataModelType) -> bool {
match dmt {
// These are all primitives
DataModelType::Bool => true,
DataModelType::I8 => true,
DataModelType::U8 => true,
DataModelType::I16 => true,
DataModelType::I32 => true,
DataModelType::I64 => true,
DataModelType::I128 => true,
DataModelType::U16 => true,
DataModelType::U32 => true,
DataModelType::U64 => true,
DataModelType::U128 => true,
DataModelType::Usize => true,
DataModelType::Isize => true,
DataModelType::F32 => true,
DataModelType::F64 => true,
DataModelType::Char => true,
DataModelType::String => true,
DataModelType::ByteArray => true,
DataModelType::Unit => true,
DataModelType::Schema => true,

// A unit-struct is always named, so it is not primitive, as the
// name has meaning even without a value
DataModelType::UnitStruct => false,
// Items with subtypes are composite, and therefore not primitives, as
// we need to convey this information.
DataModelType::Option(_) | DataModelType::NewtypeStruct(_) | DataModelType::Seq(_) => false,
DataModelType::Tuple(_) | DataModelType::TupleStruct(_) => false,
DataModelType::Map { .. } => false,
DataModelType::Struct(_) => false,
DataModelType::Enum(_) => false,
}
}

/// A const version of `<str as PartialEq>::eq`
pub const fn str_eq(a: &str, b: &str) -> bool {
let mut i = 0;
if a.len() != b.len() {
return false;
}
let a_by = a.as_bytes();
let b_by = b.as_bytes();
while i < a.len() {
if a_by[i] != b_by[i] {
return false;
}
i += 1;
}
true
}

/// A const version of `<NamedType as PartialEq>::eq`
pub const fn nty_eq(a: &NamedType, b: &NamedType) -> bool {
str_eq(a.name, b.name) && dmt_eq(a.ty, b.ty)
}

/// A const version of `<[&NamedType] as PartialEq>::eq`
pub const fn ntys_eq(a: &[&NamedType], b: &[&NamedType]) -> bool {
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if !nty_eq(a[i], b[i]) {
return false;
}
i += 1;
}
true
}

/// A const version of `<DataModelType as PartialEq>::eq`
pub const fn dmt_eq(a: &DataModelType, b: &DataModelType) -> bool {
match (a, b) {
// Data model types are ONLY matching if they are both the same variant
//
// For primitives (and unit structs), we only check the discriminant matches.
(DataModelType::Bool, DataModelType::Bool) => true,
(DataModelType::I8, DataModelType::I8) => true,
(DataModelType::U8, DataModelType::U8) => true,
(DataModelType::I16, DataModelType::I16) => true,
(DataModelType::I32, DataModelType::I32) => true,
(DataModelType::I64, DataModelType::I64) => true,
(DataModelType::I128, DataModelType::I128) => true,
(DataModelType::U16, DataModelType::U16) => true,
(DataModelType::U32, DataModelType::U32) => true,
(DataModelType::U64, DataModelType::U64) => true,
(DataModelType::U128, DataModelType::U128) => true,
(DataModelType::Usize, DataModelType::Usize) => true,
(DataModelType::Isize, DataModelType::Isize) => true,
(DataModelType::F32, DataModelType::F32) => true,
(DataModelType::F64, DataModelType::F64) => true,
(DataModelType::Char, DataModelType::Char) => true,
(DataModelType::String, DataModelType::String) => true,
(DataModelType::ByteArray, DataModelType::ByteArray) => true,
(DataModelType::Unit, DataModelType::Unit) => true,
(DataModelType::UnitStruct, DataModelType::UnitStruct) => true,
(DataModelType::Schema, DataModelType::Schema) => true,

// For non-primitive types, we check whether all children are equivalent as well.
(DataModelType::Option(nta), DataModelType::Option(ntb)) => nty_eq(nta, ntb),
(DataModelType::NewtypeStruct(nta), DataModelType::NewtypeStruct(ntb)) => nty_eq(nta, ntb),
(DataModelType::Seq(nta), DataModelType::Seq(ntb)) => nty_eq(nta, ntb),

(DataModelType::Tuple(ntsa), DataModelType::Tuple(ntsb)) => ntys_eq(ntsa, ntsb),
(DataModelType::TupleStruct(ntsa), DataModelType::TupleStruct(ntsb)) => ntys_eq(ntsa, ntsb),
(
DataModelType::Map {
key: keya,
val: vala,
},
DataModelType::Map {
key: keyb,
val: valb,
},
) => nty_eq(keya, keyb) && nty_eq(vala, valb),
(DataModelType::Struct(nvalsa), DataModelType::Struct(nvalsb)) => vals_eq(nvalsa, nvalsb),
(DataModelType::Enum(nvarsa), DataModelType::Enum(nvarsb)) => vars_eq(nvarsa, nvarsb),

// Any mismatches are not equal
_ => false,
}
}

/// A const version of `<NamedVariant as PartialEq>::eq`
pub const fn var_eq(a: &NamedVariant, b: &NamedVariant) -> bool {
str_eq(a.name, b.name) && dmv_eq(a.ty, b.ty)
}

/// A const version of `<&[&NamedVariant] as PartialEq>::eq`
pub const fn vars_eq(a: &[&NamedVariant], b: &[&NamedVariant]) -> bool {
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if !var_eq(a[i], b[i]) {
return false;
}
i += 1;
}
true
}

/// A const version of `<&[&NamedValue] as PartialEq>::eq`
pub const fn vals_eq(a: &[&NamedValue], b: &[&NamedValue]) -> bool {
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if !str_eq(a[i].name, b[i].name) {
return false;
}
if !nty_eq(a[i].ty, b[i].ty) {
return false;
}

i += 1;
}
true
}

/// A const version of `<DataModelVariant as PartialEq>::eq`
pub const fn dmv_eq(a: &DataModelVariant, b: &DataModelVariant) -> bool {
match (a, b) {
(DataModelVariant::UnitVariant, DataModelVariant::UnitVariant) => true,
(DataModelVariant::NewtypeVariant(nta), DataModelVariant::NewtypeVariant(ntb)) => {
nty_eq(nta, ntb)
}
(DataModelVariant::TupleVariant(ntsa), DataModelVariant::TupleVariant(ntsb)) => {
ntys_eq(ntsa, ntsb)
}
(DataModelVariant::StructVariant(nvarsa), DataModelVariant::StructVariant(nvarsb)) => {
vals_eq(nvarsa, nvarsb)
}
_ => false,
}
}
6 changes: 6 additions & 0 deletions source/postcard-schema/src/impls/chrono_v0_4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,10 @@ impl<Tz: chrono_v0_4::TimeZone> Schema for chrono_v0_4::DateTime<Tz> {
name: "DateTime",
ty: <&str>::SCHEMA.ty,
};
// TODO: Can we implement manual maxsize for this? The default serialization
// repr is RFC3339, which I think is bounded, but users can opt into alternative
// reprs using `serde_with`, see https://docs.rs/chrono/latest/chrono/serde/index.html
// for more details.
//
// A PR is welcome here, if someone needs to calculate max size for chrono types.
}
14 changes: 14 additions & 0 deletions source/postcard-schema/src/impls/heapless_v0_7.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Implementations of the [`Schema`] trait for the `heapless` crate v0.7

use crate::max_size::{bounded_seq_max, bounded_string_max};
use crate::{
schema::{DataModelType, NamedType},
Schema,
Expand All @@ -11,11 +12,24 @@ impl<T: Schema, const N: usize> Schema for heapless_v0_7::Vec<T, N> {
name: "heapless::Vec<T, N>",
ty: &DataModelType::Seq(T::SCHEMA),
};
const MANUAL_MAX_SIZE: Option<usize> = bounded_seq_max::<Self, T, N>();
}
#[cfg_attr(docsrs, doc(cfg(feature = "heapless-v0_7")))]
impl<const N: usize> Schema for heapless_v0_7::String<N> {
const SCHEMA: &'static NamedType = &NamedType {
name: "heapless::String<N>",
ty: &DataModelType::String,
};
const MANUAL_MAX_SIZE: Option<usize> = bounded_string_max::<N>();
}

#[cfg(test)]
mod test {
use crate::max_size::max_size;

#[test]
fn smoke() {
assert_eq!(max_size::<heapless_v0_7::Vec<u8, 128>>(), Some(130));
assert_eq!(max_size::<heapless_v0_7::String<128>>(), Some(130));
}
}
14 changes: 14 additions & 0 deletions source/postcard-schema/src/impls/heapless_v0_8.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Implementations of the [`Schema`] trait for the `heapless` crate v0.8

use crate::max_size::{bounded_seq_max, bounded_string_max};
use crate::{
schema::{DataModelType, NamedType},
Schema,
Expand All @@ -11,11 +12,24 @@ impl<T: Schema, const N: usize> Schema for heapless_v0_8::Vec<T, N> {
name: "heapless::Vec<T, N>",
ty: &DataModelType::Seq(T::SCHEMA),
};
const MANUAL_MAX_SIZE: Option<usize> = bounded_seq_max::<Self, T, N>();
}
#[cfg_attr(docsrs, doc(cfg(feature = "heapless-v0_8")))]
impl<const N: usize> Schema for heapless_v0_8::String<N> {
const SCHEMA: &'static NamedType = &NamedType {
name: "heapless::String<N>",
ty: &DataModelType::String,
};
const MANUAL_MAX_SIZE: Option<usize> = bounded_string_max::<N>();
}

#[cfg(test)]
mod test {
use crate::max_size::max_size;

#[test]
fn smoke() {
assert_eq!(max_size::<heapless_v0_8::Vec<u8, 128>>(), Some(130));
assert_eq!(max_size::<heapless_v0_8::String<128>>(), Some(130));
}
}
17 changes: 15 additions & 2 deletions source/postcard-schema/src/impls/nalgebra_v0_33.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Implementations of the [`Schema`] trait for the `nalgebra` crate v0.33

use crate::{
schema::{DataModelType, NamedType},
schema::NamedType,
Schema,
};

Expand All @@ -16,8 +16,21 @@ impl<T, const R: usize, const C: usize> Schema
where
T: Schema + nalgebra_v0_33::Scalar,
{
/// Warning!
const SCHEMA: &'static NamedType = &NamedType {
name: "nalgebra::Matrix<T, R, C, ArrayStorage<T, R, C>>",
ty: &DataModelType::Seq(T::SCHEMA),
// NOTE: This is not TECHNICALLY correct.
ty: <[[T; R]; C]>::SCHEMA.ty,
};
}

#[cfg(test)]
mod test {
use nalgebra_v0_33::Const;
#[test]
fn smoke() {
let x = nalgebra_v0_33::Matrix::<u8, Const<3>, Const<3>, _>::new(1, 2, 3, 4, 5, 6, 7, 8, 9);
let y = postcard::to_stdvec(&x).unwrap();
assert_eq!(&[1, 4, 7, 2, 5, 8, 3, 6, 9], y.as_slice());
}
}
23 changes: 23 additions & 0 deletions source/postcard-schema/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
//! # Postcard Schema

pub mod impls;
pub mod max_size;
pub mod schema;
pub mod const_helpers;

#[cfg(feature = "derive")]
pub use postcard_derive::Schema;
Expand All @@ -14,4 +16,25 @@ pub trait Schema {
/// A recursive data structure that describes the schema of the given
/// type.
const SCHEMA: &'static schema::NamedType;

/// A manually calculated maximum size hint.
///
/// This is useful in the case that a foreign type is expressed
/// in some form that has a technically unbounded max size, for
/// example a byte slice, but in reality it never sends more than
/// some calcuable size.
///
/// A good example is `heapless::Vec<T, N>`, which is expressed
/// on the wire as a `Seq(N)`, which is unbounded, however the
/// true max size is the length to store N as a varint plus
/// `N * max_size::<T>`, which is calcuable.
///
/// This value should not typically be used directly, instead use
/// [`max_size()`][crate::max_size::max_size()], which will EITHER
/// use the manual max size, or calculate the max size from the
/// schema.
///
/// You must not rely on this value for safety reasons, as implementations
/// could be wrong.
const MANUAL_MAX_SIZE: Option<usize> = None;
}
Loading
Loading