14 Commits
v0.2.0 ... main

6 changed files with 167 additions and 43 deletions

View File

@@ -1,18 +1,17 @@
[package] [package]
name = "serde-inline-default" name = "serde-inline-default"
version = "0.2.0" version = "1.0.0"
authors = ["ByteDream"] authors = ["bytedream"]
edition = "2021" edition = "2021"
description = "Serde default values via inline declaration" description = "Serde default values via inline declaration"
readme = "README.md" readme = "README.md"
repository = "https://github.com/ByteDream/serde-inline-default" repository = "https://github.com/bytedream/serde-inline-default"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
keywords = ["serde", "serialization"] keywords = ["serde", "serialization"]
categories = ["encoding"] categories = ["encoding"]
[lib] [lib]
proc-macro = true proc-macro = true
doctest = false doctest = false
[dependencies] [dependencies]

View File

@@ -1,12 +1,12 @@
# serde-inline-default [![ci](https://github.com/ByteDream/serde-inline-default/actions/workflows/ci.yml/badge.svg)](https://github.com/ByteDream/serde-inline-default/actions/workflows/ci.yml) [![crates.io](https://img.shields.io/crates/v/serde-inline-default)](https://crates.io/crates/serde-inline-default) [![docs](https://img.shields.io/docsrs/serde-inline-default)](https://docs.rs/serde-inline-default/latest/serde_inline_default/) # serde-inline-default [![ci](https://github.com/ByteDream/serde-inline-default/actions/workflows/ci.yml/badge.svg)](https://github.com/ByteDream/serde-inline-default/actions/workflows/ci.yml) [![crates.io](https://img.shields.io/crates/v/serde-inline-default)](https://crates.io/crates/serde-inline-default) [![crates.io downloads](https://img.shields.io/crates/d/serde-inline-default)](https://crates.io/crates/serde-inline-default) [![docs](https://img.shields.io/docsrs/serde-inline-default)](https://docs.rs/serde-inline-default/latest/serde_inline_default/)
Tiny crate to set default values for serde fields via inline attribute declaration. A tiny crate to set default values for serde struct fields via inline attribute declaration.
## Overview ## Overview
This crate is an approach to do what [serde-rs/serde#368](https://github.com/serde-rs/serde/issues/368) purposes. This crate is an approach to do what [serde-rs/serde#368](https://github.com/serde-rs/serde/issues/368) purposes: Defining default values for struct fields via inline declaration instead of creating a separate function for it.
If you want to set default values in plain [`serde`](https://serde.rs/), you have to create a function and link to it with `#[serde(default = "...")`.
This may be good if you need to do calculations to get the default value, but often you just want a simple integer or string to be the default value and have to create a whole function to return a hard-coded value. So instead of writing something like this, which can get very verbose quickly with many fields:
```rust ```rust
#[derive(Deserialize)] #[derive(Deserialize)]
struct Test { struct Test {
@@ -16,11 +16,7 @@ struct Test {
fn value_default() -> u32 { 42 } fn value_default() -> u32 { 42 }
``` ```
you can just do this:
That can get quiet messy if you have many fields with many (different) default values.
This crate tries to solve this issue by providing the `#[serde_inline_default]` proc macro.
With this macro set at the struct level (_**before `#[derive(Deserialize)]`/`#[derive(Serialize)]`!, otherwise it's not working correctly**_), you can set default values via `#[serde_inline_default(...)]` for your serde fields inline, without creating an extra function.
```rust ```rust
#[serde_inline_default] #[serde_inline_default]
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -30,8 +26,53 @@ struct Test {
} }
``` ```
> [!IMPORTANT]
> **`#[serde_inline_default]` must be set before `#[derive(Deserialize)]`/`#[derive(Serialize)]`!**
Internally, `#[serde_inline_default(...)]` gets expanded to a function which returns the set value and the attribute is replaced with `#[serde(default = "<function name>")]`. Internally, `#[serde_inline_default(...)]` gets expanded to a function which returns the set value and the attribute is replaced with `#[serde(default = "<function name>")]`.
So this macro is just some syntax sugar for you, but can get quiet handy if you want to keep your code clean or write declarative macros / `macro_rules!`. So this macro is just some syntax sugar for you, but can get quite handy if you want to keep your code clean or write declarative macros / `macro_rules!`.
## Alternatives
This crate isn't perfect. Thus, you might be more satisfied with alternatives `serde` provides.
With `#[serde(default)]` + `impl Default` on a struct, `serde` uses the default implementation of the struct to get default values for each field ([docs](https://serde.rs/container-attrs.html#default)):
```rust
#[derive(Deserialize)]
#[serde(default)]
struct Test {
value: u32
}
impl Default for Test {
fn default() -> Self {
Self {
value: 42
}
}
}
```
If you still need/want `serde-inline-default` features, you also can combine them with `#[serde(default))` and `impl Default`:
```rust
#[serde_inline_default]
#[derive(Deserialize)]
#[serde(default)]
struct Test {
value: u32,
#[serde_inline_default(0)]
other_value: u32,
}
impl Default for Test {
fn default() -> Self {
Self {
value: 42,
other_value: 42
}
}
}
```
## License ## License

View File

@@ -1,9 +1,10 @@
use crate::utils::type_lifetimes_to_static;
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::quote; use quote::quote;
use syn::{parse_quote, ItemStruct}; use syn::{parse_quote, ItemStruct};
pub(crate) fn expand_struct(mut item: ItemStruct) -> proc_macro::TokenStream { pub(crate) fn expand_struct(mut item: ItemStruct) -> proc_macro::TokenStream {
let mut inline_fns: Vec<(String, TokenStream, TokenStream)> = vec![]; let mut inline_fns: Vec<TokenStream> = vec![];
for (i, field) in item.fields.iter_mut().enumerate() { for (i, field) in item.fields.iter_mut().enumerate() {
for (j, attr) in field.attrs.iter_mut().enumerate() { for (j, attr) in field.attrs.iter_mut().enumerate() {
@@ -13,27 +14,30 @@ pub(crate) fn expand_struct(mut item: ItemStruct) -> proc_macro::TokenStream {
let default: TokenStream = attr.parse_args().unwrap(); let default: TokenStream = attr.parse_args().unwrap();
// we check here if a function with the exact same return value already exists. if so, // copy all the same #[cfg] conditional compilations flags for the field onto our built
// this function gets used. // default function.
let fn_name_lit = if let Some((fn_name_lit, _, _)) = inline_fns // otherwise, it's possible to create a constructor for a type that may be filtered by
.iter() // the same #[cfg]'s, breaking compilation
.find(|(_, def, _)| def.to_string() == default.to_string()) let cfg_attrs = field.attrs.iter().filter(|a| a.path().is_ident("cfg"));
{
fn_name_lit.clone()
} else {
let fn_name_lit = format!("__serde_inline_default_{}_{}", item.ident, i); let fn_name_lit = format!("__serde_inline_default_{}_{}", item.ident, i);
let fn_name_ident = Ident::new(&fn_name_lit, Span::call_site()); let fn_name_ident = Ident::new(&fn_name_lit, Span::call_site());
let return_type = &field.ty; let mut return_type = field.ty.clone();
let inline_fn = quote! { // replace lifetimes with 'static.
// the built default function / default values in general can only be static as they're
// generated without reference to the parent struct
type_lifetimes_to_static(&mut return_type);
inline_fns.push(quote! {
#[doc(hidden)] #[doc(hidden)]
#[allow(non_snake_case)]
#( #cfg_attrs )*
fn #fn_name_ident () -> #return_type { fn #fn_name_ident () -> #return_type {
#default #default
} }
}; });
inline_fns.push((fn_name_lit.clone(), default, inline_fn));
fn_name_lit
};
field.attrs.remove(j); field.attrs.remove(j);
field field
.attrs .attrs
@@ -42,10 +46,8 @@ pub(crate) fn expand_struct(mut item: ItemStruct) -> proc_macro::TokenStream {
} }
} }
let real_inline_fns: Vec<TokenStream> =
inline_fns.into_iter().map(|(_, _, func)| func).collect();
let expanded = quote! { let expanded = quote! {
#( #real_inline_fns )* #( #inline_fns )*
#item #item
}; };

View File

@@ -4,15 +4,19 @@ use proc_macro::TokenStream;
use syn::{parse_macro_input, Item}; use syn::{parse_macro_input, Item};
mod expand; mod expand;
mod utils;
/// The main macro of this crate. /// The main macro of this crate.
/// Use it to define default values of fields in structs you [`Serialize`] or [`Deserialize`]. /// Use it to define default values of fields in structs you [`Serialize`] or [`Deserialize`].
/// You do not need to create a extra function to provide the default value, as it is the case in serdes' implementation of default (`#[serde(default = "...")]`). /// You do not need to create an extra function to provide the default value, as it is the case in
/// serdes' implementation of default (`#[serde(default = "...")]`).
/// ///
/// Set this macro on a struct where you use [`Serialize`] or [`Deserialize`] and use `#[serde_inline_default(...)]` on the field you want to have a inline default value. /// Set this macro on a struct where you use [`Serialize`] or [`Deserialize`] and use
/// Replace the `...` with the value you want and it will be set as default if serde needs it. /// `#[serde_inline_default(...)]` on the field you want to have an inline default value.
/// Replace the `...` with the value you want, and it will be set as default if serde needs it.
/// ///
/// Note that you must set this macro _before_ `#[derive(Serialize)]` / `#[derive(Deserialize)]` as it wouldn't work properly if set after the derive. /// Note that you must set this macro _before_ `#[derive(Serialize)]` / `#[derive(Deserialize)]` as
/// it won't work properly if it's set after the derive.
/// ///
/// # Examples /// # Examples
/// ///

40
src/utils.rs Normal file
View File

@@ -0,0 +1,40 @@
use syn::{parse_quote, GenericArgument, PathArguments, Type};
pub(crate) fn type_lifetimes_to_static(ty: &mut Type) {
match ty {
Type::Array(array) => type_lifetimes_to_static(array.elem.as_mut()),
Type::Group(group) => type_lifetimes_to_static(&mut group.elem),
Type::Path(path) => {
for segment in &mut path.path.segments {
match &mut segment.arguments {
PathArguments::None => (),
PathArguments::AngleBracketed(angle_bracketed) => {
for arg in &mut angle_bracketed.args {
match arg {
GenericArgument::Lifetime(lifetime) => {
*lifetime = parse_quote!('static);
}
GenericArgument::Type(ty) => type_lifetimes_to_static(ty),
_ => (),
}
}
}
PathArguments::Parenthesized(parenthesized) => {
for input in &mut parenthesized.inputs {
type_lifetimes_to_static(input)
}
}
}
}
}
Type::Ptr(ptr) => type_lifetimes_to_static(&mut ptr.elem),
Type::Reference(reference) => reference.lifetime = Some(parse_quote!('static)),
Type::Slice(slice) => type_lifetimes_to_static(&mut slice.elem),
Type::Tuple(tuple) => {
for elem in &mut tuple.elems {
type_lifetimes_to_static(elem)
}
}
_ => (),
}
}

View File

@@ -1,6 +1,7 @@
use serde::Deserialize; use serde::Deserialize;
use serde_inline_default::serde_inline_default; use serde_inline_default::serde_inline_default;
use serde_json::json; use serde_json::json;
use std::borrow::Cow;
#[test] #[test]
fn test_serde_inline_default() { fn test_serde_inline_default() {
@@ -28,3 +29,40 @@ fn test_serde_inline_default() {
assert_eq!(test.inline_negative, -1337); assert_eq!(test.inline_negative, -1337);
assert_eq!(test.string, "string".to_string()); assert_eq!(test.string, "string".to_string());
} }
#[test]
fn test_lifetime() {
#[serde_inline_default]
#[derive(Deserialize)]
struct LifetimeTest<'a> {
#[serde_inline_default("test".into())]
test_str: Cow<'a, str>,
}
let lifetime_test: LifetimeTest = serde_json::from_value(json!({})).unwrap();
assert_eq!(lifetime_test.test_str, "test");
}
#[test]
#[allow(dead_code)]
fn test_conditional_compilation() {
#[cfg(debug_assertions)]
#[derive(Deserialize)]
struct TypeA(u8);
#[cfg(not(debug_assertions))]
#[derive(Deserialize)]
struct TypeB(u8);
#[serde_inline_default]
#[derive(Deserialize)]
struct Test {
#[cfg(debug_assertions)]
#[serde_inline_default(TypeA(1))]
val_a: TypeA,
#[cfg(not(debug_assertions))]
#[serde_inline_default(TypeB(1))]
val_b: TypeB,
}
}