Skip to content

Commit

Permalink
Add support for path to error for serializers
Browse files Browse the repository at this point in the history
  • Loading branch information
melody-rs committed May 31, 2024
1 parent d8ef1f7 commit e1f3515
Show file tree
Hide file tree
Showing 11 changed files with 806 additions and 133 deletions.
2 changes: 1 addition & 1 deletion alox-48-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "alox-48-derive"
version = "0.3.0"
version = "0.3.1"
edition = "2021"
authors = ["Melody Madeline Lyons"]
repository = "https://github.com/Speak2Erase/alox-48"
Expand Down
35 changes: 35 additions & 0 deletions alox-48-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,48 @@ struct VariantReciever {
class: Option<String>,
}

/// Derive `Deserialize` for a struct.
///
/// Does not currently support enums.
///
/// Type attributes:
/// - `alox_crate_path`: The path to the alox-48 crate.
/// - `class`: Override the class that the class enforcer checks for. By default, the class of structs is the struct name.
/// - `deny_unknown_fields`: If set, the deserializer will error if it encounters a field not in the struct.
/// - `enforce_class`: If set, the deserializer will enforce that the class matches.
/// - `default`: The default function to use for a field. Leave empty to use `Default::default`.
/// - `from`: Deserialize from a different type. That type must implement `Deserialize`.
/// - `try_from`: Deserialize from a different type. That type must implement `TryFrom`, and its error type must implement `Display`.
/// - `expecting`: The error message to use if deserialization fails.
///
/// Field attributes:
/// - `rename`: Rename the field.
/// - `default`: The default function to use for a field. Leave empty to use `Default::default`.
/// - `skip` or `skip_deserializing`: Skip deserializing the field.
/// - `deserialize_with`: Use a custom function to deserialize the field. That function must have the signature `fn(impl Deserializer<'de>) -> Result<T, DeError>`.
/// - `with`: Like `deserialize_with`, but the function is in a module.
#[proc_macro_derive(Deserialize, attributes(marshal))]
pub fn derive_deserialize(item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as DeriveInput);

de::derive_inner(&input).into()
}

/// Derive `Serialize` for a struct.
///
/// Does not currently support enums.
///
/// Type attributes:
/// - `alox_crate_path`: The path to the alox-48 crate.
/// - `class`: Override the class that this type is serialized as. By default, the class is the struct name.
/// - `into`: Serialize to a different type. That type must implement `Serialize`, and `Self` must impl `Into<T> + Clone`.
/// - `try_into`: Serialize to a different type. That type must implement `Serialize`, and Self must impl `TryInto<T> + Clone`.
///
/// Field attributes:
/// - `rename`: Rename the field.
/// - `skip` or `skip_serializing`: Skip serializing the field.
/// - `serialize_with`: Use a custom function to serialize the field. That function must have the signature `fn(&T, impl Serializer) -> Result<S::Ok, SerError>`.
/// - `with`: Like `serialize_with`, but the function is in a module.
#[proc_macro_derive(Serialize, attributes(marshal))]
pub fn derive_serialize(item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as DeriveInput);
Expand Down
2 changes: 1 addition & 1 deletion alox-48/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "alox-48"
version = "0.5.0-pre.3"
version = "0.5.0"
edition = "2021"
authors = ["Melody Madeline Lyons"]
repository = "https://github.com/Speak2Erase/alox-48"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

#[derive(alox_48::Serialize, Debug)]
struct Test {
a: i32,
b: i32,

throws: Throws,
}

#[derive(alox_48::Serialize, Debug)]
struct Throws {
string: &'static str,
#[marshal(serialize_with = "throws_error")]
what_errors: (),
}

fn throws_error<T, S>(_: &T, _serializer: S) -> alox_48::SerResult<S::Ok>
where
S: alox_48::SerializerTrait,
{
Err(alox_48::SerError::custom("custom error"))
}

fn main() {
let data = [
0x04, 0x08, 0x5b, 0x06, 0x6f, 0x3a, 0x0c, 0x42, 0x61, 0x64, 0x44, 0x61, 0x74, 0x61, 0x08,
Expand Down Expand Up @@ -35,4 +57,32 @@ fn main() {
println!(" {ctx}");
}
}

let test = Test {
a: 1,
b: 2,
throws: Throws {
string: "something went wrong",
what_errors: (),
},
};

let mut serializer = alox_48::Serializer::new();
let (error, trace) =
alox_48::path_to_error::serialize(&test, &mut serializer).expect_err("what??");

println!("Error: {error}");
println!("Backtrace:");
for ctx in trace.context.iter().rev() {
println!(" {ctx}");
}

let (error, trace) =
alox_48::path_to_error::serialize(&test, alox_48::ValueSerializer).expect_err("what??");

println!("Error: {error}");
println!("Backtrace:");
for ctx in trace.context.iter().rev() {
println!(" {ctx}");
}
}
4 changes: 4 additions & 0 deletions alox-48/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,24 @@ mod value;
pub use value::{from_value, to_value, Serializer as ValueSerializer, Value};

mod rb_types;
#[doc(inline)]
pub use rb_types::{
Instance, Object, RbArray, RbFields, RbHash, RbString, RbStruct, Sym, Symbol, Userdata,
};

