├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── derive ├── Cargo.toml └── src │ └── lib.rs ├── src ├── debug.rs ├── error.rs ├── lib.rs ├── map.rs ├── seq.rs ├── structure.rs └── tuple.rs └── tests └── test.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Build release (verify no-std) 16 | run: cargo build --verbose --release 17 | - name: Run tests 18 | run: cargo test --verbose 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | version = "1.2.0" 3 | edition = "2021" 4 | authors = ["Ingvar Stepanyan "] 5 | license = "MIT" 6 | repository = "https://github.com/RReverser/serdebug" 7 | categories = ["development-tools::debugging", "value-formatting", "no-std"] 8 | keywords = ["serialization", "serde", "debug", "formatting", "derive"] 9 | 10 | [workspace.dependencies] 11 | serde = { version = "1.0", default-features = false, features = ["alloc"] } 12 | 13 | [workspace] 14 | members = ["derive"] 15 | 16 | [package] 17 | name = "serdebug" 18 | description = "serde-based replacement for #[derive(Debug)]" 19 | readme = "README.md" 20 | version.workspace = true 21 | edition.workspace = true 22 | authors.workspace = true 23 | license.workspace = true 24 | repository.workspace = true 25 | categories.workspace = true 26 | keywords.workspace = true 27 | 28 | [dependencies] 29 | ref-cast = "1.0.23" 30 | serde.workspace = true 31 | serdebug_derive = { path = "derive", version = "1.2.0" } 32 | 33 | [features] 34 | 35 | [dev-dependencies] 36 | serde = { workspace = true, features = ["derive", "std"] } 37 | proptest = "1.5.0" 38 | test-strategy = "0.4.0" 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ingvar Stepanyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serdebug 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/serdebug.svg)](https://crates.io/crates/serdebug) 4 | [![docs.rs](https://docs.rs/serdebug/badge.svg)](https://docs.rs/serdebug) 5 | 6 | This is a drop-in replacement for `#[derive(Debug)]` that uses `serde::Serialize` under the hood to provide advanced control over output serialisation. 7 | 8 | ## Usage 9 | 10 | By default, the generated code will produce exactly same output as `#[derive(Debug)]` for compatibility. 11 | 12 | However, this might be not very interesting, so let's add some serde attributes to see how we can control debug representation: 13 | 14 | ```rust 15 | use serde::Serialize; 16 | use serdebug::SerDebug; 17 | 18 | pub struct CustomType(u32); 19 | 20 | #[derive(Serialize, SerDebug)] 21 | pub enum MyEnum { 22 | // renaming items works as expected 23 | #[serde(rename = "AAAAAAA!!!")] 24 | A, 25 | 26 | B(u32), 27 | 28 | C { flag: bool }, 29 | } 30 | 31 | #[derive(Serialize, SerDebug)] 32 | // so does bulk rename on containers 33 | #[serde(rename_all = "PascalCase")] 34 | pub struct MyStruct { 35 | number: u32, 36 | 37 | my_enum: Vec, 38 | 39 | // we might want to hide some items from the output 40 | #[serde(skip_serializing)] 41 | hidden: bool, 42 | 43 | // or override serialisation for otherwise verbose wrappers or 44 | // third-party types that don't implement `Debug` and/or `Serialize` 45 | #[serde(serialize_with = "custom_serialize")] 46 | custom_type: CustomType, 47 | } 48 | 49 | fn custom_serialize(value: &CustomType, ser: S) -> Result { 50 | value.0.serialize(ser) 51 | } 52 | 53 | fn main() { 54 | let s = MyStruct { 55 | number: 42, 56 | my_enum: vec![MyEnum::A, MyEnum::B(10), MyEnum::C { flag: true }], 57 | hidden: true, 58 | custom_type: CustomType(20), 59 | }; 60 | 61 | assert_eq!(format!("{s:#?}"), " 62 | MyStruct { 63 | Number: 42, 64 | MyEnum: [ 65 | AAAAAAA!!!, 66 | B( 67 | 10, 68 | ), 69 | C { 70 | flag: true, 71 | }, 72 | ], 73 | CustomType: 20, 74 | } 75 | ".trim()); 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serdebug_derive" 3 | description = "Proc-macro part for serdebug (see main crate for more details)" 4 | version.workspace = true 5 | edition.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | keywords.workspace = true 11 | 12 | [dependencies] 13 | quote = "1.0.37" 14 | syn = "2.0.79" 15 | 16 | [lib] 17 | proc-macro = true 18 | -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::parse_quote; 4 | 5 | #[proc_macro_derive(SerDebug)] 6 | pub fn derive(input: TokenStream) -> TokenStream { 7 | let syn::DeriveInput { 8 | ident, 9 | mut generics, 10 | .. 11 | } = syn::parse(input).unwrap(); 12 | 13 | generics 14 | .make_where_clause() 15 | .predicates 16 | .push(parse_quote!(Self: ::serde::Serialize)); 17 | 18 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 19 | 20 | (quote! { 21 | impl #impl_generics ::std::fmt::Debug for #ident #ty_generics #where_clause { 22 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 23 | ::serdebug::fmt(self, f) 24 | } 25 | } 26 | }) 27 | .into() 28 | } 29 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | use crate::Serializer; 2 | use core::fmt::{self, Debug, Formatter}; 3 | use ref_cast::RefCast; 4 | use serde::ser::Serialize; 5 | 6 | #[derive(RefCast)] 7 | #[repr(transparent)] 8 | struct Wrapper(T); 9 | 10 | impl Debug for Wrapper { 11 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 12 | self.0.serialize(Serializer(f))?; 13 | Ok(()) 14 | } 15 | } 16 | 17 | /// Wrap a value supporting just [`Serialize`] into [`Debug`]. 18 | pub fn debug(value: &T) -> &(impl ?Sized + Debug) { 19 | Wrapper::ref_cast(value) 20 | } 21 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use serde::ser; 3 | 4 | /// A [`ser::Error`]-compatible wrapper for [`fmt::Error`]. 5 | #[derive(Debug)] 6 | pub struct Error(fmt::Error); 7 | 8 | impl From for Error { 9 | fn from(err: fmt::Error) -> Error { 10 | Error(err) 11 | } 12 | } 13 | 14 | impl From for fmt::Error { 15 | fn from(err: Error) -> fmt::Error { 16 | err.0 17 | } 18 | } 19 | 20 | impl fmt::Display for Error { 21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | self.0.fmt(f) 23 | } 24 | } 25 | 26 | impl core::error::Error for Error { 27 | fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { 28 | Some(&self.0) 29 | } 30 | } 31 | 32 | impl ser::Error for Error { 33 | fn custom(_msg: T) -> Self { 34 | unimplemented!("This type is intended to be used only for fmt::Error") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![no_std] 3 | 4 | extern crate alloc; 5 | 6 | mod debug; 7 | mod error; 8 | mod map; 9 | mod seq; 10 | mod structure; 11 | mod tuple; 12 | 13 | use alloc::format; 14 | use alloc::string::String; 15 | use core::fmt::{self, Debug, Formatter}; 16 | use serde::ser::{self, Serialize, SerializeTupleStruct}; 17 | 18 | pub use debug::debug; 19 | pub use error::Error; 20 | pub use serdebug_derive::SerDebug; 21 | 22 | /// A [`Serializer`](::serde::Serializer)-compatible wrapper for a [`Formatter`]. 23 | pub struct Serializer<'a, 'b: 'a>(pub &'a mut Formatter<'b>); 24 | 25 | macro_rules! simple_impl { 26 | ($(fn $name:ident ( $v:ident : $ty:ty );)*) => { 27 | $(fn $name(self, $v: $ty) -> Result { 28 | Ok($v.fmt(self.0)?) 29 | })* 30 | }; 31 | } 32 | 33 | impl<'a, 'b: 'a> ser::Serializer for Serializer<'a, 'b> { 34 | type Ok = (); 35 | type Error = Error; 36 | 37 | type SerializeSeq = seq::Serializer<'a, 'b>; 38 | type SerializeTuple = tuple::Serializer<'a, 'b>; 39 | type SerializeTupleStruct = tuple::Serializer<'a, 'b>; 40 | type SerializeTupleVariant = tuple::Serializer<'a, 'b>; 41 | type SerializeMap = map::Serializer<'a, 'b>; 42 | type SerializeStruct = structure::Serializer<'a, 'b>; 43 | type SerializeStructVariant = structure::Serializer<'a, 'b>; 44 | 45 | simple_impl! { 46 | fn serialize_bool(v: bool); 47 | fn serialize_i8(v: i8); 48 | fn serialize_i16(v: i16); 49 | fn serialize_i32(v: i32); 50 | fn serialize_i64(v: i64); 51 | fn serialize_u8(v: u8); 52 | fn serialize_u16(v: u16); 53 | fn serialize_u32(v: u32); 54 | fn serialize_u64(v: u64); 55 | fn serialize_f32(v: f32); 56 | fn serialize_f64(v: f64); 57 | fn serialize_char(v: char); 58 | fn serialize_str(v: &str); 59 | fn serialize_bytes(v: &[u8]); 60 | } 61 | 62 | fn serialize_none(self) -> Result { 63 | self.serialize_unit_struct("None") 64 | } 65 | 66 | fn serialize_some(self, value: &T) -> Result { 67 | self.serialize_newtype_struct("Some", value) 68 | } 69 | 70 | fn serialize_unit(self) -> Result { 71 | Ok(().fmt(self.0)?) 72 | } 73 | 74 | fn serialize_unit_struct(self, name: &'static str) -> Result { 75 | self.serialize_tuple_struct(name, 0)?.end() 76 | } 77 | 78 | fn serialize_unit_variant( 79 | self, 80 | _name: &'static str, 81 | _variant_index: u32, 82 | variant: &'static str, 83 | ) -> Result { 84 | self.serialize_unit_struct(variant) 85 | } 86 | 87 | fn serialize_newtype_struct( 88 | self, 89 | name: &'static str, 90 | value: &T, 91 | ) -> Result { 92 | let mut tuple = self.serialize_tuple_struct(name, 1)?; 93 | tuple.serialize_field(value)?; 94 | tuple.end() 95 | } 96 | 97 | fn serialize_newtype_variant( 98 | self, 99 | _name: &'static str, 100 | _variant_index: u32, 101 | variant: &'static str, 102 | value: &T, 103 | ) -> Result { 104 | self.serialize_newtype_struct(variant, value) 105 | } 106 | 107 | fn serialize_seq(self, _len: Option) -> Result { 108 | Ok(seq::Serializer::new(self.0)) 109 | } 110 | 111 | fn serialize_tuple_struct( 112 | self, 113 | name: &'static str, 114 | _len: usize, 115 | ) -> Result { 116 | Ok(tuple::Serializer::new(self.0, name)) 117 | } 118 | 119 | fn serialize_tuple(self, len: usize) -> Result { 120 | self.serialize_tuple_struct("", len) 121 | } 122 | 123 | fn serialize_tuple_variant( 124 | self, 125 | _name: &'static str, 126 | _variant_index: u32, 127 | variant: &'static str, 128 | len: usize, 129 | ) -> Result { 130 | self.serialize_tuple_struct(variant, len) 131 | } 132 | 133 | fn serialize_map(self, _len: Option) -> Result { 134 | Ok(map::Serializer::new(self.0)) 135 | } 136 | 137 | fn serialize_struct( 138 | self, 139 | name: &'static str, 140 | _len: usize, 141 | ) -> Result { 142 | Ok(structure::Serializer::new(self.0, name)) 143 | } 144 | 145 | fn serialize_struct_variant( 146 | self, 147 | _name: &'static str, 148 | _variant_index: u32, 149 | variant: &'static str, 150 | len: usize, 151 | ) -> Result { 152 | self.serialize_struct(variant, len) 153 | } 154 | } 155 | 156 | /// Format value's debug representation into a given [`Formatter`]. 157 | pub fn fmt(value: &T, f: &mut Formatter) -> fmt::Result { 158 | debug(value).fmt(f) 159 | } 160 | 161 | /// Convert value into a string with a concise debug representation. 162 | pub fn to_string(value: &T) -> String { 163 | format!("{:?}", debug(value)) 164 | } 165 | 166 | /// Pretty-print value into a string with a debug representation. 167 | pub fn to_string_pretty(value: &T) -> String { 168 | format!("{:#?}", debug(value)) 169 | } 170 | -------------------------------------------------------------------------------- /src/map.rs: -------------------------------------------------------------------------------- 1 | use crate::{debug, Error}; 2 | use core::fmt::{DebugMap, Formatter}; 3 | use serde::ser::{Serialize, SerializeMap}; 4 | 5 | pub struct Serializer<'a, 'b: 'a>(DebugMap<'a, 'b>); 6 | 7 | impl<'a, 'b: 'a> Serializer<'a, 'b> { 8 | pub fn new(f: &'a mut Formatter<'b>) -> Self { 9 | Serializer(f.debug_map()) 10 | } 11 | } 12 | 13 | impl<'a, 'b: 'a> SerializeMap for Serializer<'a, 'b> { 14 | type Ok = (); 15 | type Error = Error; 16 | 17 | fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> { 18 | self.0.key(&debug(key)); 19 | Ok(()) 20 | } 21 | 22 | fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> { 23 | self.0.value(&debug(value)); 24 | Ok(()) 25 | } 26 | 27 | fn end(mut self) -> Result<(), Error> { 28 | Ok(self.0.finish()?) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/seq.rs: -------------------------------------------------------------------------------- 1 | use crate::{debug, Error}; 2 | use core::fmt::{DebugList, Formatter}; 3 | use serde::ser::{Serialize, SerializeSeq}; 4 | 5 | pub struct Serializer<'a, 'b: 'a>(DebugList<'a, 'b>); 6 | 7 | impl<'a, 'b: 'a> Serializer<'a, 'b> { 8 | pub fn new(f: &'a mut Formatter<'b>) -> Self { 9 | Serializer(f.debug_list()) 10 | } 11 | } 12 | 13 | impl<'a, 'b: 'a> SerializeSeq for Serializer<'a, 'b> { 14 | type Ok = (); 15 | type Error = Error; 16 | 17 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { 18 | self.0.entry(&debug(value)); 19 | Ok(()) 20 | } 21 | 22 | fn end(mut self) -> Result<(), Error> { 23 | Ok(self.0.finish()?) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/structure.rs: -------------------------------------------------------------------------------- 1 | use crate::{debug, Error}; 2 | use core::fmt::{DebugStruct, Formatter}; 3 | use serde::ser::{Serialize, SerializeStruct, SerializeStructVariant}; 4 | 5 | pub struct Serializer<'a, 'b: 'a>(DebugStruct<'a, 'b>); 6 | 7 | impl<'a, 'b: 'a> Serializer<'a, 'b> { 8 | pub fn new(f: &'a mut Formatter<'b>, name: &str) -> Self { 9 | Serializer(f.debug_struct(name)) 10 | } 11 | } 12 | 13 | impl<'a, 'b: 'a> SerializeStruct for Serializer<'a, 'b> { 14 | type Ok = (); 15 | type Error = Error; 16 | 17 | fn serialize_field( 18 | &mut self, 19 | key: &'static str, 20 | value: &T, 21 | ) -> Result<(), Self::Error> { 22 | self.0.field(key, &debug(value)); 23 | Ok(()) 24 | } 25 | 26 | fn end(mut self) -> Result<(), Error> { 27 | Ok(self.0.finish()?) 28 | } 29 | } 30 | 31 | impl<'a, 'b: 'a> SerializeStructVariant for Serializer<'a, 'b> { 32 | type Ok = (); 33 | type Error = Error; 34 | 35 | fn serialize_field( 36 | &mut self, 37 | key: &'static str, 38 | value: &T, 39 | ) -> Result<(), Self::Error> { 40 | SerializeStruct::serialize_field(self, key, value) 41 | } 42 | 43 | fn end(self) -> Result<(), Error> { 44 | SerializeStruct::end(self) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/tuple.rs: -------------------------------------------------------------------------------- 1 | use crate::{debug, Error}; 2 | use core::fmt::{DebugTuple, Formatter}; 3 | use serde::ser::{Serialize, SerializeTuple, SerializeTupleStruct, SerializeTupleVariant}; 4 | 5 | pub struct Serializer<'a, 'b: 'a>(DebugTuple<'a, 'b>); 6 | 7 | impl<'a, 'b: 'a> Serializer<'a, 'b> { 8 | pub fn new(f: &'a mut Formatter<'b>, name: &str) -> Self { 9 | Serializer(f.debug_tuple(name)) 10 | } 11 | } 12 | 13 | impl<'a, 'b: 'a> SerializeTuple for Serializer<'a, 'b> { 14 | type Ok = (); 15 | type Error = Error; 16 | 17 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { 18 | self.0.field(&debug(value)); 19 | Ok(()) 20 | } 21 | 22 | fn end(mut self) -> Result<(), Error> { 23 | Ok(self.0.finish()?) 24 | } 25 | } 26 | 27 | impl<'a, 'b: 'a> SerializeTupleStruct for Serializer<'a, 'b> { 28 | type Ok = (); 29 | type Error = Error; 30 | 31 | fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> { 32 | SerializeTuple::serialize_element(self, value) 33 | } 34 | 35 | fn end(self) -> Result<(), Error> { 36 | SerializeTuple::end(self) 37 | } 38 | } 39 | 40 | impl<'a, 'b: 'a> SerializeTupleVariant for Serializer<'a, 'b> { 41 | type Ok = (); 42 | type Error = Error; 43 | 44 | fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> { 45 | SerializeTuple::serialize_element(self, value) 46 | } 47 | 48 | fn end(self) -> Result<(), Error> { 49 | SerializeTuple::end(self) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use serdebug::SerDebug; 3 | use std::collections::HashMap; 4 | use std::fmt::Debug; 5 | use test_strategy::{proptest, Arbitrary}; 6 | 7 | fn test_via_serdebug(lhs: T) { 8 | #[derive(Serialize, SerDebug)] 9 | #[serde(transparent)] 10 | struct Rhs<'a, T>(&'a T); 11 | 12 | let rhs = Rhs(&lhs); 13 | 14 | assert_eq!(format!("{lhs:#?}"), format!("{rhs:#?}")); 15 | assert_eq!(format!("{lhs:?}"), format!("{rhs:?}")); 16 | } 17 | 18 | macro_rules! test { 19 | (@decl $(# $attr:tt)* struct { $($payload:tt)* }) => { 20 | $(# $attr)* 21 | struct Lhs { $($payload)* } 22 | }; 23 | 24 | (@decl $(# $attr:tt)* struct $($payload:tt)*) => { 25 | $(# $attr)* 26 | struct Lhs $($payload)*; 27 | }; 28 | 29 | (@decl $(# $attr:tt)* enum $($payload:tt)*) => { 30 | $(# $attr)* 31 | enum Lhs { 32 | Variant $($payload)* 33 | } 34 | }; 35 | 36 | // Uninhabited enums can't be constructed by definition. 37 | (@kind $name:ident enum {}) => {}; 38 | 39 | (@kind $name:ident $kind:ident $($payload:tt)*) => { 40 | mod $name { 41 | use crate::*; 42 | 43 | test!(@decl #[derive(Serialize, Debug, Arbitrary)] $kind $($payload)*); 44 | 45 | #[proptest] 46 | fn test(lhs: Lhs) { 47 | test_via_serdebug(lhs); 48 | } 49 | } 50 | }; 51 | 52 | ($name:ident $($payload:tt)*) => { 53 | mod $name { 54 | test!(@kind test_struct struct $($payload)*); 55 | test!(@kind test_enum enum $($payload)*); 56 | } 57 | }; 58 | } 59 | 60 | test!(named_fields { a: u32, b: Option, c: String, d: (), e: HashMap, f: Vec }); 61 | test!(empty_named_fields {}); 62 | test!(tuple_fields(u32, Option, String, (), HashMap, Vec)); 63 | test!(single_tuple_field(u32)); 64 | test!(empty_tuple_fields()); 65 | test!(no_fields); 66 | --------------------------------------------------------------------------------