serde_derive_default-0.1.1/.cargo_vcs_info.json0000644000000001360000000000100151770ustar { "git": { "sha1": "bd31117ed9cbbeca9d62f3ac03f08efdb78047bd" }, "path_in_vcs": "" }serde_derive_default-0.1.1/.github/workflows/rust.yml000064400000000000000000000004741046102023000211110ustar 00000000000000name: Rust on: push: branches: [ "main" ] pull_request: branches: [ "main" ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose serde_derive_default-0.1.1/.gitignore000064400000000000000000000000341046102023000157540ustar 00000000000000/target /Cargo.lock /.idea/ serde_derive_default-0.1.1/Cargo.lock0000644000000156170000000000100131640ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" dependencies = [ "memchr", ] [[package]] name = "basic-toml" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" dependencies = [ "serde", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" [[package]] name = "indexmap" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "proc-macro2" version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3cbb081b9784b07cceb8824c8583f86db4814d172ab043f3c23f7dc600bf83d" [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_derive_default" version = "0.1.1" dependencies = [ "quote", "regex", "serde", "serde_yaml", "syn", "thiserror", "trybuild", ] [[package]] name = "serde_json" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_yaml" version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "syn" version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "trybuild" version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1" dependencies = [ "basic-toml", "glob", "once_cell", "serde", "serde_derive", "serde_json", "termcolor", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unsafe-libyaml" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" serde_derive_default-0.1.1/Cargo.toml0000644000000023220000000000100131740ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "serde_derive_default" version = "0.1.1" description = "Derive Default using serde field level annotations" homepage = "https://github.com/BrynCooke/serde-derive-default" readme = "README.md" keywords = [ "derive", "macro", "serde", "default", ] license = "Apache-2.0" repository = "https://github.com/BrynCooke/serde-derive-default" [lib] proc-macro = true [dependencies.quote] version = "1.0" [dependencies.regex] version = "1.10" [dependencies.syn] version = "2.0" features = [ "parsing", "derive", ] [dependencies.thiserror] version = "1.0" [dev-dependencies.serde] version = "1.0" features = ["derive"] [dev-dependencies.serde_yaml] version = "0.9" [dev-dependencies.trybuild] version = "1.0.85" serde_derive_default-0.1.1/Cargo.toml.orig000064400000000000000000000012631046102023000166600ustar 00000000000000[package] name = "serde_derive_default" version = "0.1.1" edition = "2021" license = "Apache-2.0" description = "Derive Default using serde field level annotations" homepage="https://github.com/BrynCooke/serde-derive-default" repository="https://github.com/BrynCooke/serde-derive-default" keywords = ["derive", "macro", "serde", "default"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] proc-macro = true [dependencies] syn = {version="2.0", features=["parsing", "derive"] } quote = "1.0" thiserror = "1.0" regex = "1.10" [dev-dependencies] trybuild = "1.0.85" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" serde_derive_default-0.1.1/LICENSE-APACHE000064400000000000000000000010531046102023000157120ustar 00000000000000Copyright [2023] [Bryn Cooke] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. serde_derive_default-0.1.1/README.md000064400000000000000000000034661046102023000152570ustar 00000000000000[![Latest Version](https://img.shields.io/crates/v/serde-derive-default.svg)](https://crates.io/crates/serde_derive_default) # Usage ```toml [dependencies] serde_derive_default = "0.1" ``` ```rust #[derive(Deserialize, serde_derive_default::Default)] struct MyStruct { } ``` # Problem When using serde defaulting users currently have to manually create a Default implementations that matches the serde field level annotations. If you use the regular `#[derive(Default)]`, it you will get unexpected results. For example: ```rust #[derive(Deserialize)] struct Container { a: A, } #[derive(Deserialize)] struct A { #[serde(default)] b: B, } #[derive(Deserialize, Default)] struct B { #[serde(default = "true_fn")] c: bool, } fn true_fn() -> bool { true } fn main() { let container1 = serde_yaml::from_str::("a: {}").unwrap(); let container2 = serde_yaml::from_str::("a: {b: {}}").unwrap(); if container1.a.b.c == container2.a.b.c { println!("serde and Default match!"); } else { println!("serde and Default do not match, this is a bug!"); } } ``` The output is: ``` serde and Default do not match, this is a bug! ``` This is because the implementation of Default disagrees with the serde defaults. If instead `serde_serive_default::Default` is used it will use the same annotations used by serde to create the default implementation: ```rust #[derive(Deserialize, serde_serive_default::Default)] struct B { #[serde(default = "true_fn")] c: bool, } ``` The output is: ``` serde and Default match! ``` Note that tha above problem only manifests when using field level annotations. If you are using container level `#[serde(default)]` then the regular `#[derive(Default)]` or a manual implementation of `Default` will work as expected. serde_derive_default-0.1.1/examples/example.rs000064400000000000000000000012041046102023000176030ustar 00000000000000use serde::Deserialize; #[derive(Deserialize)] struct Container { a: A, } #[derive(Deserialize)] struct A { #[serde(default)] b: B, } #[derive(Deserialize, serde_derive_default::Default)] struct B { #[serde(default = "true_fn")] c: bool, } fn true_fn() -> bool { true } fn main() { let container1 = serde_yaml::from_str::("a: {}").unwrap(); let container2 = serde_yaml::from_str::("a: {b: {}}").unwrap(); if container1.a.b.c == container2.a.b.c { println!("serde and Default match!"); } else { println!("serde and Default do not match, this is a bug!"); } } serde_derive_default-0.1.1/renovate.json000064400000000000000000000001531046102023000165040ustar 00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" ] } serde_derive_default-0.1.1/src/lib.rs000064400000000000000000000067611046102023000157040ustar 00000000000000#[macro_use] extern crate quote; #[macro_use] extern crate syn; extern crate proc_macro; use proc_macro::TokenStream; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::Comma; use syn::{Data, DeriveInput, Expr, Ident, Lit, Meta}; #[derive(thiserror::Error, Debug)] enum Error { #[error("struct field did not have an ident")] MissingFieldIdent, #[error("enums cannot derive Default")] EnumNotSupported, #[error("unions cannot derive Default")] UnionNotSupported, #[error("failed to parse serde attribute arguments {0}")] ParseArgs(String), } #[proc_macro_derive(Default)] pub fn derive_default(input: TokenStream) -> TokenStream { // Parse the input tokens into a syntax tree let input = parse_macro_input!(input as DeriveInput); match process_input(&input) { Ok(derived_token_stream) => derived_token_stream, Err(err) => { TokenStream::from(syn::Error::new(input.span(), err.to_string()).to_compile_error()) } } } fn process_input(input: &DeriveInput) -> Result { let fields = process_data(&input.data)?; let ident = &input.ident; let fields = fields .iter() .map(|f| { let ident = &f.ident; if let Some(initializer) = &f.initializer { let initializer = Ident::new(initializer, ident.span()); quote! { #ident: #initializer() } } else { quote! { #ident: core::default::Default::default() } } }) .collect::>(); let expanded = quote! { impl Default for #ident { fn default() -> Self { Self { #(#fields),* } } } }; Ok(expanded.into()) } struct Field { ident: Ident, initializer: Option, } fn process_data(data: &Data) -> Result, Error> { match data { Data::Struct(s) => { let mut fields = Vec::new(); for field in s.fields.iter() { let ident = field.ident.as_ref().ok_or(Error::MissingFieldIdent)?; let serde = field.attrs.iter().find(|a| a.path().is_ident("serde")); if let Some(serde) = serde { let meta_list = serde .parse_args_with(Punctuated::::parse_terminated) .map_err(|e| Error::ParseArgs(e.to_string()))?; fields.push(Field { ident: ident.clone(), initializer: find_default(meta_list), }); } else { fields.push(Field { ident: ident.clone(), initializer: None, }); } } Ok(fields) } Data::Enum(_) => Err(Error::EnumNotSupported), Data::Union(_) => Err(Error::UnionNotSupported), } } fn find_default(meta_list: Punctuated) -> Option { for meta in meta_list { if let Meta::NameValue(name_value) = meta { if name_value.path.is_ident("default") { if let Expr::Lit(val) = &name_value.value { if let Lit::Str(val) = &val.lit { return Some(val.value().clone()); } } } } } None } serde_derive_default-0.1.1/tests/test.rs000064400000000000000000000013151046102023000164560ustar 00000000000000use serde::Deserialize; #[test] fn test() { #[derive(Deserialize, Eq, PartialEq, Debug)] struct Container { a: A, } #[derive(Deserialize, Eq, PartialEq, Debug)] struct A { #[serde(default)] b: B, } #[derive(Deserialize, serde_derive_default::Default, Eq, PartialEq, Debug)] struct B { #[serde(default = "true_fn")] c: bool, #[serde(rename = "e", default = "true_fn")] d: bool, } fn true_fn() -> bool { true } let container1 = serde_yaml::from_str::("a: {}").unwrap(); let container2 = serde_yaml::from_str::("a: {b: {}}").unwrap(); assert_eq!(container1, container2); } serde_derive_default-0.1.1/tests/ui/fail/enum.rs000064400000000000000000000002621046102023000177730ustar 00000000000000use serde::Deserialize; #[derive(Deserialize, serde_derive_default::Default)] enum Test { Test(usize), TestStr(String), } fn default() -> usize { 1 } fn main() {} serde_derive_default-0.1.1/tests/ui/fail/enum.stderr000064400000000000000000000001371046102023000206530ustar 00000000000000error: enums cannot derive Default --> tests/ui/fail/enum.rs:3:1 | 3 | enum Test { | ^^^^ serde_derive_default-0.1.1/tests/ui/pass/basic.rs000064400000000000000000000005031046102023000201410ustar 00000000000000use serde::Deserialize; #[derive(Deserialize, serde_derive_default::Default)] struct Test { #[serde(default = "default")] field_1: usize, #[serde(rename = "field2", default = "default", flatten)] field_2: usize, test_str: String, } fn default() -> usize { 1 } fn main() { Test::default(); } serde_derive_default-0.1.1/tests/ui.rs000064400000000000000000000002101046102023000161050ustar 00000000000000#[test] fn ui() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/fail/*.rs"); t.pass("tests/ui/pass/*.rs"); }