13 Commits

Author SHA1 Message Date
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
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
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 157 additions and 33 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

@ -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 (_**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]

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,
// this function gets used.
let fn_name_lit = if let Some((fn_name_lit, _, _)) = inline_fns
.iter()
.find(|(_, def, _)| def.to_string() == default.to_string())
{
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! { // replaces most lifetimes with 'static
type_lifetimes_to_static(&mut return_type);
inline_fns.push(quote! {
#[doc(hidden)] #[doc(hidden)]
#[allow(non_snake_case)]
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
@ -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");
} }