Compare commits

...

21 Commits
v0.1.0 ... 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
8d1d49150f build: update version to 0.1.1 2023-02-11 19:45:26 +01:00
76727e73f4 docs: add documentation to macro function 2023-02-11 19:36:54 +01:00
d8a0dff1af docs: add README as library documentation 2023-02-11 18:51:49 +01:00
81b089be37 docs: fix docs.rs link 2023-02-11 18:49:17 +01:00
9 changed files with 231 additions and 36 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,22 +1,23 @@
[package] [package]
name = "serde-inline-default" name = "serde-inline-default"
version = "0.1.0" 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
[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,10 +1,10 @@
# 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/crunchyroll-rs/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.
## Overview ## Overview
This crate is an approach to do what serde-rs/serde#368 purposes. This crate is an approach to do what [serde-rs/serde#368](https://github.com/serde-rs/serde/issues/368) purposes.
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 = "...")`. 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. 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.
```rust ```rust
@ -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,12 +30,57 @@ 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:
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT License ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - MIT License ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)

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

@ -1,8 +1,33 @@
#![doc = include_str!("../README.md")]
use proc_macro::TokenStream; 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.
/// 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 = "...")]`).
///
/// 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.
/// 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.
///
/// # Examples
///
/// ```rust
/// #[serde_inline_default]
/// #[derive(Deserialize)]
/// struct Test {
/// #[serde_inline_default(42)]
/// value: u32
/// }
/// ```
///
/// [`Serialize`]: https://docs.rs/serde/*/serde/trait.Serialize.html
/// [`Deserialize`]: https://docs.rs/serde/*/serde/trait.Deserialize.html
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn serde_inline_default(_attr: TokenStream, input: TokenStream) -> TokenStream { pub fn serde_inline_default(_attr: TokenStream, input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as Item); let item = parse_macro_input!(input as Item);

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");
} }