Again lock 'SystemTimeDecodeError' behind the 'std' feature; Support custom error types when deriving 'Encode' and 'Decode';

This commit is contained in:
Gabriel Bjørnager Jensen 2025-04-03 12:16:52 +02:00
parent 869629fa01
commit 9520130eb3
16 changed files with 151 additions and 38 deletions

View file

@ -3,6 +3,11 @@
This is the changelog of [Oct](https://crates.io/crates/oct/).
See `README.md` for more information.
## 0.21.0
* Again lock `SystemTimeDecodeError` behind the `std` feature
* Support custom error types when deriving `Encode` and `Decode`
## 0.20.2
* Clean up procedural macros

View file

@ -9,7 +9,7 @@ members = ["oct", "oct-benchmarks", "oct-macros"]
resolver = "2"
[workspace.package]
version = "0.20.2"
version = "0.21.0"
authors = ["Gabriel Bjørnager Jensen"]
readme = "README.md"
repository = "https://mandelbrot.dk/bjoernager/oct/"

View file

@ -32,7 +32,7 @@ readme.workspace = true
repository.workspace = true
[dependencies]
oct = { path = "../oct", version = "0.20", features = ["proc-macro"]}
oct = { path = "../oct", version = "0.21", features = ["proc-macro"]}
bincode = "2.0"
rand = "0.9"

View file

@ -11,10 +11,10 @@ use crate::{Discriminants, Repr};
use core::iter;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{DataEnum, Fields};
use syn::{DataEnum, Fields, Type};
#[must_use]
pub fn decode_enum(data: DataEnum, repr: Repr) -> TokenStream {
pub fn decode_enum(data: DataEnum, repr: Repr, error: Type) -> TokenStream {
let discriminants: Vec<_> = Discriminants::new(&data.variants).collect();
let values = data
@ -26,7 +26,7 @@ pub fn decode_enum(data: DataEnum, repr: Repr) -> TokenStream {
let commands = iter::repeat_n(
quote! {
::oct::decode::Decode::decode(stream)
.map_err(::core::convert::Into::<::oct::error::GenericDecodeError>::into)
.map_err(::core::convert::Into::<#error>::into)
.map_err(::oct::error::EnumDecodeError::BadField)?
},
variant.fields.len(),
@ -49,7 +49,7 @@ pub fn decode_enum(data: DataEnum, repr: Repr) -> TokenStream {
});
quote! {
type Error = ::oct::error::EnumDecodeError<#repr, <#repr as ::oct::decode::Decode>::Error, ::oct::error::GenericDecodeError>;
type Error = ::oct::error::EnumDecodeError<#repr, <#repr as ::oct::decode::Decode>::Error, #error>;
#[inline]
fn decode(stream: &mut ::oct::decode::Input) -> ::core::result::Result<Self, Self::Error> {

View file

@ -8,10 +8,10 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{DataStruct, Fields};
use syn::{DataStruct, Fields, Type};
#[must_use]
pub fn decode_struct(data: DataStruct) -> TokenStream {
pub fn decode_struct(data: DataStruct, error: Type) -> TokenStream {
let commands = data
.fields
.iter()
@ -23,7 +23,7 @@ pub fn decode_struct(data: DataStruct) -> TokenStream {
quote! {
#slot {
::oct::decode::Decode::decode(input)
.map_err(::core::convert::Into::<::oct::error::GenericDecodeError>::into)?
.map_err(::core::convert::Into::<#error>::into)?
},
}
});
@ -35,7 +35,7 @@ pub fn decode_struct(data: DataStruct) -> TokenStream {
};
quote! {
type Error = ::oct::error::GenericDecodeError;
type Error = #error;
#[inline]
fn decode(input: &mut ::oct::decode::Input) -> ::core::result::Result<Self, Self::Error> {

View file

@ -10,10 +10,16 @@ use crate::{Discriminants, Repr};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{DataEnum, Fields, Ident, LitInt};
use syn::{
DataEnum,
Fields,
Ident,
LitInt,
Type,
};
#[must_use]
pub fn encode_enum(data: DataEnum, repr: Repr) -> TokenStream {
pub fn encode_enum(data: DataEnum, repr: Repr, error: Type) -> TokenStream {
let discriminants: Vec<LitInt> = Discriminants::new(&data.variants).collect();
let captures: Vec<Vec<Ident>> = data
@ -53,7 +59,7 @@ pub fn encode_enum(data: DataEnum, repr: Repr) -> TokenStream {
});
quote! {
type Error = ::oct::error::EnumEncodeError<<#repr as ::oct::encode::Encode>::Error, ::oct::error::GenericEncodeError>;
type Error = ::oct::error::EnumEncodeError<<#repr as ::oct::encode::Encode>::Error, #error>;
#[allow(unreachable_patterns)]
#[inline]
@ -66,7 +72,7 @@ pub fn encode_enum(data: DataEnum, repr: Repr) -> TokenStream {
#(
::oct::encode::Encode::encode(#captures, stream)
.map_err(::core::convert::Into::<::oct::error::GenericEncodeError>::into)
.map_err(::core::convert::Into::<#error>::into)
.map_err(::oct::error::EnumEncodeError::BadField)?;
)*
}

View file

@ -8,10 +8,10 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{DataStruct, Index};
use syn::{DataStruct, Index, Type};
#[must_use]
pub fn encode_struct(data: DataStruct) -> TokenStream {
pub fn encode_struct(data: DataStruct, error: Type) -> TokenStream {
let commands = data
.fields
.iter()
@ -28,12 +28,12 @@ pub fn encode_struct(data: DataStruct) -> TokenStream {
quote! {
::oct::encode::Encode::encode(&self.#name, stream)
.map_err(::core::convert::Into::<::oct::error::GenericEncodeError>::into)?;
.map_err(::core::convert::Into::<#error>::into)?;
}
});
quote! {
type Error = ::oct::error::GenericEncodeError;
type Error = #error;
#[inline]
fn encode(&self, stream: &mut ::oct::encode::Output) -> ::core::result::Result<(), Self::Error> {

View file

@ -15,7 +15,14 @@ extern crate self as oct_macros;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput};
use syn::{
parse_macro_input,
parse2,
Data,
DeriveInput,
Type,
};
use syn::parse::Parse;
mod derive_impl;
@ -25,19 +32,33 @@ mod repr;
use discriminants::Discriminants;
use repr::Repr;
#[proc_macro_derive(Decode)]
#[proc_macro_derive(Decode, attributes(oct))]
pub fn derive_decode(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let self_name = input.ident;
let mut error: Type = parse2(quote! { ::oct::error::GenericDecodeError }).unwrap();
for attr in &input.attrs {
if attr.meta.path().is_ident("oct") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("decode_error") {
error = Parse::parse(meta.value()?)?;
}
Ok(())
}).unwrap();
}
}
let body = match input.data {
Data::Struct(data) => derive_impl::decode_struct(data),
Data::Struct(data) => derive_impl::decode_struct(data, error),
Data::Enum(data) => {
let repr = Repr::get(&input.attrs).unwrap_or_default();
derive_impl::decode_enum(data, repr)
derive_impl::decode_enum(data, repr, error)
}
Data::Union(_) => panic!("untagged union `{self_name}` cannot derive `oct::decode::Decode`"),
@ -46,6 +67,7 @@ pub fn derive_decode(input: TokenStream) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let output = quote! {
#[automatically_derived]
impl #impl_generics ::oct::decode::Decode for #self_name #ty_generics
#where_clause
{
@ -56,19 +78,33 @@ pub fn derive_decode(input: TokenStream) -> TokenStream {
output.into()
}
#[proc_macro_derive(Encode)]
#[proc_macro_derive(Encode, attributes(oct))]
pub fn derive_encode(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let self_name = input.ident;
let mut error: Type = parse2(quote! { ::oct::error::GenericEncodeError }).unwrap();
for attr in &input.attrs {
if attr.meta.path().is_ident("oct") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("encode_error") {
error = Parse::parse(meta.value()?)?;
}
Ok(())
}).unwrap();
}
}
let body = match input.data {
Data::Struct(data) => derive_impl::encode_struct(data),
Data::Struct(data) => derive_impl::encode_struct(data, error),
Data::Enum(data) => {
let repr = Repr::get(&input.attrs).unwrap_or_default();
derive_impl::encode_enum(data, repr)
derive_impl::encode_enum(data, repr, error)
}
Data::Union(_) => panic!("untagged union `{self_name}` cannot derive `oct::encode::Encode`"),
@ -77,6 +113,7 @@ pub fn derive_encode(input: TokenStream) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let output = quote! {
#[automatically_derived]
impl #impl_generics ::oct::encode::Encode for #self_name #ty_generics
#where_clause
{
@ -108,6 +145,7 @@ pub fn derive_sized_encode(input: TokenStream) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let output = quote! {
#[automatically_derived]
impl #impl_generics ::oct::encode::SizedEncode for #self_name #ty_generics
#where_clause
{

View file

@ -26,7 +26,7 @@ categories.workspace = true
all-features = true
[dependencies]
oct-macros = { path = "../oct-macros", version = "0.20", optional = true}
oct-macros = { path = "../oct-macros", version = "0.21", optional = true}
[features]
default = ["alloc", "proc-macro", "std"]

View file

@ -15,7 +15,6 @@ use crate::error::{
CollectionDecodeError,
EnumDecodeError,
ItemDecodeError,
SystemTimeDecodeError,
};
use core::cell::{Cell, RefCell, UnsafeCell};
@ -62,6 +61,8 @@ use crate::oct::encode::SizedEncode;
#[cfg(feature = "std")]
use {
crate::error::SystemTimeDecodeError,
core::hash::{BuildHasher, Hash},
std::collections::{HashMap, HashSet},

View file

@ -176,6 +176,49 @@ fn test_decode_derive() {
}
}
#[test]
fn test_decode_derive_custom_error() {
#[derive(Debug, Eq, PartialEq)]
struct Error;
#[derive(Debug, Eq, PartialEq)]
struct Foo(pub u8);
impl Decode for Foo {
type Error = Error;
fn decode(input: &mut Input) -> Result<Self, Self::Error> {
let Ok(value) = u8::decode(input);
if value % 0x2 != 0x0 {
return Err(Error);
}
let this = Self(value);
Ok(this)
}
}
#[derive(Debug, Decode, Eq, PartialEq)]
#[oct(decode_error = Error)]
struct Bar(Foo);
test! {
Foo {
[0x00] => Ok(Foo(0x00)),
[0x02] => Ok(Foo(0x02)),
[0x80] => Ok(Foo(0x80)),
[0x7F] => Err(Error),
}
Bar {
[0x00] => Ok(Bar(Foo(0x00))),
[0xFE] => Ok(Bar(Foo(0xFE))),
[0xFF] => Err(Error),
}
}
}
#[test]
fn test_decode_oct_vec_long_len() {
let data = [

View file

@ -42,7 +42,13 @@ pub use input::Input;
/// Implements [`Decode`] for the provided type.
///
/// This macro assumes the same format used by the equivalent [`Encode`](derive@crate::encode::Encode) macro.
/// This derive macro assumes the same format used by the equivalent [`Encode`](derive@crate::encode::Encode) derive macro.
///
/// By default, this macro assumes that all fields implement <code>Decode&lt;[Error]: [Into]&lt;[GenericDecodeError]&gt;&gt;</code>.
/// If this is **not** the case, then the `#[oct(decode_error = T)` attribute should be used for the desired error `T` on the deriving type.
///
/// [Error]: Encode::Error
/// [GenericDecodeError]: crate::error::GenericDecodeError
#[cfg(feature = "proc-macro")]
#[cfg_attr(doc, doc(cfg(feature = "proc-macro")))]
#[doc(inline)]

View file

@ -58,13 +58,13 @@ pub use sized_encode::SizedEncode;
/// Implements [`Encode`] for the provided type.
///
/// This derive macro assumes that all fields implement <code>Encode&lt;[Error]: [Into]&lt;[GenericEncodeError]&gt;&gt;</code>.
/// If this is **not** the case, then the trait should be implemented manually instead.
/// This derive macro, by default, assumes that all fields implement <code>Encode&lt;[Error]: [Into]&lt;[GenericEncodeError]&gt;&gt;</code>.
/// If this is **not** the case, then the `#[oct(encode_error = T)` attribute should be used for the desired error `T` on the deriving type.
///
/// [Error]: Encode::Error
/// [GenericEncodeError]: crate::error::GenericEncodeError
///
/// Do also consider deriving [`SizedEncode`](derive@SizedEncode) -- if possible.
/// Do also consider deriving [`SizedEncode`](derive@SizedEncode) -- if appropriate.
///
/// # Structs
///

View file

@ -13,7 +13,6 @@ use crate::error::{
ItemDecodeError,
NonZeroDecodeError,
LengthError,
SystemTimeDecodeError,
Utf8Error,
};
@ -21,13 +20,16 @@ use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
#[cfg(feature = "std")]
use crate::error::SystemTimeDecodeError;
/// A generic decoding error type.
///
/// The intended use of this type is by [derived](derive@crate::decode::Decode) implementations of [`crate::decode::Decode`].
/// Manual implementors are recommended to use a custom or less generic type for the sake of efficiency.
#[must_use]
#[non_exhaustive]
#[derive(Debug, Eq, PartialEq)]
#[derive(Debug, PartialEq)]
pub enum GenericDecodeError {
/// A string contained a non-UTF-8 sequence.
BadString(Utf8Error),
@ -43,6 +45,8 @@ pub enum GenericDecodeError {
/// The contained value denotes the raw, numerical value of the discriminant.
UnassignedDiscriminant(PrimDiscriminant),
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
/// The [`SystemTime`](std::time::SystemTime) type was too narrow.
NarrowSystemTime(SystemTimeDecodeError),
}
@ -63,6 +67,8 @@ impl Display for GenericDecodeError {
Self::UnassignedDiscriminant(value)
=> write!(f, "discriminant value `{value:#X} has not been assigned"),
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
Self::NarrowSystemTime(ref e)
=> write!(f, "{e}"),
}
@ -79,6 +85,8 @@ impl Error for GenericDecodeError {
Self::SmallBuffer(ref e) => Some(e),
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
Self::NarrowSystemTime(ref e) => Some(e),
_ => None,
@ -91,7 +99,7 @@ where
L: Into<Self>,
I: Into<Self>,
{
#[inline(always)]
#[inline]
fn from(value: CollectionDecodeError<L, I>) -> Self {
use CollectionDecodeError as Error;
@ -109,7 +117,7 @@ where
D: Into<Self>,
F: Into<Self>,
{
#[inline(always)]
#[inline]
fn from(value: EnumDecodeError<T, D, F>) -> Self {
use EnumDecodeError as Error;
@ -151,6 +159,8 @@ impl From<LengthError> for GenericDecodeError {
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl From<SystemTimeDecodeError> for GenericDecodeError {
#[inline(always)]
fn from(value: SystemTimeDecodeError) -> Self {

View file

@ -17,7 +17,7 @@ use crate::error::{
use core::cell::BorrowError;
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
use core::fmt::{self, Debug, Display, Formatter};
/// A generic encoding error type.
///
@ -66,8 +66,6 @@ impl Error for GenericEncodeError {
}
}
impl Eq for GenericEncodeError { }
impl From<BorrowError> for GenericEncodeError {
#[inline(always)]
fn from(value: BorrowError) -> Self {

View file

@ -6,6 +6,8 @@
// can obtain one at:
// <https://mozilla.org/MPL/2.0/>.
#![cfg(feature = "std")]
use core::convert::Infallible;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
@ -14,6 +16,7 @@ use core::fmt::{self, Display, Formatter};
///
/// Note that a UNIX timestamp is here defined as a signed, 64-bit integer denoting a difference of time to 1 january 1970, as measured in Greenwich using seconds.
/// This error should therefore not occur on systems that use the same or a more precise counter.
#[cfg_attr(doc, doc(cfg(feature = "std")))]
#[derive(Debug, Eq, PartialEq)]
#[must_use]
pub struct SystemTimeDecodeError {
@ -21,6 +24,7 @@ pub struct SystemTimeDecodeError {
pub timestamp: i64,
}
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl Display for SystemTimeDecodeError {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
@ -28,8 +32,10 @@ impl Display for SystemTimeDecodeError {
}
}
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl Error for SystemTimeDecodeError { }
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl From<Infallible> for SystemTimeDecodeError {
#[inline(always)]
fn from(_value: Infallible) -> Self {