#[doc(inline)]
pub use de::{
ArrayAccess, Deserialize, Deserializer, DeserializerTrait, Error as DeError, HashAccess,
InstanceAccess, IvarAccess, Result as DeResult, Visitor, VisitorInstance, VisitorOption,
};
#[doc(inline)]
pub use ser::{
ByteString as SerializeByteString, Error as SerError, Result as SerResult, Serialize,
SerializeArray, SerializeHash, SerializeIvars, Serializer, SerializerTrait,
};

#[cfg(feature = "derive")]
#[doc(inline)]
pub use alox_48_derive::{Deserialize, Serialize};

/// Deserialize data from some bytes.
Expand Down
127 changes: 2 additions & 125 deletions alox-48/src/path_to_error/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use super::{add_context, Context, Trace};
use crate::{
de::{DeserializeSeed, DeserializerTrait},
ArrayAccess, DeResult, HashAccess, InstanceAccess, IvarAccess, Sym, Symbol, Visitor,
Expand All @@ -17,125 +18,12 @@ pub struct Deserializer<'trace, T> {
trace: &'trace mut Trace,
}

/// Like a stack trace, but for deserialization.
///
/// This is used to track the path to an error in a deserialization.
#[derive(Debug, Default)]
pub struct Trace {
/// The context of the error.
///
/// This will be in reverse order!
/// The context furthest down the stack is the first element.
pub context: Vec<Context>,
}

#[derive(Debug)]
struct Wrapped<'trace, X> {
inner: X,
trace: &'trace mut Trace,
}

#[derive(Debug)]
// TODO deserializer position (no clue how to do this)
// FIXME this doesn't account for discarding errors!
pub enum Context {
Nil,
Bool(bool),
Int(i32),
Float(f64),

Hash(usize),
HashKey(usize),
HashValue(usize),

Array(usize),
ArrayIndex(usize),

String(String),
Symbol(Symbol),
Regex(String, u8),

Object(Symbol, usize),
Struct(Symbol, usize),

FetchingField(usize),
Field(Option<Symbol>, usize),

Class(Symbol),
Module(Symbol),

Instance,

Extended(Symbol),
UserClass(Symbol),
UserData(Symbol),
UserMarshal(Symbol),
ProcessingData(Symbol),
}

impl std::fmt::Display for Trace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for context in self.context.iter().rev() {
writeln!(f, "{context}")?;
}
Ok(())
}
}

impl std::fmt::Display for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Context::{
Array, ArrayIndex, Bool, Class, Extended, FetchingField, Field, Float, Hash, HashKey,
HashValue, Instance, Int, Module, Nil, Object, ProcessingData, Regex, String, Struct,
Symbol, UserClass, UserData, UserMarshal,
};
match self {
Nil => write!(f, "while processing a nil"),
Bool(v) => write!(f, "while processing a boolean: {v}"),
Int(v) => write!(f, "while processing an integer: {v}"),
Float(v) => write!(f, "while processing a float: {v}"),
Hash(len) => write!(f, "while processing a hash with {len} entries",),
HashKey(index) => write!(f, "while processing the {index} key of a hash",),
HashValue(index) => write!(f, "while processing the {index} value of a hash"),
Array(len) => write!(f, "while processing an array with {len} elements",),
ArrayIndex(index) => write!(f, "while processing the {index} element of an array"),
String(s) => write!(f, "while processing a string: {s}"),
Symbol(s) => write!(f, "while processing a symbol: {s}"),
Regex(s, flags) => write!(f, "while processing a regex: /{s}/ {flags}"),
Object(class, len) => write!(
f,
"while processing an instance of {class} with {len} ivars"
),
Struct(name, len) => write!(f, "while processing a struct of {name} with {len} ivars"),
FetchingField(index) => write!(f, "while fetching the {index} field"),
Field(Some(field), index) => {
write!(f, "while processing {field} (field index {index})")
}
Field(None, index) => write!(f, "while processing an invalid field at index {index}"),
Class(class) => write!(f, "while processing a class: {class}"),
Module(module) => write!(f, "while processing a module: {module}"),
Instance => write!(f, "while processing an instance"),
Extended(module) => write!(f, "while processing an object extended by {module}"),
UserClass(class) => write!(f, "while processing a user class: {class}"),
UserData(class) => write!(f, "while processing user data: {class}"),
UserMarshal(class) => write!(f, "while processing user marshal: {class}"),
ProcessingData(class) => write!(f, "while processing data: {class}"),
}
}
}

macro_rules! add_context {
($erroring_expr:expr $(, $context:expr )*) => {
match $erroring_expr {
Ok(value) => Ok(value),
Err(err) => {
$( $context; )*
Err(err)
}
}
};
}

impl<'de, 'trace, T> Deserializer<'trace, T>
where
T: DeserializerTrait<'de>,
Expand All @@ -149,17 +37,6 @@ where
}
}

impl Trace {
/// Create a new trace.
pub fn new() -> Self {
Self::default()
}

fn push(&mut self, context: Context) {
self.context.push(context);
}
}

impl<'de, 'trace, T> DeserializerTrait<'de> for Deserializer<'trace, T>
where
T: DeserializerTrait<'de>,
Expand Down Expand Up @@ -384,7 +261,7 @@ where
let wrapped = Deserializer::new(deserializer, self.trace);
add_context!(
self.inner.visit_data(class, wrapped),
self.trace.push(Context::ProcessingData(class.to_symbol()))
self.trace.push(Context::Data(class.to_symbol()))
)
}
}
Expand Down
Loading

0 comments on commit e1f3515

Please sign in to comment.