Compare commits

...

17 Commits
v0.1.1 ... main

Author SHA1 Message Date
6c014e41a6 docs: update placement visibility (#11) 2025-04-22 14:26:00 +02:00
5fbcd2eb14 docs: remove code usage comment 2025-04-11 20:59:15 +02:00
baeb1969fe docs: add alternatives 2025-04-11 20:42:14 +02:00
0a12ee6b3f docs: add crates.io total downloads badge 2025-03-25 12:22:58 +01:00
6f0f9f9799 build: update version to 0.2.3 2024-12-02 21:59:27 +01:00
985eca392c fix: remove default function reusing 2024-12-02 21:58:22 +01:00
6eddcf38fe build: update version to 0.2.2 2024-10-14 22:27:03 +02:00
c5d874a3ad feat: add basic support for non-static lifetimes 2024-10-14 22:27:01 +02:00
5d0f313523 build: update version to 0.2.1 2024-09-24 13:01:00 +02:00
nullstalgia
c020a6e0e4
Suppress rust-analyzer's "Non Snake Case" warning for generated idents (#5) 2024-09-24 12:49:40 +02:00
7130dc8927 build: update version to 0.2.0 2024-01-27 00:19:41 +01:00
cbd26efdd4 chore: highlight required the macro position even more 2024-01-23 15:34:42 +01:00
85b1fbdfcd fix: apply syn update changes 2023-03-27 20:48:52 +00:00
dependabot[bot]
cc8120fb4a build(deps): update syn requirement from 1.0 to 2.0
Updates the requirements on [syn](https://github.com/dtolnay/syn) to permit the latest version.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.0...2.0.10)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-27 20:48:52 +00:00
b0489edfaf chore: add dependabot 2023-02-24 14:22:26 +01:00
ba2c2133a5 test: add string default to test 2023-02-24 14:22:16 +01:00
6bb8576fa8 test(examples): add examples 2023-02-24 14:21:56 +01:00
9 changed files with 203 additions and 34 deletions

6
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: cargo
directory: /
schedule:
interval: weekly

View File

@ -1,24 +1,23 @@
[package] [package]
name = "serde-inline-default" name = "serde-inline-default"
version = "0.1.1" version = "0.2.3"
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]
proc-macro2 = "1.0" proc-macro2 = "1.0"
quote = "1.0" quote = "1.0"
syn = { version = "1.0", features = ["full"] } syn = { version = "2.0", features = ["full"] }
[dev-dependencies] [dev-dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@ -1,4 +1,4 @@
# 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. Tiny crate to set default values for serde fields via inline attribute declaration.
@ -19,7 +19,7 @@ fn value_default() -> u32 { 42 }
That can get quiet messy if you have many fields with many (different) default values. 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. 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. With this macro set at the struct level, 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]
@ -30,9 +30,54 @@ struct Test {
} }
``` ```
> [!IMPORTANT]
> **`#[serde_inline_default]` must be set before `#[derive(Deserialize)]`/`#[derive(Serialize)]`, otherwise it's not working correctly!**
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 quiet 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
This project is licensed under either of the following licenses, at your option: This project is licensed under either of the following licenses, at your option:

29
examples/basic.rs Normal file
View File

@ -0,0 +1,29 @@
use serde::Deserialize;
use serde_inline_default::serde_inline_default;
use serde_json::json;
#[serde_inline_default]
#[derive(Deserialize)]
struct Basic {
// if using `String` you have to call `.to_string()`
#[serde_inline_default("0.0.0.0".to_string())]
host: String,
// works without specifying the integer type at the end of the value (8080u16)
#[serde_inline_default(8080)]
port: u16,
// expressions are working too
#[serde_inline_default(serde_json::json!({}))]
random_third_party_type: serde_json::Value,
}
fn main() -> Result<(), serde_json::Error> {
// creating a empty json object to use the default value of all fields
let json_object = json!({});
let basic: Basic = serde_json::from_value(json_object)?;
assert_eq!(basic.host, "0.0.0.0".to_string());
assert_eq!(basic.port, 8080);
assert_eq!(basic.random_third_party_type, json!({}));
Ok(())
}

39
examples/macro_rules.rs Normal file
View File

@ -0,0 +1,39 @@
use serde_json::json;
macro_rules! simple_macro {
(struct $name:ident { $($field:ident: $type:ty $(= $default:expr)?),*$(,)? }) => {
#[serde_inline_default::serde_inline_default]
#[derive(serde::Deserialize)]
struct $name {
$(
$(
#[serde_inline_default($default)]
)?
$field: $type
),*
}
}
}
fn main() -> Result<(), serde_json::Error> {
// `username` and `password` must be set when deserializing as no default value is defined for
// them. `secret` not as we're defining a default value for it
simple_macro! {
struct Example {
username: String,
password: String,
secret: String = "verysecretsecret".to_string()
}
}
let json_object = json!({
"username": "testuser",
"password": "testpassword"
});
let example: Example = serde_json::from_value(json_object)?;
assert_eq!(example.username, "testuser");
assert_eq!(example.password, "testpassword");
assert_eq!(example.secret, "verysecretsecret");
Ok(())
}

View File

@ -1,40 +1,34 @@
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() {
if !attr.path.is_ident("serde_inline_default") { if !attr.path().is_ident("serde_inline_default") {
continue; continue;
} }
let _default_str = attr.tokens.to_string(); let default: TokenStream = attr.parse_args().unwrap();
let default: TokenStream = _default_str[1.._default_str.len() - 1].parse().unwrap();
// we check here if a function with the exact same return value already exists. if so, let fn_name_lit = format!("__serde_inline_default_{}_{}", item.ident, i);
// this function gets used. let fn_name_ident = Ident::new(&fn_name_lit, Span::call_site());
let fn_name_lit = if let Some((fn_name_lit, _, _)) = inline_fns let mut return_type = field.ty.clone();
.iter()
.find(|(_, def, _)| def.to_string() == default.to_string()) // replaces most lifetimes with 'static
{ type_lifetimes_to_static(&mut return_type);
fn_name_lit.clone()
} else { inline_fns.push(quote! {
let fn_name_lit = format!("__serde_inline_default_{}_{}", item.ident, i); #[doc(hidden)]
let fn_name_ident = Ident::new(&fn_name_lit, Span::call_site()); #[allow(non_snake_case)]
let return_type = &field.ty; fn #fn_name_ident () -> #return_type {
#default
}
});
let inline_fn = quote! {
#[doc(hidden)]
fn #fn_name_ident () -> #return_type {
#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
@ -43,10 +37,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,6 +4,7 @@ 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`].

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() {
@ -17,6 +18,8 @@ fn test_serde_inline_default() {
inline: u32, inline: u32,
#[serde_inline_default(-1337)] #[serde_inline_default(-1337)]
inline_negative: i32, inline_negative: i32,
#[serde_inline_default("string".to_string())]
string: String,
} }
let test: Test = serde_json::from_value(json!({})).unwrap(); let test: Test = serde_json::from_value(json!({})).unwrap();
@ -24,4 +27,19 @@ fn test_serde_inline_default() {
assert_eq!(test.native, 69); assert_eq!(test.native, 69);
assert_eq!(test.inline, 420); assert_eq!(test.inline, 420);
assert_eq!(test.inline_negative, -1337); assert_eq!(test.inline_negative, -1337);
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");
} }