From 40edf85c35a0b92295927309a550639424594370 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 9 Oct 2023 14:26:41 -0700 Subject: [PATCH 1/9] Update deps. --- Cargo.lock | 148 ++++++++++++++++++++++----------------- Cargo.toml | 6 +- crates/config/Cargo.toml | 10 +-- crates/macros/Cargo.toml | 4 +- 4 files changed, 95 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 445b3ccf..cbad067a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,7 +147,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -707,7 +707,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -718,7 +718,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -919,9 +919,9 @@ dependencies = [ [[package]] name = "extism" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25944ee40692db74f53a65804c08f23b88c4f2b0903f3098e94bfcf6cbfd3d0f" +checksum = "7a94848d5b49906bd97b83cf5a8bd25082dbc6f8bdfe98f12687910228734552" dependencies = [ "anyhow", "extism-manifest", @@ -943,9 +943,9 @@ dependencies = [ [[package]] name = "extism-runtime" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b284fa52d9c9eed7c26bfaa348183f1c83f08e080859a392b481c70f3c0f848e" +checksum = "a3b0ba8ef6ecbf59c0f6e47fd2feea575ebc3a09e81603d06a41af92fe61cdfa" dependencies = [ "anyhow", "cbindgen", @@ -1190,7 +1190,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1294,9 +1294,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" dependencies = [ "ahash 0.8.3", ] @@ -1495,12 +1495,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.1", ] [[package]] @@ -1817,7 +1817,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1935,7 +1935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "169e72192bc84493725bfca5d47da4ee2abc8acf155586d8127eb17f022497f4" dependencies = [ "ahash 0.8.3", - "hashbrown 0.14.0", + "hashbrown 0.14.1", "parking_lot", "stable_deref_trait", ] @@ -2084,9 +2084,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -2216,13 +2216,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" dependencies = [ "aho-corasick 1.0.1", "memchr", - "regex-automata 0.3.8", + "regex-automata 0.4.0", "regex-syntax", ] @@ -2234,9 +2234,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "5d58da636bd923eae52b7e9120271cbefb16f399069ee566ca5ebf9c30e32238" dependencies = [ "aho-corasick 1.0.1", "memchr", @@ -2245,9 +2245,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c3cbb081b9784b07cceb8824c8583f86db4814d172ab043f3c23f7dc600bf83d" [[package]] name = "relative-path" @@ -2260,9 +2260,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ "base64", "bytes", @@ -2287,6 +2287,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-rustls", "tower-service", @@ -2453,7 +2454,7 @@ version = "0.11.8" dependencies = [ "chrono", "garde", - "indexmap 2.0.0", + "indexmap 2.0.2", "miette", "regex", "relative-path", @@ -2471,7 +2472,7 @@ dependencies = [ "starbase_sandbox", "starbase_styles", "thiserror", - "toml 0.8.0", + "toml 0.8.2", "tracing", "url", "version_spec", @@ -2486,7 +2487,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2578,7 +2579,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2641,7 +2642,7 @@ version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.0.2", "itoa", "ryu", "serde", @@ -2670,14 +2671,14 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -2801,9 +2802,9 @@ dependencies = [ [[package]] name = "starbase_styles" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251a45da918b41577ab62fb764c9683a0f50fb2b8999a629dbd6c25476a9c411" +checksum = "d5d5d3e494c96659752fd79227acff36f375d6f113cb5460acdfdfe9a5b95a0d" dependencies = [ "dirs 5.0.1", "owo-colors", @@ -2828,7 +2829,7 @@ dependencies = [ "starbase_styles", "thiserror", "tokio", - "toml 0.8.0", + "toml 0.8.2", "tracing", "wax", ] @@ -2847,9 +2848,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "supports-color" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354" +checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" dependencies = [ "is-terminal", "is_ci", @@ -2886,15 +2887,36 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-interface" version = "0.25.9" @@ -2998,22 +3020,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3115,14 +3137,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.0", + "toml_edit 0.20.2", ] [[package]] @@ -3140,7 +3162,7 @@ version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.0.2", "serde", "serde_spanned", "toml_datetime", @@ -3149,11 +3171,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.0.2", "serde", "serde_spanned", "toml_datetime", @@ -3187,7 +3209,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3344,9 +3366,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "version_spec" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf76e050d169eab93ec6369ec26d60748d48f2f2edb59ed8f357e7d7242e809c" +checksum = "5babc3fe9e74e0559d8f8a825b6dcfec6f0dbf04db30926eaaf3caddcb4fb8ef" dependencies = [ "human-sort", "once_cell", @@ -3386,9 +3408,9 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbf79662d393831516fe10316f9613a4810db388adf86a57c763cf72b32e68b" +checksum = "b86b10e880209a6cf820f5c33101a4c7e98f5121ff8466ab0f2ba517e656d514" dependencies = [ "extism", "miette", @@ -3409,9 +3431,9 @@ dependencies = [ [[package]] name = "warpgate_api" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87f4f62fa42fa715ce697387b4c5c66ae0380723b841b1e0ff9fd285c78ab5f" +checksum = "88d55e21fc85ba24a15b008acf53638354e81d14ba966bb909918271b1f0138d" dependencies = [ "serde", ] @@ -3487,7 +3509,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -3521,7 +3543,7 @@ checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3566,7 +3588,7 @@ version = "0.113.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a128cea7b8516703ab41b10a0b1aa9ba18d0454cd3792341489947ddeee268db" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.0.2", "semver", ] diff --git a/Cargo.toml b/Cargo.toml index 4e7f22b8..02e1c350 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,10 @@ members = ["crates/*"] [workspace.dependencies] chrono = "0.4.31" miette = "5.10.0" -regex = "1.9.5" +regex = "1.10.0" relative-path = "1.9.0" semver = "1.0.19" serde = { version = "1.0.188", features = ["derive"] } url = "2.4.1" -version_spec = "0.1.0" -warpgate = "0.5.7" +version_spec = "0.1.1" +warpgate = "0.5.9" diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 56970fe7..eb37f048 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -19,15 +19,15 @@ all-features = true schematic_macros = { version = "0.11.7", path = "../macros" } schematic_types = { version = "0.4.7", path = "../types" } garde = { version = "0.15.0", default-features = false, features = ["regex"] } -indexmap = "2.0.0" +indexmap = "2.0.2" miette = { workspace = true } -reqwest = { version = "0.11.20", default-features = false, features = [ +reqwest = { version = "0.11.22", default-features = false, features = [ "blocking", ], optional = true } serde = { workspace = true } serde_path_to_error = "0.1.14" -starbase_styles = "0.1.15" -thiserror = "1.0.48" +starbase_styles = "0.1.16" +thiserror = "1.0.49" tracing = "0.1.37" # json @@ -37,7 +37,7 @@ serde_json = { version = "1.0.107", optional = true } schemars = { version = "0.8.15", optional = true, default-features = false } # toml -toml = { version = "0.8.0", optional = true } +toml = { version = "0.8.2", optional = true } # yaml serde_yaml = { version = "0.9.25", optional = true } diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 784a76f6..d757d401 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -15,9 +15,9 @@ proc-macro = true [dependencies] convert_case = "0.6.0" darling = "0.20.3" -proc-macro2 = "1.0.67" +proc-macro2 = "1.0.69" quote = "1.0.33" -syn = { version = "2.0.37", features = ["full"] } +syn = { version = "2.0.38", features = ["full"] } [features] default = [] From 8bc374990641605ac959c698e0fc0bcfb5ad79be Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 9 Oct 2023 14:56:45 -0700 Subject: [PATCH 2/9] Migrate variant. --- crates/macros/src/common/container.rs | 0 crates/macros/src/common/field.rs | 0 crates/macros/src/common/mod.rs | 5 + crates/macros/src/common/variant.rs | 247 +++++++++++++++++++++++ crates/macros/src/common_schema.rs | 25 +++ crates/macros/src/config/config.rs | 27 +-- crates/macros/src/config/mod.rs | 3 +- crates/macros/src/config/variant.rs | 245 +--------------------- crates/macros/src/config_enum/mod.rs | 14 +- crates/macros/src/config_enum/variant.rs | 13 +- crates/macros/src/lib.rs | 14 ++ crates/macros/src/schematic/mod.rs | 20 ++ 12 files changed, 322 insertions(+), 291 deletions(-) create mode 100644 crates/macros/src/common/container.rs create mode 100644 crates/macros/src/common/field.rs create mode 100644 crates/macros/src/common/mod.rs create mode 100644 crates/macros/src/common/variant.rs create mode 100644 crates/macros/src/common_schema.rs create mode 100644 crates/macros/src/schematic/mod.rs diff --git a/crates/macros/src/common/container.rs b/crates/macros/src/common/container.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/macros/src/common/field.rs b/crates/macros/src/common/field.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/macros/src/common/mod.rs b/crates/macros/src/common/mod.rs new file mode 100644 index 00000000..9e19d0f7 --- /dev/null +++ b/crates/macros/src/common/mod.rs @@ -0,0 +1,5 @@ +mod container; +mod field; +mod variant; + +pub use variant::*; diff --git a/crates/macros/src/common/variant.rs b/crates/macros/src/common/variant.rs new file mode 100644 index 00000000..003c2042 --- /dev/null +++ b/crates/macros/src/common/variant.rs @@ -0,0 +1,247 @@ +use crate::utils::{extract_common_attrs, format_case}; +use darling::FromAttributes; +use proc_macro2::{Ident, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{Attribute, Expr, ExprPath, Fields, Variant as NativeVariant}; + +pub enum TaggedFormat { + Untagged, + External, + Internal(String), + Adjacent(String, String), +} + +// #[serde()] +#[derive(FromAttributes, Default)] +#[darling(default, allow_unknown_fields, attributes(serde))] +pub struct VariantSerdeArgs { + pub alias: Option, + pub rename: Option, + pub skip: bool, +} + +// #[setting()], #[variant()] +#[derive(FromAttributes, Default)] +#[darling(default, attributes(setting, variant))] +pub struct VariantArgs { + pub null: bool, + + // config + pub default: bool, + pub merge: Option, + pub nested: bool, + pub validate: Option, + + // serde + pub rename: Option, + pub skip: bool, +} + +pub struct Variant<'l> { + pub args: VariantArgs, + pub serde_args: VariantSerdeArgs, + pub attrs: Vec<&'l Attribute>, + pub name: &'l Ident, + pub value: &'l NativeVariant, +} + +impl<'l> Variant<'l> { + pub fn from(var: &NativeVariant) -> Variant { + Variant { + args: VariantArgs::from_attributes(&var.attrs).unwrap_or_default(), + serde_args: VariantSerdeArgs::from_attributes(&var.attrs).unwrap_or_default(), + attrs: extract_common_attrs(&var.attrs), + name: &var.ident, + value: var, + } + } + + pub fn is_default(&self) -> bool { + self.args.default + } + + pub fn is_nested(&self) -> bool { + self.args.nested + } + + pub fn get_name(&self, casing_format: Option<&str>) -> String { + if let Some(local) = &self.args.rename { + local.to_owned() + } else if let Some(serde) = &self.serde_args.rename { + serde.to_owned() + } else if let Some(format) = casing_format { + format_case(format, &self.name.to_string(), true) + } else { + self.name.to_string() + } + } + + pub fn get_serde_meta(&self) -> Option { + let mut meta = vec![]; + + if let Some(alias) = &self.serde_args.alias { + meta.push(quote! { alias = #alias }); + } + + if let Some(rename) = &self.args.rename { + meta.push(quote! { rename = #rename }); + } else if let Some(rename) = &self.serde_args.rename { + meta.push(quote! { rename = #rename }); + } + + if self.args.skip || self.serde_args.skip { + meta.push(quote! { skip }); + } + + if meta.is_empty() { + return None; + } + + Some(quote! { + #(#meta),* + }) + } + + pub fn generate_schema_type( + &self, + casing_format: &str, + tagged_format: &TaggedFormat, + ) -> TokenStream { + let name = self.get_name(Some(casing_format)); + let untagged = matches!(tagged_format, TaggedFormat::Untagged); + let partial = self.is_nested(); + + let inner = match &self.value.fields { + Fields::Named(_) => unreachable!(), + Fields::Unnamed(fields) => { + if self.args.null { + panic!("Only unit variants can be marked as `null`."); + } + + let fields = fields + .unnamed + .iter() + .map(|field| { + let ty = &field.ty; + + if partial { + quote! { SchemaType::infer_partial::<#ty>() } + } else { + quote! { SchemaType::infer::<#ty>() } + } + }) + .collect::>(); + + if fields.len() == 1 { + let inner = &fields[0]; + + quote! { #inner } + } else { + quote! { + SchemaType::tuple([ + #(#fields),* + ]) + } + } + } + Fields::Unit => { + if self.args.null || untagged { + quote! { + SchemaType::Null + } + } else { + quote! { + SchemaType::literal(LiteralValue::String(#name.into())) + } + } + } + }; + + let outer = match tagged_format { + TaggedFormat::Untagged => inner, + TaggedFormat::External => { + quote! { + SchemaType::structure([ + SchemaField::new(#name, #inner), + ]) + } + } + TaggedFormat::Internal(tag) => { + return quote! { + { + let mut schema = #inner; + schema.add_field(SchemaField::new(#tag, SchemaType::literal(LiteralValue::String(#name.into())))); + schema.set_partial(#partial); + schema + } + }; + } + TaggedFormat::Adjacent(tag, content) => { + quote! { + SchemaType::structure([ + SchemaField::new(#tag, SchemaType::literal(LiteralValue::String(#name.into()))), + SchemaField::new(#content, #inner), + ]) + } + } + }; + + if partial { + quote! { + { + let mut schema = #outer; + schema.set_partial(#partial); + schema + } + } + } else { + outer + } + } +} + +impl<'l> ToTokens for Variant<'l> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = self.name; + + // Gather all attributes + let mut attrs = vec![]; + + if let Some(serde_meta) = self.get_serde_meta() { + attrs.push(quote! { #[serde(#serde_meta)] }); + } + + for attr in &self.attrs { + attrs.push(quote! { #attr }); + } + + tokens.extend(match &self.value.fields { + Fields::Named(_) => unreachable!(), + Fields::Unnamed(fields) => { + let fields = fields + .unnamed + .iter() + .map(|field| { + let vis = &field.vis; + let ty = &field.ty; + + if self.is_nested() { + quote! { #vis <#ty as schematic::Config>::Partial } + } else { + quote! { #vis #ty } + } + }) + .collect::>(); + + quote! { + #(#attrs)* + #name(#(#fields),*), + } + } + Fields::Unit => quote! { + #(#attrs)* + #name, + }, + }); + } +} diff --git a/crates/macros/src/common_schema.rs b/crates/macros/src/common_schema.rs new file mode 100644 index 00000000..258d5b11 --- /dev/null +++ b/crates/macros/src/common_schema.rs @@ -0,0 +1,25 @@ +use darling::{FromDeriveInput, FromMeta}; + +// #[serde()] +#[derive(FromDeriveInput, Default)] +#[darling(default, allow_unknown_fields, attributes(serde))] +pub struct SerdeArgs { + // struct + pub rename: Option, + pub rename_all: Option, + + // enum + pub content: Option, + pub expecting: Option, + pub tag: Option, + pub untagged: bool, +} + +#[derive(FromMeta, Default)] +#[darling(default)] +pub struct SerdeMeta { + pub content: Option, + pub expecting: Option, + pub tag: Option, + pub untagged: bool, +} diff --git a/crates/macros/src/config/config.rs b/crates/macros/src/config/config.rs index d78a74cf..78078fab 100644 --- a/crates/macros/src/config/config.rs +++ b/crates/macros/src/config/config.rs @@ -1,34 +1,11 @@ use super::config_type::ConfigType; use super::variant::TaggedFormat; -use darling::{FromDeriveInput, FromMeta}; +use crate::common_schema::*; +use darling::FromDeriveInput; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, ToTokens}; use syn::{Attribute, ExprPath}; -// #[serde()] -#[derive(FromDeriveInput, Default)] -#[darling(default, allow_unknown_fields, attributes(serde))] -pub struct SerdeArgs { - // struct - rename: Option, - rename_all: Option, - - // enum - content: Option, - expecting: Option, - tag: Option, - untagged: bool, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct SerdeMeta { - content: Option, - expecting: Option, - tag: Option, - untagged: bool, -} - // #[config()] #[derive(FromDeriveInput, Default)] #[darling(default, attributes(config), supports(struct_named, enum_any))] diff --git a/crates/macros/src/config/mod.rs b/crates/macros/src/config/mod.rs index 5a30cb3e..e959c9f2 100644 --- a/crates/macros/src/config/mod.rs +++ b/crates/macros/src/config/mod.rs @@ -5,7 +5,8 @@ pub mod setting; pub mod setting_type; pub mod variant; -use crate::config::config::{Config, ConfigArgs, SerdeArgs}; +use crate::common_schema::SerdeArgs; +use crate::config::config::{Config, ConfigArgs}; use crate::config::config_type::ConfigType; use crate::config::setting::Setting; use crate::config::variant::Variant; diff --git a/crates/macros/src/config/variant.rs b/crates/macros/src/config/variant.rs index a59ae76f..bcfc1d90 100644 --- a/crates/macros/src/config/variant.rs +++ b/crates/macros/src/config/variant.rs @@ -1,105 +1,9 @@ -use crate::utils::{extract_common_attrs, format_case}; -use darling::FromAttributes; +pub use crate::common::*; use proc_macro2::{Ident, TokenStream}; -use quote::{format_ident, quote, ToTokens}; -use syn::{Attribute, Expr, ExprPath, Fields, FieldsUnnamed, Variant as NativeVariant}; - -pub enum TaggedFormat { - Untagged, - External, - Internal(String), - Adjacent(String, String), -} - -// #[serde()] -#[derive(FromAttributes, Default)] -#[darling(default, allow_unknown_fields, attributes(serde))] -pub struct SerdeArgs { - pub alias: Option, - pub rename: Option, - pub skip: bool, -} - -// #[variant()] -#[derive(FromAttributes, Default)] -#[darling(default, attributes(setting))] -pub struct VariantArgs { - pub default: bool, - pub merge: Option, - pub nested: bool, - pub null: bool, - pub validate: Option, - - // serde - pub rename: Option, - pub skip: bool, -} - -pub struct Variant<'l> { - pub args: VariantArgs, - pub serde_args: SerdeArgs, - pub attrs: Vec<&'l Attribute>, - pub name: &'l Ident, - pub value: &'l NativeVariant, -} +use quote::{format_ident, quote}; +use syn::{Expr, Fields, FieldsUnnamed}; impl<'l> Variant<'l> { - pub fn from(var: &NativeVariant) -> Variant { - Variant { - args: VariantArgs::from_attributes(&var.attrs).unwrap_or_default(), - serde_args: SerdeArgs::from_attributes(&var.attrs).unwrap_or_default(), - attrs: extract_common_attrs(&var.attrs), - name: &var.ident, - value: var, - } - } - - pub fn is_default(&self) -> bool { - self.args.default - } - - pub fn is_nested(&self) -> bool { - self.args.nested - } - - pub fn get_name(&self, casing_format: Option<&str>) -> String { - if let Some(local) = &self.args.rename { - local.to_owned() - } else if let Some(serde) = &self.serde_args.rename { - serde.to_owned() - } else if let Some(format) = casing_format { - format_case(format, &self.name.to_string(), true) - } else { - self.name.to_string() - } - } - - pub fn get_serde_meta(&self) -> Option { - let mut meta = vec![]; - - if let Some(alias) = &self.serde_args.alias { - meta.push(quote! { alias = #alias }); - } - - if let Some(rename) = &self.args.rename { - meta.push(quote! { rename = #rename }); - } else if let Some(rename) = &self.serde_args.rename { - meta.push(quote! { rename = #rename }); - } - - if self.args.skip || self.serde_args.skip { - meta.push(quote! { skip }); - } - - if meta.is_empty() { - return None; - } - - Some(quote! { - #(#meta),* - }) - } - pub fn generate_default_value(&self) -> TokenStream { let name = &self.name; @@ -327,103 +231,6 @@ impl<'l> Variant<'l> { } } - pub fn generate_schema_type( - &self, - casing_format: &str, - tagged_format: &TaggedFormat, - ) -> TokenStream { - let name = self.get_name(Some(casing_format)); - let untagged = matches!(tagged_format, TaggedFormat::Untagged); - let partial = self.is_nested(); - - let inner = match &self.value.fields { - Fields::Named(_) => unreachable!(), - Fields::Unnamed(fields) => { - if self.args.null { - panic!("Only unit variants can be marked as `null`."); - } - - let fields = fields - .unnamed - .iter() - .map(|field| { - let ty = &field.ty; - - if partial { - quote! { SchemaType::infer_partial::<#ty>() } - } else { - quote! { SchemaType::infer::<#ty>() } - } - }) - .collect::>(); - - if fields.len() == 1 { - let inner = &fields[0]; - - quote! { #inner } - } else { - quote! { - SchemaType::tuple([ - #(#fields),* - ]) - } - } - } - Fields::Unit => { - if self.args.null || untagged { - quote! { - SchemaType::Null - } - } else { - quote! { - SchemaType::literal(LiteralValue::String(#name.into())) - } - } - } - }; - - let outer = match tagged_format { - TaggedFormat::Untagged => inner, - TaggedFormat::External => { - quote! { - SchemaType::structure([ - SchemaField::new(#name, #inner), - ]) - } - } - TaggedFormat::Internal(tag) => { - return quote! { - { - let mut schema = #inner; - schema.add_field(SchemaField::new(#tag, SchemaType::literal(LiteralValue::String(#name.into())))); - schema.set_partial(#partial); - schema - } - }; - } - TaggedFormat::Adjacent(tag, content) => { - quote! { - SchemaType::structure([ - SchemaField::new(#tag, SchemaType::literal(LiteralValue::String(#name.into()))), - SchemaField::new(#content, #inner), - ]) - } - } - }; - - if partial { - quote! { - { - let mut schema = #outer; - schema.set_partial(#partial); - schema - } - } - } else { - outer - } - } - fn map_unnamed_match(&self, name: &Ident, fields: &FieldsUnnamed, factory: F) -> TokenStream where F: FnOnce(&[Ident], &[Ident]) -> TokenStream, @@ -471,49 +278,3 @@ impl<'l> Variant<'l> { } } } - -impl<'l> ToTokens for Variant<'l> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let name = self.name; - - // Gather all attributes - let mut attrs = vec![]; - - if let Some(serde_meta) = self.get_serde_meta() { - attrs.push(quote! { #[serde(#serde_meta)] }); - } - - for attr in &self.attrs { - attrs.push(quote! { #attr }); - } - - tokens.extend(match &self.value.fields { - Fields::Named(_) => unreachable!(), - Fields::Unnamed(fields) => { - let fields = fields - .unnamed - .iter() - .map(|field| { - let vis = &field.vis; - let ty = &field.ty; - - if self.is_nested() { - quote! { #vis <#ty as schematic::Config>::Partial } - } else { - quote! { #vis #ty } - } - }) - .collect::>(); - - quote! { - #(#attrs)* - #name(#(#fields),*), - } - } - Fields::Unit => quote! { - #(#attrs)* - #name, - }, - }); - } -} diff --git a/crates/macros/src/config_enum/mod.rs b/crates/macros/src/config_enum/mod.rs index f5a4754f..35e51c8a 100644 --- a/crates/macros/src/config_enum/mod.rs +++ b/crates/macros/src/config_enum/mod.rs @@ -1,24 +1,12 @@ mod variant; +use crate::common_schema::SerdeArgs; use crate::config_enum::variant::Variant; use darling::FromDeriveInput; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput}; -// #[serde()] -#[derive(FromDeriveInput, Default)] -#[darling( - default, - allow_unknown_fields, - attributes(serde), - supports(enum_unit, enum_tuple) -)] -pub struct SerdeArgs { - rename: Option, - rename_all: Option, -} - // #[config()] #[derive(FromDeriveInput, Default)] #[darling(default, attributes(config), supports(enum_unit, enum_tuple))] diff --git a/crates/macros/src/config_enum/variant.rs b/crates/macros/src/config_enum/variant.rs index f8d1d17f..c61b6024 100644 --- a/crates/macros/src/config_enum/variant.rs +++ b/crates/macros/src/config_enum/variant.rs @@ -1,17 +1,10 @@ +use crate::common::VariantSerdeArgs; use crate::utils::{extract_comment, extract_common_attrs, format_case, has_attr}; use darling::FromAttributes; use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{Attribute, Fields, Variant as NativeVariant}; -// #[serde()] -#[derive(FromAttributes, Default)] -#[darling(default, allow_unknown_fields, attributes(serde))] -pub struct SerdeArgs { - pub alias: Option, - pub rename: Option, -} - // #[variant()] #[derive(FromAttributes, Default)] #[darling(default, attributes(variant))] @@ -22,7 +15,7 @@ pub struct VariantArgs { pub struct Variant<'l> { pub args: VariantArgs, - pub serde_args: SerdeArgs, + pub serde_args: VariantSerdeArgs, pub attrs: Vec<&'l Attribute>, pub name: &'l Ident, pub value: String, @@ -31,7 +24,7 @@ pub struct Variant<'l> { impl<'l> Variant<'l> { pub fn from<'n>(variant: &'n NativeVariant, format: &str) -> Variant<'n> { let args = VariantArgs::from_attributes(&variant.attrs).unwrap_or_default(); - let serde_args = SerdeArgs::from_attributes(&variant.attrs).unwrap_or_default(); + let serde_args = VariantSerdeArgs::from_attributes(&variant.attrs).unwrap_or_default(); if args.fallback { if let Fields::Unnamed(fields) = &variant.fields { diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 77b2cf22..4065c7f1 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -1,15 +1,29 @@ +mod common; +mod common_schema; mod config; mod config_enum; mod utils; +#[cfg(feature = "schema")] +mod schematic; + use proc_macro::TokenStream; +// #[derive(Config)] #[proc_macro_derive(Config, attributes(config, setting))] pub fn config(item: TokenStream) -> TokenStream { config::macro_impl(item) } +// #[derive(ConfigEnum)] #[proc_macro_derive(ConfigEnum, attributes(config, variant))] pub fn config_enum(item: TokenStream) -> TokenStream { config_enum::macro_impl(item) } + +// // #[derive(Schematic)] +// #[cfg(feature = "schema")] +// #[proc_macro_derive(Schematic, attributes(schematic))] +// pub fn schematic(item: TokenStream) -> TokenStream { +// schematic::macro_impl(item) +// } diff --git a/crates/macros/src/schematic/mod.rs b/crates/macros/src/schematic/mod.rs new file mode 100644 index 00000000..447d24a7 --- /dev/null +++ b/crates/macros/src/schematic/mod.rs @@ -0,0 +1,20 @@ +// use crate::common_schema::*; +// use darling::{FromDeriveInput, FromMeta}; +// use proc_macro2::{Ident, TokenStream}; +// use quote::{format_ident, quote, ToTokens}; +// use syn::{Attribute, ExprPath}; + +// // #[schematic()] +// #[derive(FromDeriveInput, Default)] +// #[darling(default, attributes(schematic), supports(struct_named, enum_any))] +// pub struct SchematicArgs { +// allow_unknown_fields: bool, +// context: Option, +// env_prefix: Option, +// file: Option, + +// // serde +// rename: Option, +// rename_all: Option, +// serde: SerdeMeta, +// } From d3491dae4cbc5e8cc418711a713e26bc5aadf9ce Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 9 Oct 2023 15:23:02 -0700 Subject: [PATCH 3/9] Migrate field. --- crates/macros/src/common/field.rs | 206 ++++++++++++++ crates/macros/src/common/field_value.rs | 157 +++++++++++ crates/macros/src/common/mod.rs | 3 + crates/macros/src/common/variant.rs | 14 +- crates/macros/src/common_schema.rs | 13 +- crates/macros/src/config/config.rs | 4 +- crates/macros/src/config/config_type.rs | 5 +- crates/macros/src/config/mod.rs | 11 +- crates/macros/src/config/setting.rs | 222 +-------------- crates/macros/src/config/setting_type.rs | 317 ---------------------- crates/macros/src/config/setting_value.rs | 164 +++++++++++ crates/macros/src/config/variant.rs | 2 +- crates/macros/src/config_enum/mod.rs | 4 +- crates/macros/src/config_enum/variant.rs | 6 +- 14 files changed, 565 insertions(+), 563 deletions(-) create mode 100644 crates/macros/src/common/field_value.rs delete mode 100644 crates/macros/src/config/setting_type.rs create mode 100644 crates/macros/src/config/setting_value.rs diff --git a/crates/macros/src/common/field.rs b/crates/macros/src/common/field.rs index e69de29b..38a6690a 100644 --- a/crates/macros/src/common/field.rs +++ b/crates/macros/src/common/field.rs @@ -0,0 +1,206 @@ +use crate::common::field_value::FieldValue; +use crate::common_schema::FieldSerdeArgs; +use crate::utils::{ + extract_comment, extract_common_attrs, format_case, has_attr, preserve_str_literal, +}; +use darling::FromAttributes; +use proc_macro2::{Ident, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{Attribute, Expr, ExprPath, Field as NativeField, Lit, Type}; + +// #[field()], #[setting()] +#[derive(FromAttributes, Default)] +#[darling(default, attributes(field, setting))] +pub struct FieldArgs { + // config + #[darling(with = "preserve_str_literal", map = "Some")] + pub default: Option, + pub env: Option, + pub extend: bool, + pub merge: Option, + pub nested: bool, + pub parse_env: Option, + pub validate: Option, + + // serde + pub rename: Option, + pub skip: bool, +} + +pub struct Field<'l> { + pub args: FieldArgs, + pub serde_args: FieldSerdeArgs, + pub attrs: Vec<&'l Attribute>, + pub name: &'l Ident, + pub value: &'l Type, + pub value_type: FieldValue<'l>, +} + +impl<'l> Field<'l> { + pub fn from(field: &NativeField) -> Field { + let args = FieldArgs::from_attributes(&field.attrs).unwrap_or_default(); + let serde_args = FieldSerdeArgs::from_attributes(&field.attrs).unwrap_or_default(); + + let field = Field { + name: field.ident.as_ref().unwrap(), + attrs: extract_common_attrs(&field.attrs), + value: &field.ty, + value_type: if args.nested { + FieldValue::nested(&field.ty) + } else { + FieldValue::value(&field.ty) + }, + args, + serde_args, + }; + + if field.args.default.is_some() { + if field.is_nested() { + panic!("Cannot use defaults with `nested` configs."); + } + + if field.is_optional() { + panic!("Cannot use defaults with optional settings."); + } + } + + field + } + + pub fn is_extendable(&self) -> bool { + self.args.extend + } + + pub fn is_nested(&self) -> bool { + self.args.nested + } + + pub fn is_optional(&self) -> bool { + self.value_type.is_optional() + } + + pub fn is_skipped(&self) -> bool { + self.args.skip || self.serde_args.skip + } + + pub fn get_name(&self, casing_format: Option<&str>) -> String { + if let Some(local) = &self.args.rename { + local.to_owned() + } else if let Some(serde) = &self.serde_args.rename { + serde.to_owned() + } else if let Some(format) = casing_format { + format_case(format, &self.name.to_string(), false) + } else { + self.name.to_string() + } + } + + pub fn get_serde_meta(&self) -> Option { + let mut meta = vec![]; + + if let Some(rename) = &self.args.rename { + meta.push(quote! { rename = #rename }); + } else if let Some(rename) = &self.serde_args.rename { + meta.push(quote! { rename = #rename }); + } + + if self.args.skip || self.serde_args.skip { + meta.push(quote! { skip }); + } else { + meta.push(quote! { skip_serializing_if = "Option::is_none" }); + } + + if meta.is_empty() { + return None; + } + + Some(quote! { + #(#meta),* + }) + } + + pub fn generate_schema_type(&self, casing_format: &str) -> TokenStream { + let name = self.get_name(Some(casing_format)); + let value = self.value; + + let deprecated = has_attr(&self.attrs, "deprecated"); + let hidden = self.is_skipped(); + let nullable = self.is_optional(); + let partial = self.is_nested(); + + let description = if let Some(comment) = extract_comment(&self.attrs) { + quote! { + Some(#comment.into()) + } + } else { + quote! { + None + } + }; + + let mut type_of = if partial { + quote! { SchemaType::infer_partial::<#value>() } + } else { + quote! { SchemaType::infer::<#value>() } + }; + + if let Some(Expr::Lit(lit)) = &self.args.default { + let lit_value = match &lit.lit { + Lit::Str(v) => quote! { LiteralValue::String(#v.into()) }, + Lit::Int(v) => { + if v.suffix().starts_with('u') { + quote! { LiteralValue::Uint(#v) } + } else { + quote! { LiteralValue::Int(#v) } + } + } + Lit::Float(v) => { + if v.suffix() == "f32" { + quote! { LiteralValue::F32(#v) } + } else { + quote! { LiteralValue::F64(#v) } + } + } + Lit::Bool(v) => quote! { LiteralValue::Bool(#v) }, + _ => unimplemented!(), + }; + + type_of = quote! { SchemaType::infer_with_default::<#value>(#lit_value) }; + } + + quote! { + SchemaField { + name: Some(#name.into()), + description: #description, + type_of: #type_of, + deprecated: #deprecated, + hidden: #hidden, + nullable: #nullable, + ..Default::default() + } + } + } +} + +impl<'l> ToTokens for Field<'l> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = self.name; + let value = &self.value_type; + + // Gather all attributes + let mut attrs = vec![]; + + if let Some(serde_meta) = self.get_serde_meta() { + attrs.push(quote! { #[serde(#serde_meta)] }); + } + + for attr in &self.attrs { + attrs.push(quote! { #attr }); + } + + tokens.extend(quote! { + #(#attrs)* + pub #name: #value, + }); + } +} diff --git a/crates/macros/src/common/field_value.rs b/crates/macros/src/common/field_value.rs new file mode 100644 index 00000000..1811311c --- /dev/null +++ b/crates/macros/src/common/field_value.rs @@ -0,0 +1,157 @@ +use crate::utils::unwrap_option; +use proc_macro2::{Ident, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{GenericArgument, PathArguments, Type, TypePath}; + +fn get_option_inner(path: &TypePath) -> Option<&TypePath> { + let last_segment = path.path.segments.last().unwrap(); + + if last_segment.ident != "Option" { + return None; + } + + let PathArguments::AngleBracketed(args) = &last_segment.arguments else { + return None; + }; + + let GenericArgument::Type(arg) = &args.args[0] else { + return None; + }; + + match &arg { + Type::Path(t) => Some(t), + _ => None, + } +} + +pub enum FieldValue<'l> { + // Vec + NestedList { + collection: &'l Ident, + item: &'l GenericArgument, + optional: bool, + path: &'l TypePath, + }, + // HashMap + NestedMap { + collection: &'l Ident, + key: &'l GenericArgument, + optional: bool, + path: &'l TypePath, + value: &'l GenericArgument, + }, + // config + NestedValue { + config: &'l Ident, + optional: bool, + path: &'l TypePath, + }, + // value + Value { + optional: bool, + value: &'l Type, + }, +} + +impl<'l> FieldValue<'l> { + pub fn nested(raw: &'l Type) -> FieldValue { + let mut optional = false; + + let Type::Path(raw_path) = raw else { + panic!("Nested values may only be paths/type references."); + }; + + let path = if let Some(unwrapped_path) = get_option_inner(raw_path) { + optional = true; + unwrapped_path + } else { + raw_path + }; + + let segment = path.path.segments.last().unwrap(); + let container = &segment.ident; + + match &segment.arguments { + PathArguments::None => Self::NestedValue { + path, + config: container, + optional, + }, + PathArguments::AngleBracketed(args) => match container.to_string().as_str() { + "Vec" | "HashSet" | "FxHashSet" | "BTreeSet" => Self::NestedList { + collection: container, + item: args.args.first().unwrap(), + optional, + path, + }, + "HashMap" | "FxHashMap" | "BTreeMap" => Self::NestedMap { + collection: container, + key: args.args.first().unwrap(), + optional, + path, + value: args.args.last().unwrap(), + }, + _ => panic!("Unsupported collection used with nested config."), + }, + _ => panic!("Parens are not supported for nested config."), + } + } + + pub fn value(raw: &'l Type) -> FieldValue { + let mut optional = false; + + let value = if let Some(unwrapped_value) = unwrap_option(raw) { + optional = true; + unwrapped_value + } else { + raw + }; + + Self::Value { value, optional } + } + + pub fn is_optional(&self) -> bool { + match self { + Self::NestedValue { optional, .. } => *optional, + Self::NestedList { optional, .. } => *optional, + Self::NestedMap { optional, .. } => *optional, + Self::Value { optional, .. } => *optional, + } + } + + pub fn get_inner_type(&self) -> Option<&'l Type> { + match self { + Self::Value { value, .. } => Some(value), + _ => None, + } + } +} + +// Only used for partials +impl<'l> ToTokens for FieldValue<'l> { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(match self { + Self::NestedList { + collection, item, .. + } => { + quote! { Option<#collection<<#item as schematic::Config>::Partial>> } + } + Self::NestedMap { + collection, + key, + value, + .. + } => { + quote! { + Option<#collection<#key, <#value as schematic::Config>::Partial>> + } + } + Self::NestedValue { path, .. } => { + quote! { Option<<#path as schematic::Config>::Partial> } + } + Self::Value { value, .. } => { + quote! { Option<#value> } + } + }) + } +} diff --git a/crates/macros/src/common/mod.rs b/crates/macros/src/common/mod.rs index 9e19d0f7..c59d9625 100644 --- a/crates/macros/src/common/mod.rs +++ b/crates/macros/src/common/mod.rs @@ -1,5 +1,8 @@ mod container; mod field; +mod field_value; mod variant; +pub use field::*; +pub use field_value::*; pub use variant::*; diff --git a/crates/macros/src/common/variant.rs b/crates/macros/src/common/variant.rs index 003c2042..673144d8 100644 --- a/crates/macros/src/common/variant.rs +++ b/crates/macros/src/common/variant.rs @@ -1,3 +1,4 @@ +use crate::common_schema::FieldSerdeArgs; use crate::utils::{extract_common_attrs, format_case}; use darling::FromAttributes; use proc_macro2::{Ident, TokenStream}; @@ -11,15 +12,6 @@ pub enum TaggedFormat { Adjacent(String, String), } -// #[serde()] -#[derive(FromAttributes, Default)] -#[darling(default, allow_unknown_fields, attributes(serde))] -pub struct VariantSerdeArgs { - pub alias: Option, - pub rename: Option, - pub skip: bool, -} - // #[setting()], #[variant()] #[derive(FromAttributes, Default)] #[darling(default, attributes(setting, variant))] @@ -39,7 +31,7 @@ pub struct VariantArgs { pub struct Variant<'l> { pub args: VariantArgs, - pub serde_args: VariantSerdeArgs, + pub serde_args: FieldSerdeArgs, pub attrs: Vec<&'l Attribute>, pub name: &'l Ident, pub value: &'l NativeVariant, @@ -49,7 +41,7 @@ impl<'l> Variant<'l> { pub fn from(var: &NativeVariant) -> Variant { Variant { args: VariantArgs::from_attributes(&var.attrs).unwrap_or_default(), - serde_args: VariantSerdeArgs::from_attributes(&var.attrs).unwrap_or_default(), + serde_args: FieldSerdeArgs::from_attributes(&var.attrs).unwrap_or_default(), attrs: extract_common_attrs(&var.attrs), name: &var.ident, value: var, diff --git a/crates/macros/src/common_schema.rs b/crates/macros/src/common_schema.rs index 258d5b11..13f5c5f9 100644 --- a/crates/macros/src/common_schema.rs +++ b/crates/macros/src/common_schema.rs @@ -1,9 +1,9 @@ -use darling::{FromDeriveInput, FromMeta}; +use darling::{FromAttributes, FromDeriveInput, FromMeta}; // #[serde()] #[derive(FromDeriveInput, Default)] #[darling(default, allow_unknown_fields, attributes(serde))] -pub struct SerdeArgs { +pub struct ContainerSerdeArgs { // struct pub rename: Option, pub rename_all: Option, @@ -15,6 +15,15 @@ pub struct SerdeArgs { pub untagged: bool, } +// #[serde()] +#[derive(FromAttributes, Default)] +#[darling(default, allow_unknown_fields, attributes(serde))] +pub struct FieldSerdeArgs { + pub alias: Option, + pub rename: Option, + pub skip: bool, +} + #[derive(FromMeta, Default)] #[darling(default)] pub struct SerdeMeta { diff --git a/crates/macros/src/config/config.rs b/crates/macros/src/config/config.rs index 78078fab..3fc5c109 100644 --- a/crates/macros/src/config/config.rs +++ b/crates/macros/src/config/config.rs @@ -1,5 +1,5 @@ use super::config_type::ConfigType; -use super::variant::TaggedFormat; +use crate::common::TaggedFormat; use crate::common_schema::*; use darling::FromDeriveInput; use proc_macro2::{Ident, TokenStream}; @@ -23,7 +23,7 @@ pub struct ConfigArgs { pub struct Config<'l> { pub args: ConfigArgs, - pub serde_args: SerdeArgs, + pub serde_args: ContainerSerdeArgs, pub attrs: Vec<&'l Attribute>, pub name: &'l Ident, pub type_of: ConfigType<'l>, diff --git a/crates/macros/src/config/config_type.rs b/crates/macros/src/config/config_type.rs index 3fb90fa5..f3af76d7 100644 --- a/crates/macros/src/config/config_type.rs +++ b/crates/macros/src/config/config_type.rs @@ -1,5 +1,4 @@ -use super::setting::Setting; -use super::variant::{TaggedFormat, Variant}; +use crate::common::{Field, TaggedFormat, Variant}; use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; use syn::FieldsNamed; @@ -7,7 +6,7 @@ use syn::FieldsNamed; pub enum ConfigType<'l> { NamedStruct { fields: &'l FieldsNamed, - settings: Vec>, + settings: Vec>, }, Enum { variants: Vec>, diff --git a/crates/macros/src/config/mod.rs b/crates/macros/src/config/mod.rs index e959c9f2..63bc6c72 100644 --- a/crates/macros/src/config/mod.rs +++ b/crates/macros/src/config/mod.rs @@ -2,14 +2,13 @@ pub mod config; pub mod config_type; pub mod setting; -pub mod setting_type; +pub mod setting_value; pub mod variant; -use crate::common_schema::SerdeArgs; +use crate::common::{Field, Variant}; +use crate::common_schema::ContainerSerdeArgs; use crate::config::config::{Config, ConfigArgs}; use crate::config::config_type::ConfigType; -use crate::config::setting::Setting; -use crate::config::variant::Variant; use crate::utils::extract_common_attrs; use darling::FromDeriveInput; use proc_macro::TokenStream; @@ -20,12 +19,12 @@ use syn::{parse_macro_input, Data, DeriveInput, Fields}; pub fn macro_impl(item: TokenStream) -> TokenStream { let input: DeriveInput = parse_macro_input!(item); let args = ConfigArgs::from_derive_input(&input).expect("Failed to parse arguments."); - let serde_args = SerdeArgs::from_derive_input(&input).unwrap_or_default(); + let serde_args = ContainerSerdeArgs::from_derive_input(&input).unwrap_or_default(); let config_type = match &input.data { Data::Struct(data) => match &data.fields { Fields::Named(fields) => ConfigType::NamedStruct { - settings: fields.named.iter().map(Setting::from).collect::>(), + settings: fields.named.iter().map(Field::from).collect::>(), fields, }, Fields::Unnamed(_) => { diff --git a/crates/macros/src/config/setting.rs b/crates/macros/src/config/setting.rs index f2e40752..5992f893 100644 --- a/crates/macros/src/config/setting.rs +++ b/crates/macros/src/config/setting.rs @@ -1,110 +1,9 @@ -use crate::config::setting_type::SettingType; -use crate::utils::{ - extract_comment, extract_common_attrs, format_case, has_attr, preserve_str_literal, -}; -use darling::FromAttributes; -use proc_macro2::{Ident, TokenStream}; -use quote::{quote, ToTokens}; -use syn::{Attribute, Expr, ExprPath, Field, Lit, Type}; - -// #[serde()] -#[derive(FromAttributes, Default)] -#[darling(default, allow_unknown_fields, attributes(serde))] -pub struct SerdeArgs { - pub rename: Option, - pub skip: bool, -} - -// #[setting()] -#[derive(FromAttributes, Default)] -#[darling(default, attributes(setting))] -pub struct SettingArgs { - #[darling(with = "preserve_str_literal", map = "Some")] - pub default: Option, - pub env: Option, - pub extend: bool, - pub merge: Option, - pub nested: bool, - pub parse_env: Option, - pub validate: Option, - - // serde - pub rename: Option, - pub skip: bool, -} - -pub struct Setting<'l> { - pub args: SettingArgs, - pub serde_args: SerdeArgs, - pub attrs: Vec<&'l Attribute>, - pub name: &'l Ident, - pub value: &'l Type, - pub value_type: SettingType<'l>, -} - -impl<'l> Setting<'l> { - pub fn from(field: &Field) -> Setting { - let args = SettingArgs::from_attributes(&field.attrs).unwrap_or_default(); - let serde_args = SerdeArgs::from_attributes(&field.attrs).unwrap_or_default(); - - let setting = Setting { - name: field.ident.as_ref().unwrap(), - attrs: extract_common_attrs(&field.attrs), - value: &field.ty, - value_type: if args.nested { - SettingType::nested(&field.ty) - } else { - SettingType::value(&field.ty) - }, - args, - serde_args, - }; - - if setting.has_default() { - if setting.is_nested() { - panic!("Cannot use defaults with `nested` configs."); - } - - if setting.is_optional() { - panic!("Cannot use defaults with optional settings."); - } - } - - setting - } - - pub fn has_default(&self) -> bool { - self.args.default.is_some() - } - - pub fn is_extendable(&self) -> bool { - self.args.extend - } - - pub fn is_nested(&self) -> bool { - self.args.nested - } - - pub fn is_optional(&self) -> bool { - self.value_type.is_optional() - } - - pub fn is_skipped(&self) -> bool { - self.args.skip || self.serde_args.skip - } - - pub fn get_name(&self, casing_format: Option<&str>) -> String { - if let Some(local) = &self.args.rename { - local.to_owned() - } else if let Some(serde) = &self.serde_args.rename { - serde.to_owned() - } else if let Some(format) = casing_format { - format_case(format, &self.name.to_string(), false) - } else { - self.name.to_string() - } - } +use crate::common::{Field, FieldValue}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::Expr; +impl<'l> Field<'l> { pub fn generate_default_value(&self) -> TokenStream { if self.is_optional() { quote! { None } @@ -165,7 +64,7 @@ impl<'l> Setting<'l> { let value = self.value_type.get_from_partial_value(); #[allow(clippy::collapsible_else_if)] - if matches!(self.value_type, SettingType::Value { .. }) { + if matches!(self.value_type, FieldValue::Value { .. }) { // Reset extendable values since we don't have the entire resolved list if self.args.extend { quote! { Default::default() } @@ -243,113 +142,4 @@ impl<'l> Setting<'l> { } } } - - pub fn generate_schema_type(&self, casing_format: &str) -> TokenStream { - let name = self.get_name(Some(casing_format)); - let value = self.value; - - let deprecated = has_attr(&self.attrs, "deprecated"); - let hidden = self.is_skipped(); - let nullable = self.is_optional(); - let partial = self.is_nested(); - - let description = if let Some(comment) = extract_comment(&self.attrs) { - quote! { - Some(#comment.into()) - } - } else { - quote! { - None - } - }; - - let mut type_of = if partial { - quote! { SchemaType::infer_partial::<#value>() } - } else { - quote! { SchemaType::infer::<#value>() } - }; - - if let Some(Expr::Lit(lit)) = &self.args.default { - let lit_value = match &lit.lit { - Lit::Str(v) => quote! { LiteralValue::String(#v.into()) }, - Lit::Int(v) => { - if v.suffix().starts_with('u') { - quote! { LiteralValue::Uint(#v) } - } else { - quote! { LiteralValue::Int(#v) } - } - } - Lit::Float(v) => { - if v.suffix() == "f32" { - quote! { LiteralValue::F32(#v) } - } else { - quote! { LiteralValue::F64(#v) } - } - } - Lit::Bool(v) => quote! { LiteralValue::Bool(#v) }, - _ => unimplemented!(), - }; - - type_of = quote! { SchemaType::infer_with_default::<#value>(#lit_value) }; - } - - quote! { - SchemaField { - name: Some(#name.into()), - description: #description, - type_of: #type_of, - deprecated: #deprecated, - hidden: #hidden, - nullable: #nullable, - ..Default::default() - } - } - } - - pub fn get_serde_meta(&self) -> Option { - let mut meta = vec![]; - - if let Some(rename) = &self.args.rename { - meta.push(quote! { rename = #rename }); - } else if let Some(rename) = &self.serde_args.rename { - meta.push(quote! { rename = #rename }); - } - - if self.args.skip || self.serde_args.skip { - meta.push(quote! { skip }); - } else { - meta.push(quote! { skip_serializing_if = "Option::is_none" }); - } - - if meta.is_empty() { - return None; - } - - Some(quote! { - #(#meta),* - }) - } -} - -impl<'l> ToTokens for Setting<'l> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let name = self.name; - let value = &self.value_type; - - // Gather all attributes - let mut attrs = vec![]; - - if let Some(serde_meta) = self.get_serde_meta() { - attrs.push(quote! { #[serde(#serde_meta)] }); - } - - for attr in &self.attrs { - attrs.push(quote! { #attr }); - } - - tokens.extend(quote! { - #(#attrs)* - pub #name: #value, - }); - } } diff --git a/crates/macros/src/config/setting_type.rs b/crates/macros/src/config/setting_type.rs deleted file mode 100644 index bdc59651..00000000 --- a/crates/macros/src/config/setting_type.rs +++ /dev/null @@ -1,317 +0,0 @@ -use super::setting::SettingArgs; -use crate::utils::unwrap_option; -use proc_macro2::{Ident, TokenStream}; -use quote::{format_ident, quote, ToTokens}; -use syn::{Expr, GenericArgument, Lit, PathArguments, Type, TypePath}; - -fn get_option_inner(path: &TypePath) -> Option<&TypePath> { - let last_segment = path.path.segments.last().unwrap(); - - if last_segment.ident != "Option" { - return None; - } - - let PathArguments::AngleBracketed(args) = &last_segment.arguments else { - return None; - }; - - let GenericArgument::Type(arg) = &args.args[0] else { - return None; - }; - - match &arg { - Type::Path(t) => Some(t), - _ => None, - } -} - -pub enum SettingType<'l> { - // Vec - NestedList { - collection: &'l Ident, - item: &'l GenericArgument, - optional: bool, - path: &'l TypePath, - }, - // HashMap - NestedMap { - collection: &'l Ident, - key: &'l GenericArgument, - optional: bool, - path: &'l TypePath, - value: &'l GenericArgument, - }, - // config - NestedValue { - config: &'l Ident, - optional: bool, - path: &'l TypePath, - }, - // value - Value { - optional: bool, - value: &'l Type, - }, -} - -impl<'l> SettingType<'l> { - pub fn nested(raw: &Type) -> SettingType { - let mut optional = false; - - let Type::Path(raw_path) = raw else { - panic!("Nested values may only be paths/type references."); - }; - - let path = if let Some(unwrapped_path) = get_option_inner(raw_path) { - optional = true; - unwrapped_path - } else { - raw_path - }; - - let segment = path.path.segments.last().unwrap(); - let container = &segment.ident; - - match &segment.arguments { - PathArguments::None => SettingType::NestedValue { - path, - config: container, - optional, - }, - PathArguments::AngleBracketed(args) => match container.to_string().as_str() { - "Vec" | "HashSet" | "FxHashSet" | "BTreeSet" => SettingType::NestedList { - collection: container, - item: args.args.first().unwrap(), - optional, - path, - }, - "HashMap" | "FxHashMap" | "BTreeMap" => SettingType::NestedMap { - collection: container, - key: args.args.first().unwrap(), - optional, - path, - value: args.args.last().unwrap(), - }, - _ => panic!("Unsupported collection used with nested config."), - }, - _ => panic!("Parens are not supported for nested config."), - } - } - - pub fn value(raw: &Type) -> SettingType { - let mut optional = false; - - let value = if let Some(unwrapped_value) = unwrap_option(raw) { - optional = true; - unwrapped_value - } else { - raw - }; - - SettingType::Value { value, optional } - } - - pub fn is_optional(&self) -> bool { - match self { - SettingType::NestedValue { optional, .. } => *optional, - SettingType::NestedList { optional, .. } => *optional, - SettingType::NestedMap { optional, .. } => *optional, - SettingType::Value { optional, .. } => *optional, - } - } - - pub fn get_inner_type(&self) -> Option<&'l Type> { - match self { - SettingType::Value { value, .. } => Some(value), - _ => None, - } - } - - pub fn generate_default_value(&self, name: &Ident, args: &SettingArgs) -> TokenStream { - match self { - SettingType::NestedList { .. } | SettingType::NestedMap { .. } => { - quote! { Some(Default::default()) } - } - SettingType::NestedValue { config, .. } => { - let partial_name = format_ident!("Partial{}", config); - - quote! { #partial_name::default_values(context)? } - } - SettingType::Value { value, .. } => { - if let Some(expr) = args.default.as_ref() { - match expr { - Expr::Array(_) | Expr::Call(_) | Expr::Macro(_) | Expr::Tuple(_) => { - quote! { Some(#expr) } - } - Expr::Path(func) => quote! { #func(context) }, - Expr::Lit(lit) => match &lit.lit { - Lit::Str(string) => quote! { - Some( - #value::try_from(#string) - .map_err(|e| schematic::ConfigError::InvalidDefault(e.to_string()))? - ) - }, - other => quote! { Some(#other) }, - }, - invalid => { - let name = name.to_string(); - let info = format!("{:?}", invalid); - - panic!("Unsupported default value for {name} ({info}). May only provide literals, primitives, arrays, or tuples."); - } - } - } else { - quote! { Some(Default::default()) } - } - } - } - } - - pub fn get_finalize_value(&self) -> Option { - match self { - SettingType::NestedList { .. } | SettingType::NestedMap { .. } => { - Some(self.map_data(quote! { value.finalize(context)? })) - } - SettingType::NestedValue { .. } => { - Some(self.map_data(quote! { data.finalize(context)? })) - } - SettingType::Value { .. } => None, - } - } - - pub fn get_from_partial_value(&self) -> TokenStream { - match self { - SettingType::NestedList { item, .. } => self.map_data(quote! { - #item::from_partial(value) - }), - SettingType::NestedMap { value, .. } => self.map_data(quote! { - #value::from_partial(value) - }), - SettingType::NestedValue { config, .. } => quote! { - #config::from_partial(data) - }, - SettingType::Value { .. } => quote! { data }, - } - } - - pub fn get_merge_statement(&self, name: &Ident, args: &SettingArgs) -> TokenStream { - if let SettingType::NestedValue { .. } = self { - if args.merge.is_some() { - panic!("Nested configs do not support `merge` unless wrapped in a collection."); - } - - return quote! { - self.#name = schematic::internal::merge_partial_setting( - self.#name.take(), - next.#name.take(), - context, - )?; - }; - }; - - if let Some(func) = args.merge.as_ref() { - quote! { - self.#name = schematic::internal::merge_setting( - self.#name.take(), - next.#name.take(), - context, - #func, - )?; - } - } else { - quote! { - if next.#name.is_some() { - self.#name = next.#name; - } - } - } - } - - pub fn get_validate_statement(&self, name: &Ident) -> Option { - let name_quoted = format!("{}", name); - - match self { - SettingType::NestedList { .. } => Some(quote! { - for (i, item) in setting.iter().enumerate() { - if let Err(nested_error) = item.validate_with_path(context, path.join_key(#name_quoted).join_index(i)) { - errors.push(schematic::ValidateErrorType::nested(nested_error)); - } - } - }), - SettingType::NestedMap { .. } => Some(quote! { - for (key, value) in setting { - if let Err(nested_error) = value.validate_with_path(context, path.join_key(#name_quoted).join_key(key)) { - errors.push(schematic::ValidateErrorType::nested(nested_error)); - } - } - }), - SettingType::NestedValue { .. } => Some(quote! { - if let Err(nested_error) = setting.validate_with_path(context, path.join_key(#name_quoted)) { - errors.push(schematic::ValidateErrorType::nested(nested_error)); - } - }), - SettingType::Value { .. } => { - // Handled in parent struct - None - } - } - } - - pub fn map_data(&self, mapped_data: TokenStream) -> TokenStream { - match self { - SettingType::NestedList { collection, .. } => { - quote! { - { - let mut result = #collection::default(); - for value in data { - result.push(#mapped_data); - } - result - } - } - } - SettingType::NestedMap { collection, .. } => { - quote! { - { - let mut result = #collection::default(); - for (key, value) in data { - result.insert(key, #mapped_data); - } - result - } - } - } - SettingType::NestedValue { .. } | SettingType::Value { .. } => { - quote! { #mapped_data } - } - } - } -} - -impl<'l> ToTokens for SettingType<'l> { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - SettingType::NestedList { - collection, item, .. - } => { - quote! { Option<#collection<<#item as schematic::Config>::Partial>> } - } - SettingType::NestedMap { - collection, - key, - value, - .. - } => { - quote! { - Option<#collection<#key, <#value as schematic::Config>::Partial>> - } - } - SettingType::NestedValue { path, .. } => { - quote! { Option<<#path as schematic::Config>::Partial> } - } - SettingType::Value { value, .. } => { - quote! { Option<#value> } - } - }) - } -} diff --git a/crates/macros/src/config/setting_value.rs b/crates/macros/src/config/setting_value.rs new file mode 100644 index 00000000..fa9091dd --- /dev/null +++ b/crates/macros/src/config/setting_value.rs @@ -0,0 +1,164 @@ +use crate::common::{FieldArgs, FieldValue}; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; +use syn::{Expr, Lit}; + +impl<'l> FieldValue<'l> { + pub fn generate_default_value(&self, name: &Ident, args: &FieldArgs) -> TokenStream { + match self { + Self::NestedList { .. } | Self::NestedMap { .. } => { + quote! { Some(Default::default()) } + } + Self::NestedValue { config, .. } => { + let partial_name = format_ident!("Partial{}", config); + + quote! { #partial_name::default_values(context)? } + } + Self::Value { value, .. } => { + if let Some(expr) = args.default.as_ref() { + match expr { + Expr::Array(_) | Expr::Call(_) | Expr::Macro(_) | Expr::Tuple(_) => { + quote! { Some(#expr) } + } + Expr::Path(func) => quote! { #func(context) }, + Expr::Lit(lit) => match &lit.lit { + Lit::Str(string) => quote! { + Some( + #value::try_from(#string) + .map_err(|e| schematic::ConfigError::InvalidDefault(e.to_string()))? + ) + }, + other => quote! { Some(#other) }, + }, + invalid => { + let name = name.to_string(); + let info = format!("{:?}", invalid); + + panic!("Unsupported default value for {name} ({info}). May only provide literals, primitives, arrays, or tuples."); + } + } + } else { + quote! { Some(Default::default()) } + } + } + } + } + + pub fn get_finalize_value(&self) -> Option { + match self { + Self::NestedList { .. } | Self::NestedMap { .. } => { + Some(self.map_data(quote! { value.finalize(context)? })) + } + Self::NestedValue { .. } => Some(self.map_data(quote! { data.finalize(context)? })), + Self::Value { .. } => None, + } + } + + pub fn get_from_partial_value(&self) -> TokenStream { + match self { + Self::NestedList { item, .. } => self.map_data(quote! { + #item::from_partial(value) + }), + Self::NestedMap { value, .. } => self.map_data(quote! { + #value::from_partial(value) + }), + Self::NestedValue { config, .. } => quote! { + #config::from_partial(data) + }, + Self::Value { .. } => quote! { data }, + } + } + + pub fn get_merge_statement(&self, name: &Ident, args: &FieldArgs) -> TokenStream { + if let Self::NestedValue { .. } = self { + if args.merge.is_some() { + panic!("Nested configs do not support `merge` unless wrapped in a collection."); + } + + return quote! { + self.#name = schematic::internal::merge_partial_setting( + self.#name.take(), + next.#name.take(), + context, + )?; + }; + }; + + if let Some(func) = args.merge.as_ref() { + quote! { + self.#name = schematic::internal::merge_setting( + self.#name.take(), + next.#name.take(), + context, + #func, + )?; + } + } else { + quote! { + if next.#name.is_some() { + self.#name = next.#name; + } + } + } + } + + pub fn get_validate_statement(&self, name: &Ident) -> Option { + let name_quoted = format!("{}", name); + + match self { + Self::NestedList { .. } => Some(quote! { + for (i, item) in setting.iter().enumerate() { + if let Err(nested_error) = item.validate_with_path(context, path.join_key(#name_quoted).join_index(i)) { + errors.push(schematic::ValidateErrorType::nested(nested_error)); + } + } + }), + Self::NestedMap { .. } => Some(quote! { + for (key, value) in setting { + if let Err(nested_error) = value.validate_with_path(context, path.join_key(#name_quoted).join_key(key)) { + errors.push(schematic::ValidateErrorType::nested(nested_error)); + } + } + }), + Self::NestedValue { .. } => Some(quote! { + if let Err(nested_error) = setting.validate_with_path(context, path.join_key(#name_quoted)) { + errors.push(schematic::ValidateErrorType::nested(nested_error)); + } + }), + Self::Value { .. } => { + // Handled in parent struct + None + } + } + } + + pub fn map_data(&self, mapped_data: TokenStream) -> TokenStream { + match self { + Self::NestedList { collection, .. } => { + quote! { + { + let mut result = #collection::default(); + for value in data { + result.push(#mapped_data); + } + result + } + } + } + Self::NestedMap { collection, .. } => { + quote! { + { + let mut result = #collection::default(); + for (key, value) in data { + result.insert(key, #mapped_data); + } + result + } + } + } + Self::NestedValue { .. } | Self::Value { .. } => { + quote! { #mapped_data } + } + } + } +} diff --git a/crates/macros/src/config/variant.rs b/crates/macros/src/config/variant.rs index bcfc1d90..07e82c88 100644 --- a/crates/macros/src/config/variant.rs +++ b/crates/macros/src/config/variant.rs @@ -1,4 +1,4 @@ -pub use crate::common::*; +use crate::common::Variant; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use syn::{Expr, Fields, FieldsUnnamed}; diff --git a/crates/macros/src/config_enum/mod.rs b/crates/macros/src/config_enum/mod.rs index 35e51c8a..9d81b43f 100644 --- a/crates/macros/src/config_enum/mod.rs +++ b/crates/macros/src/config_enum/mod.rs @@ -1,6 +1,6 @@ mod variant; -use crate::common_schema::SerdeArgs; +use crate::common_schema::ContainerSerdeArgs; use crate::config_enum::variant::Variant; use darling::FromDeriveInput; use proc_macro::TokenStream; @@ -22,7 +22,7 @@ pub struct ConfigEnumArgs { pub fn macro_impl(item: TokenStream) -> TokenStream { let input: DeriveInput = parse_macro_input!(item); let args = ConfigEnumArgs::from_derive_input(&input).expect("Failed to parse arguments."); - let serde_args = SerdeArgs::from_derive_input(&input).unwrap_or_default(); + let serde_args = ContainerSerdeArgs::from_derive_input(&input).unwrap_or_default(); let Data::Enum(data) = input.data else { panic!("Only unit enums are supported."); diff --git a/crates/macros/src/config_enum/variant.rs b/crates/macros/src/config_enum/variant.rs index c61b6024..1f10901d 100644 --- a/crates/macros/src/config_enum/variant.rs +++ b/crates/macros/src/config_enum/variant.rs @@ -1,4 +1,4 @@ -use crate::common::VariantSerdeArgs; +use crate::common_schema::FieldSerdeArgs; use crate::utils::{extract_comment, extract_common_attrs, format_case, has_attr}; use darling::FromAttributes; use proc_macro2::{Ident, TokenStream}; @@ -15,7 +15,7 @@ pub struct VariantArgs { pub struct Variant<'l> { pub args: VariantArgs, - pub serde_args: VariantSerdeArgs, + pub serde_args: FieldSerdeArgs, pub attrs: Vec<&'l Attribute>, pub name: &'l Ident, pub value: String, @@ -24,7 +24,7 @@ pub struct Variant<'l> { impl<'l> Variant<'l> { pub fn from<'n>(variant: &'n NativeVariant, format: &str) -> Variant<'n> { let args = VariantArgs::from_attributes(&variant.attrs).unwrap_or_default(); - let serde_args = VariantSerdeArgs::from_attributes(&variant.attrs).unwrap_or_default(); + let serde_args = FieldSerdeArgs::from_attributes(&variant.attrs).unwrap_or_default(); if args.fallback { if let Fields::Unnamed(fields) = &variant.fields { From 854e795040a730236434c38ed16ad4613e052cc5 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 9 Oct 2023 15:28:49 -0700 Subject: [PATCH 4/9] Migrate container. --- crates/macros/src/common/container.rs | 77 ++++++++++ crates/macros/src/common/mod.rs | 1 + crates/macros/src/config/config.rs | 11 +- .../config/{config_type.rs => container.rs} | 134 +++++------------- .../src/config/{setting.rs => field.rs} | 0 .../{setting_value.rs => field_value.rs} | 0 crates/macros/src/config/mod.rs | 16 +-- 7 files changed, 126 insertions(+), 113 deletions(-) rename crates/macros/src/config/{config_type.rs => container.rs} (76%) rename crates/macros/src/config/{setting.rs => field.rs} (100%) rename crates/macros/src/config/{setting_value.rs => field_value.rs} (100%) diff --git a/crates/macros/src/common/container.rs b/crates/macros/src/common/container.rs index e69de29b..427adee6 100644 --- a/crates/macros/src/common/container.rs +++ b/crates/macros/src/common/container.rs @@ -0,0 +1,77 @@ +use crate::common::{Field, TaggedFormat, Variant}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +pub enum Container<'l> { + NamedStruct { fields: Vec> }, + Enum { variants: Vec> }, +} + +impl<'l> Container<'l> { + pub fn has_nested(&self) -> bool { + match self { + Self::NamedStruct { fields, .. } => fields.iter().any(|v| v.is_nested()), + Self::Enum { variants } => variants.iter().any(|v| v.is_nested()), + } + } + + pub fn generate_schema( + &self, + config_name: &Ident, + description: Option, + casing_format: &str, + tagged_format: TaggedFormat, + ) -> TokenStream { + let config_name = config_name.to_string(); + let description = if let Some(comment) = description { + quote! { + schema.description = Some(#comment.into()); + } + } else { + quote! {} + }; + + match self { + Self::NamedStruct { fields, .. } => { + let schema_types = fields + .iter() + .map(|s| s.generate_schema_type(casing_format)) + .collect::>(); + + quote! { + let mut schema = StructType { + name: Some(#config_name.into()), + fields: vec![ + #(#schema_types),* + ], + ..Default::default() + }; + + #description + + SchemaType::Struct(schema) + } + } + Self::Enum { variants } => { + let variants_types = variants + .iter() + .map(|s| s.generate_schema_type(casing_format, &tagged_format)) + .collect::>(); + + quote! { + let mut schema = UnionType { + name: Some(#config_name.into()), + variants_types: vec![ + #(Box::new(#variants_types)),* + ], + ..Default::default() + }; + + #description + + SchemaType::Union(schema) + } + } + } + } +} diff --git a/crates/macros/src/common/mod.rs b/crates/macros/src/common/mod.rs index c59d9625..6ff85911 100644 --- a/crates/macros/src/common/mod.rs +++ b/crates/macros/src/common/mod.rs @@ -3,6 +3,7 @@ mod field; mod field_value; mod variant; +pub use container::*; pub use field::*; pub use field_value::*; pub use variant::*; diff --git a/crates/macros/src/config/config.rs b/crates/macros/src/config/config.rs index 3fc5c109..ef032909 100644 --- a/crates/macros/src/config/config.rs +++ b/crates/macros/src/config/config.rs @@ -1,5 +1,4 @@ -use super::config_type::ConfigType; -use crate::common::TaggedFormat; +use crate::common::{Container, TaggedFormat}; use crate::common_schema::*; use darling::FromDeriveInput; use proc_macro2::{Ident, TokenStream}; @@ -26,12 +25,12 @@ pub struct Config<'l> { pub serde_args: ContainerSerdeArgs, pub attrs: Vec<&'l Attribute>, pub name: &'l Ident, - pub type_of: ConfigType<'l>, + pub type_of: Container<'l>, } impl<'l> Config<'l> { pub fn is_enum(&self) -> bool { - matches!(self.type_of, ConfigType::Enum { .. }) + matches!(self.type_of, Container::Enum { .. }) } pub fn get_meta_struct(&self) -> TokenStream { @@ -89,14 +88,14 @@ impl<'l> Config<'l> { let mut meta = vec![]; match &self.type_of { - ConfigType::NamedStruct { .. } => { + Container::NamedStruct { .. } => { meta.push(quote! { default }); if !self.args.allow_unknown_fields { meta.push(quote! { deny_unknown_fields }); } } - ConfigType::Enum { .. } => { + Container::Enum { .. } => { if let Some(content) = &self.args.serde.content { meta.push(quote! { content = #content }); } else if let Some(content) = &self.serde_args.content { diff --git a/crates/macros/src/config/config_type.rs b/crates/macros/src/config/container.rs similarity index 76% rename from crates/macros/src/config/config_type.rs rename to crates/macros/src/config/container.rs index f3af76d7..d188192a 100644 --- a/crates/macros/src/config/config_type.rs +++ b/crates/macros/src/config/container.rs @@ -1,29 +1,13 @@ -use crate::common::{Field, TaggedFormat, Variant}; +use crate::common::Container; use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; -use syn::FieldsNamed; - -pub enum ConfigType<'l> { - NamedStruct { - fields: &'l FieldsNamed, - settings: Vec>, - }, - Enum { - variants: Vec>, - }, -} - -impl<'l> ConfigType<'l> { - pub fn has_nested(&self) -> bool { - match self { - ConfigType::NamedStruct { settings, .. } => settings.iter().any(|v| v.is_nested()), - ConfigType::Enum { variants } => variants.iter().any(|v| v.is_nested()), - } - } +impl<'l> Container<'l> { pub fn generate_default_values(&self) -> TokenStream { match self { - ConfigType::NamedStruct { settings, .. } => { + Self::NamedStruct { + fields: settings, .. + } => { let mut setting_names = vec![]; let mut default_values = vec![]; @@ -38,7 +22,7 @@ impl<'l> ConfigType<'l> { })) } } - ConfigType::Enum { variants } => { + Self::Enum { variants } => { let default_variant = variants.iter().find(|v| v.is_default()); if let Some(variant) = default_variant { @@ -58,7 +42,9 @@ impl<'l> ConfigType<'l> { pub fn generate_env_values(&self, prefix: Option<&String>) -> TokenStream { match self { - ConfigType::NamedStruct { settings, .. } => { + Self::NamedStruct { + fields: settings, .. + } => { let env_stmts = settings .iter() .filter_map(|s| s.generate_env_statement(prefix)) @@ -76,7 +62,7 @@ impl<'l> ConfigType<'l> { } } } - ConfigType::Enum { .. } => { + Self::Enum { .. } => { quote! { Ok(None) } @@ -86,7 +72,9 @@ impl<'l> ConfigType<'l> { pub fn generate_extends_from(&self) -> TokenStream { match self { - ConfigType::NamedStruct { settings, .. } => { + Self::NamedStruct { + fields: settings, .. + } => { // Validate only 1 setting is using it let mut names = vec![]; @@ -149,7 +137,7 @@ impl<'l> ConfigType<'l> { quote! { None } } - ConfigType::Enum { .. } => { + Self::Enum { .. } => { quote! { None } } } @@ -157,7 +145,9 @@ impl<'l> ConfigType<'l> { pub fn generate_finalize(&self) -> TokenStream { match self { - ConfigType::NamedStruct { settings, .. } => { + Self::NamedStruct { + fields: settings, .. + } => { let finalize_stmts = settings .iter() .map(|s| s.generate_finalize_statement()) @@ -181,7 +171,7 @@ impl<'l> ConfigType<'l> { Ok(partial) } } - ConfigType::Enum { variants } => { + Self::Enum { variants } => { if self.has_nested() { let finalize_stmts = variants .iter() @@ -205,7 +195,9 @@ impl<'l> ConfigType<'l> { pub fn generate_merge(&self) -> TokenStream { match self { - ConfigType::NamedStruct { settings, .. } => { + Self::NamedStruct { + fields: settings, .. + } => { let merge_stmts = settings .iter() .map(|s| s.generate_merge_statement()) @@ -216,7 +208,7 @@ impl<'l> ConfigType<'l> { Ok(()) } } - ConfigType::Enum { variants } => { + Self::Enum { variants } => { let merge_stmts = variants .iter() .filter_map(|s| s.generate_merge_statement()) @@ -244,7 +236,9 @@ impl<'l> ConfigType<'l> { pub fn generate_validate(&self) -> TokenStream { match self { - ConfigType::NamedStruct { settings, .. } => { + Self::NamedStruct { + fields: settings, .. + } => { let validate_stmts = settings .iter() .map(|s| s.generate_validate_statement()) @@ -254,7 +248,7 @@ impl<'l> ConfigType<'l> { #(#validate_stmts)* } } - ConfigType::Enum { variants } => { + Self::Enum { variants } => { let validate_stmts = variants .iter() .filter_map(|s| s.generate_validate_statement()) @@ -276,7 +270,9 @@ impl<'l> ConfigType<'l> { pub fn generate_from_partial(&self, partial_name: &Ident) -> TokenStream { match self { - ConfigType::NamedStruct { settings, .. } => { + Self::NamedStruct { + fields: settings, .. + } => { let mut setting_names = vec![]; let mut from_partial_values = vec![]; @@ -291,7 +287,7 @@ impl<'l> ConfigType<'l> { } } } - ConfigType::Enum { variants } => { + Self::Enum { variants } => { let from_partial_values = variants .iter() .map(|s| s.generate_from_partial_value(partial_name)) @@ -306,73 +302,15 @@ impl<'l> ConfigType<'l> { } } - pub fn generate_schema( - &self, - config_name: &Ident, - description: Option, - casing_format: &str, - tagged_format: TaggedFormat, - ) -> TokenStream { - let config_name = config_name.to_string(); - let description = if let Some(comment) = description { - quote! { - schema.description = Some(#comment.into()); - } - } else { - quote! {} - }; - - match self { - ConfigType::NamedStruct { settings, .. } => { - let schema_types = settings - .iter() - .map(|s| s.generate_schema_type(casing_format)) - .collect::>(); - - quote! { - let mut schema = StructType { - name: Some(#config_name.into()), - fields: vec![ - #(#schema_types),* - ], - ..Default::default() - }; - - #description - - SchemaType::Struct(schema) - } - } - ConfigType::Enum { variants } => { - let variants_types = variants - .iter() - .map(|s| s.generate_schema_type(casing_format, &tagged_format)) - .collect::>(); - - quote! { - let mut schema = UnionType { - name: Some(#config_name.into()), - variants_types: vec![ - #(Box::new(#variants_types)),* - ], - ..Default::default() - }; - - #description - - SchemaType::Union(schema) - } - } - } - } - pub fn generate_partial( &self, partial_name: &Ident, partial_attrs: &[TokenStream], ) -> TokenStream { match self { - ConfigType::NamedStruct { settings, .. } => { + Self::NamedStruct { + fields: settings, .. + } => { quote! { #[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] #(#partial_attrs)* @@ -381,7 +319,7 @@ impl<'l> ConfigType<'l> { } } } - ConfigType::Enum { variants } => { + Self::Enum { variants } => { let default_variant = variants .iter() .find(|v| v.is_default()) @@ -418,14 +356,14 @@ impl<'l> ConfigType<'l> { _partial_name: &Ident, ) -> TokenStream { match self { - ConfigType::NamedStruct { .. } => { + Self::NamedStruct { .. } => { quote! { let mut schema = #config_name::generate_schema(); schematic::internal::partialize_schema(&mut schema, true); schema } } - ConfigType::Enum { .. } => { + Self::Enum { .. } => { quote! { let mut schema = #config_name::generate_schema(); schematic::internal::partialize_schema(&mut schema, true); diff --git a/crates/macros/src/config/setting.rs b/crates/macros/src/config/field.rs similarity index 100% rename from crates/macros/src/config/setting.rs rename to crates/macros/src/config/field.rs diff --git a/crates/macros/src/config/setting_value.rs b/crates/macros/src/config/field_value.rs similarity index 100% rename from crates/macros/src/config/setting_value.rs rename to crates/macros/src/config/field_value.rs diff --git a/crates/macros/src/config/mod.rs b/crates/macros/src/config/mod.rs index 63bc6c72..86dea98f 100644 --- a/crates/macros/src/config/mod.rs +++ b/crates/macros/src/config/mod.rs @@ -1,14 +1,13 @@ #[allow(clippy::module_inception)] pub mod config; -pub mod config_type; -pub mod setting; -pub mod setting_value; +pub mod container; +pub mod field; +pub mod field_value; pub mod variant; -use crate::common::{Field, Variant}; +use crate::common::{Container, Field, Variant}; use crate::common_schema::ContainerSerdeArgs; use crate::config::config::{Config, ConfigArgs}; -use crate::config::config_type::ConfigType; use crate::utils::extract_common_attrs; use darling::FromDeriveInput; use proc_macro::TokenStream; @@ -23,9 +22,8 @@ pub fn macro_impl(item: TokenStream) -> TokenStream { let config_type = match &input.data { Data::Struct(data) => match &data.fields { - Fields::Named(fields) => ConfigType::NamedStruct { - settings: fields.named.iter().map(Field::from).collect::>(), - fields, + Fields::Named(fields) => Container::NamedStruct { + fields: fields.named.iter().map(Field::from).collect::>(), }, Fields::Unnamed(_) => { panic!("Unnamed structs are not supported."); @@ -34,7 +32,7 @@ pub fn macro_impl(item: TokenStream) -> TokenStream { panic!("Unit structs are not supported."); } }, - Data::Enum(data) => ConfigType::Enum { + Data::Enum(data) => Container::Enum { variants: data .variants .iter() From 19807e7eb08dc2fee87cacd26319a65c4b2d57bd Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 9 Oct 2023 15:30:47 -0700 Subject: [PATCH 5/9] Update rust. --- CHANGELOG.md | 6 ++++++ rust-toolchain.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e14392b..120cf201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +#### ⚙️ Internal + +- Updated Rust to v1.73. + ## 0.11.8 #### 🚀 Updates diff --git a/rust-toolchain.toml b/rust-toolchain.toml index df9cee29..f172ddee 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] profile = "default" -channel = "1.72.1" +channel = "1.73.0" From 3809ff67d3b28d08b5fcfee68700c1e94fb8d24a Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 9 Oct 2023 15:32:27 -0700 Subject: [PATCH 6/9] Move serde. --- crates/macros/src/common/field.rs | 3 +- crates/macros/src/common/mod.rs | 35 ++++++++++++++++++++++++ crates/macros/src/common/variant.rs | 2 +- crates/macros/src/common_schema.rs | 34 ----------------------- crates/macros/src/config/config.rs | 3 +- crates/macros/src/config/mod.rs | 3 +- crates/macros/src/config_enum/mod.rs | 2 +- crates/macros/src/config_enum/variant.rs | 2 +- crates/macros/src/lib.rs | 1 - 9 files changed, 41 insertions(+), 44 deletions(-) delete mode 100644 crates/macros/src/common_schema.rs diff --git a/crates/macros/src/common/field.rs b/crates/macros/src/common/field.rs index 38a6690a..e46e9d77 100644 --- a/crates/macros/src/common/field.rs +++ b/crates/macros/src/common/field.rs @@ -1,5 +1,4 @@ -use crate::common::field_value::FieldValue; -use crate::common_schema::FieldSerdeArgs; +use crate::common::{FieldSerdeArgs, FieldValue}; use crate::utils::{ extract_comment, extract_common_attrs, format_case, has_attr, preserve_str_literal, }; diff --git a/crates/macros/src/common/mod.rs b/crates/macros/src/common/mod.rs index 6ff85911..cd32a5de 100644 --- a/crates/macros/src/common/mod.rs +++ b/crates/macros/src/common/mod.rs @@ -7,3 +7,38 @@ pub use container::*; pub use field::*; pub use field_value::*; pub use variant::*; + +use darling::{FromAttributes, FromDeriveInput, FromMeta}; + +// #[serde()] +#[derive(FromDeriveInput, Default)] +#[darling(default, allow_unknown_fields, attributes(serde))] +pub struct ContainerSerdeArgs { + // struct + pub rename: Option, + pub rename_all: Option, + + // enum + pub content: Option, + pub expecting: Option, + pub tag: Option, + pub untagged: bool, +} + +// #[serde()] +#[derive(FromAttributes, Default)] +#[darling(default, allow_unknown_fields, attributes(serde))] +pub struct FieldSerdeArgs { + pub alias: Option, + pub rename: Option, + pub skip: bool, +} + +#[derive(FromMeta, Default)] +#[darling(default)] +pub struct SerdeMeta { + pub content: Option, + pub expecting: Option, + pub tag: Option, + pub untagged: bool, +} diff --git a/crates/macros/src/common/variant.rs b/crates/macros/src/common/variant.rs index 673144d8..1e059e96 100644 --- a/crates/macros/src/common/variant.rs +++ b/crates/macros/src/common/variant.rs @@ -1,4 +1,4 @@ -use crate::common_schema::FieldSerdeArgs; +use crate::common::FieldSerdeArgs; use crate::utils::{extract_common_attrs, format_case}; use darling::FromAttributes; use proc_macro2::{Ident, TokenStream}; diff --git a/crates/macros/src/common_schema.rs b/crates/macros/src/common_schema.rs deleted file mode 100644 index 13f5c5f9..00000000 --- a/crates/macros/src/common_schema.rs +++ /dev/null @@ -1,34 +0,0 @@ -use darling::{FromAttributes, FromDeriveInput, FromMeta}; - -// #[serde()] -#[derive(FromDeriveInput, Default)] -#[darling(default, allow_unknown_fields, attributes(serde))] -pub struct ContainerSerdeArgs { - // struct - pub rename: Option, - pub rename_all: Option, - - // enum - pub content: Option, - pub expecting: Option, - pub tag: Option, - pub untagged: bool, -} - -// #[serde()] -#[derive(FromAttributes, Default)] -#[darling(default, allow_unknown_fields, attributes(serde))] -pub struct FieldSerdeArgs { - pub alias: Option, - pub rename: Option, - pub skip: bool, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct SerdeMeta { - pub content: Option, - pub expecting: Option, - pub tag: Option, - pub untagged: bool, -} diff --git a/crates/macros/src/config/config.rs b/crates/macros/src/config/config.rs index ef032909..93569c2a 100644 --- a/crates/macros/src/config/config.rs +++ b/crates/macros/src/config/config.rs @@ -1,5 +1,4 @@ -use crate::common::{Container, TaggedFormat}; -use crate::common_schema::*; +use crate::common::{Container, ContainerSerdeArgs, SerdeMeta, TaggedFormat}; use darling::FromDeriveInput; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, ToTokens}; diff --git a/crates/macros/src/config/mod.rs b/crates/macros/src/config/mod.rs index 86dea98f..ce166233 100644 --- a/crates/macros/src/config/mod.rs +++ b/crates/macros/src/config/mod.rs @@ -5,8 +5,7 @@ pub mod field; pub mod field_value; pub mod variant; -use crate::common::{Container, Field, Variant}; -use crate::common_schema::ContainerSerdeArgs; +use crate::common::{Container, ContainerSerdeArgs, Field, Variant}; use crate::config::config::{Config, ConfigArgs}; use crate::utils::extract_common_attrs; use darling::FromDeriveInput; diff --git a/crates/macros/src/config_enum/mod.rs b/crates/macros/src/config_enum/mod.rs index 9d81b43f..6f21c033 100644 --- a/crates/macros/src/config_enum/mod.rs +++ b/crates/macros/src/config_enum/mod.rs @@ -1,6 +1,6 @@ mod variant; -use crate::common_schema::ContainerSerdeArgs; +use crate::common::ContainerSerdeArgs; use crate::config_enum::variant::Variant; use darling::FromDeriveInput; use proc_macro::TokenStream; diff --git a/crates/macros/src/config_enum/variant.rs b/crates/macros/src/config_enum/variant.rs index 1f10901d..92446062 100644 --- a/crates/macros/src/config_enum/variant.rs +++ b/crates/macros/src/config_enum/variant.rs @@ -1,4 +1,4 @@ -use crate::common_schema::FieldSerdeArgs; +use crate::common::FieldSerdeArgs; use crate::utils::{extract_comment, extract_common_attrs, format_case, has_attr}; use darling::FromAttributes; use proc_macro2::{Ident, TokenStream}; diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 4065c7f1..fe06431c 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -1,5 +1,4 @@ mod common; -mod common_schema; mod config; mod config_enum; mod utils; From 2598381bea3a3294fb9d7747230c0bfe4cec8291 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 9 Oct 2023 15:59:39 -0700 Subject: [PATCH 7/9] Share macro code. --- crates/macros/src/common/macros.rs | 196 +++++++++++++++++++ crates/macros/src/common/mod.rs | 2 + crates/macros/src/config/config.rs | 295 ----------------------------- crates/macros/src/config/mod.rs | 194 ++++++++++++++----- crates/macros/src/lib.rs | 8 +- 5 files changed, 351 insertions(+), 344 deletions(-) create mode 100644 crates/macros/src/common/macros.rs delete mode 100644 crates/macros/src/config/config.rs diff --git a/crates/macros/src/common/macros.rs b/crates/macros/src/common/macros.rs new file mode 100644 index 00000000..18557972 --- /dev/null +++ b/crates/macros/src/common/macros.rs @@ -0,0 +1,196 @@ +use crate::common::{Container, ContainerSerdeArgs, Field, SerdeMeta, TaggedFormat, Variant}; +use crate::utils::extract_common_attrs; +use darling::FromDeriveInput; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{Attribute, Data, DeriveInput, ExprPath, Fields}; + +// #[config()], #[schematic()] +#[derive(FromDeriveInput, Default)] +#[darling( + default, + attributes(config, schematic), + supports(struct_named, enum_any) +)] +pub struct MacroArgs { + // config + pub allow_unknown_fields: bool, + pub context: Option, + pub env_prefix: Option, + pub file: Option, + + // serde + pub rename: Option, + pub rename_all: Option, + pub serde: SerdeMeta, +} + +pub struct Macro<'l> { + pub args: MacroArgs, + pub serde_args: ContainerSerdeArgs, + pub attrs: Vec<&'l Attribute>, + pub name: &'l Ident, + pub type_of: Container<'l>, +} + +impl<'l> Macro<'l> { + pub fn from(input: &'l DeriveInput) -> Self { + let args = MacroArgs::from_derive_input(input).expect("Failed to parse arguments."); + let serde_args = ContainerSerdeArgs::from_derive_input(input).unwrap_or_default(); + + let config_type = match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(fields) => Container::NamedStruct { + fields: fields.named.iter().map(Field::from).collect::>(), + }, + Fields::Unnamed(_) => { + panic!("Unnamed structs are not supported."); + } + Fields::Unit => { + panic!("Unit structs are not supported."); + } + }, + Data::Enum(data) => Container::Enum { + variants: data + .variants + .iter() + .map(|variant| { + if matches!(variant.fields, Fields::Named(_)) { + panic!("Named enum variants are not supported."); + } + + Variant::from(variant) + }) + .collect(), + }, + Data::Union(_) => { + panic!("Unions are not supported."); + } + }; + + Self { + args, + serde_args, + attrs: extract_common_attrs(&input.attrs), + name: &input.ident, + type_of: config_type, + } + } + + pub fn is_enum(&self) -> bool { + matches!(self.type_of, Container::Enum { .. }) + } + + pub fn get_meta_struct(&self) -> TokenStream { + let name = if let Some(rename) = &self.args.rename { + rename.to_string() + } else { + format!("{}", self.name) + }; + + quote! { + schematic::Meta { + name: #name, + } + } + } + + pub fn get_casing_format(&self) -> &str { + self.args + .rename_all + .as_deref() + .or(self.serde_args.rename_all.as_deref()) + .unwrap_or(if self.is_enum() { + "kebab-case" + } else { + "camelCase" + }) + } + + pub fn get_tagged_format(&self) -> TaggedFormat { + if self.args.serde.untagged || self.serde_args.untagged { + return TaggedFormat::Untagged; + } + + match ( + self.args + .serde + .tag + .as_ref() + .or(self.serde_args.tag.as_ref()), + self.args + .serde + .content + .as_ref() + .or(self.serde_args.content.as_ref()), + ) { + (Some(tag), Some(content)) => { + TaggedFormat::Adjacent(tag.to_owned(), content.to_owned()) + } + (Some(tag), None) => TaggedFormat::Internal(tag.to_owned()), + _ => TaggedFormat::External, + } + } + + pub fn get_serde_meta(&self) -> TokenStream { + let mut meta = vec![]; + + match &self.type_of { + Container::NamedStruct { .. } => { + meta.push(quote! { default }); + + if !self.args.allow_unknown_fields { + meta.push(quote! { deny_unknown_fields }); + } + } + Container::Enum { .. } => { + if let Some(content) = &self.args.serde.content { + meta.push(quote! { content = #content }); + } else if let Some(content) = &self.serde_args.content { + meta.push(quote! { content = #content }); + } + + if let Some(tag) = &self.args.serde.tag { + meta.push(quote! { tag = #tag }); + } else if let Some(tag) = &self.serde_args.tag { + meta.push(quote! { tag = #tag }); + } + + if self.args.serde.untagged || self.serde_args.untagged { + meta.push(quote! { untagged }); + } + } + }; + + if let Some(expecting) = &self.args.serde.expecting { + meta.push(quote! { expecting = #expecting }); + } else if let Some(expecting) = &self.serde_args.expecting { + meta.push(quote! { expecting = #expecting }); + } + + if let Some(rename) = &self.args.rename { + meta.push(quote! { rename = #rename }); + } else if let Some(rename) = &self.serde_args.rename { + meta.push(quote! { rename = #rename }); + } + + let rename_all = self.get_casing_format(); + + meta.push(quote! { rename_all = #rename_all }); + + quote! { + #(#meta),* + } + } + + pub fn get_partial_attrs(&self) -> Vec { + let serde_meta = self.get_serde_meta(); + let mut attrs = vec![quote! { #[serde(#serde_meta) ]}]; + + for attr in &self.attrs { + attrs.push(quote! { #attr }); + } + + attrs + } +} diff --git a/crates/macros/src/common/mod.rs b/crates/macros/src/common/mod.rs index cd32a5de..7c0326f2 100644 --- a/crates/macros/src/common/mod.rs +++ b/crates/macros/src/common/mod.rs @@ -1,11 +1,13 @@ mod container; mod field; mod field_value; +mod macros; mod variant; pub use container::*; pub use field::*; pub use field_value::*; +pub use macros::*; pub use variant::*; use darling::{FromAttributes, FromDeriveInput, FromMeta}; diff --git a/crates/macros/src/config/config.rs b/crates/macros/src/config/config.rs deleted file mode 100644 index 93569c2a..00000000 --- a/crates/macros/src/config/config.rs +++ /dev/null @@ -1,295 +0,0 @@ -use crate::common::{Container, ContainerSerdeArgs, SerdeMeta, TaggedFormat}; -use darling::FromDeriveInput; -use proc_macro2::{Ident, TokenStream}; -use quote::{format_ident, quote, ToTokens}; -use syn::{Attribute, ExprPath}; - -// #[config()] -#[derive(FromDeriveInput, Default)] -#[darling(default, attributes(config), supports(struct_named, enum_any))] -pub struct ConfigArgs { - allow_unknown_fields: bool, - context: Option, - env_prefix: Option, - file: Option, - - // serde - rename: Option, - rename_all: Option, - serde: SerdeMeta, -} - -pub struct Config<'l> { - pub args: ConfigArgs, - pub serde_args: ContainerSerdeArgs, - pub attrs: Vec<&'l Attribute>, - pub name: &'l Ident, - pub type_of: Container<'l>, -} - -impl<'l> Config<'l> { - pub fn is_enum(&self) -> bool { - matches!(self.type_of, Container::Enum { .. }) - } - - pub fn get_meta_struct(&self) -> TokenStream { - let name = if let Some(rename) = &self.args.rename { - rename.to_string() - } else { - format!("{}", self.name) - }; - - quote! { - schematic::Meta { - name: #name, - } - } - } - - pub fn get_casing_format(&self) -> &str { - self.args - .rename_all - .as_deref() - .or(self.serde_args.rename_all.as_deref()) - .unwrap_or(if self.is_enum() { - "kebab-case" - } else { - "camelCase" - }) - } - - pub fn get_tagged_format(&self) -> TaggedFormat { - if self.args.serde.untagged || self.serde_args.untagged { - return TaggedFormat::Untagged; - } - - match ( - self.args - .serde - .tag - .as_ref() - .or(self.serde_args.tag.as_ref()), - self.args - .serde - .content - .as_ref() - .or(self.serde_args.content.as_ref()), - ) { - (Some(tag), Some(content)) => { - TaggedFormat::Adjacent(tag.to_owned(), content.to_owned()) - } - (Some(tag), None) => TaggedFormat::Internal(tag.to_owned()), - _ => TaggedFormat::External, - } - } - - pub fn get_serde_meta(&self) -> TokenStream { - let mut meta = vec![]; - - match &self.type_of { - Container::NamedStruct { .. } => { - meta.push(quote! { default }); - - if !self.args.allow_unknown_fields { - meta.push(quote! { deny_unknown_fields }); - } - } - Container::Enum { .. } => { - if let Some(content) = &self.args.serde.content { - meta.push(quote! { content = #content }); - } else if let Some(content) = &self.serde_args.content { - meta.push(quote! { content = #content }); - } - - if let Some(tag) = &self.args.serde.tag { - meta.push(quote! { tag = #tag }); - } else if let Some(tag) = &self.serde_args.tag { - meta.push(quote! { tag = #tag }); - } - - if self.args.serde.untagged || self.serde_args.untagged { - meta.push(quote! { untagged }); - } - } - }; - - if let Some(expecting) = &self.args.serde.expecting { - meta.push(quote! { expecting = #expecting }); - } else if let Some(expecting) = &self.serde_args.expecting { - meta.push(quote! { expecting = #expecting }); - } - - if let Some(rename) = &self.args.rename { - meta.push(quote! { rename = #rename }); - } else if let Some(rename) = &self.serde_args.rename { - meta.push(quote! { rename = #rename }); - } - - let rename_all = self.get_casing_format(); - - meta.push(quote! { rename_all = #rename_all }); - - quote! { - #(#meta),* - } - } - - pub fn get_partial_attrs(&self) -> Vec { - let serde_meta = self.get_serde_meta(); - let mut attrs = vec![quote! { #[serde(#serde_meta) ]}]; - - for attr in &self.attrs { - attrs.push(quote! { #attr }); - } - - attrs - } -} - -impl<'l> ToTokens for Config<'l> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let name = self.name; - let env_prefix = self.args.env_prefix.as_ref(); - - // Generate the partial implementation - let partial_name = format_ident!("Partial{}", self.name); - let partial_attrs = self.get_partial_attrs(); - let partial = self.type_of.generate_partial(&partial_name, &partial_attrs); - - tokens.extend(quote! { - #partial - }); - - // Generate implementations - let meta = self.get_meta_struct(); - let default_values = self.type_of.generate_default_values(); - let env_values = self.type_of.generate_env_values(env_prefix); - let extends_from = self.type_of.generate_extends_from(); - let finalize = self.type_of.generate_finalize(); - let merge = self.type_of.generate_merge(); - let validate = self.type_of.generate_validate(); - let from_partial = self.type_of.generate_from_partial(&partial_name); - - let context = match self.args.context.as_ref() { - Some(ctx) => quote! { #ctx }, - None => quote! { () }, - }; - - tokens.extend(quote! { - #[automatically_derived] - impl schematic::PartialConfig for #partial_name { - type Context = #context; - - fn default_values(context: &Self::Context) -> Result, schematic::ConfigError> { - #default_values - } - - fn env_values() -> Result, schematic::ConfigError> { - #env_values - } - - fn extends_from(&self) -> Option { - #extends_from - } - - fn finalize(self, context: &Self::Context) -> Result { - #finalize - } - - fn merge( - &mut self, - context: &Self::Context, - mut next: Self, - ) -> Result<(), schematic::ConfigError> { - #merge - } - - fn validate_with_path( - &self, - context: &Self::Context, - path: schematic::Path - ) -> Result<(), schematic::ValidatorError> { - let mut errors: Vec = vec![]; - - #validate - - if !errors.is_empty() { - return Err(schematic::ValidatorError { - errors, - path, - }); - } - - Ok(()) - } - } - - #[automatically_derived] - impl Default for #name { - fn default() -> Self { - let context = <::Partial as schematic::PartialConfig>::Context::default(); - - let defaults = <::Partial as schematic::PartialConfig>::default_values(&context).unwrap().unwrap_or_default(); - - ::from_partial(defaults) - } - } - - #[automatically_derived] - impl schematic::Config for #name { - type Partial = #partial_name; - - const META: schematic::Meta = #meta; - - fn from_partial(partial: Self::Partial) -> Self { - #from_partial - } - } - }); - - #[cfg(feature = "schema")] - { - use crate::utils::extract_comment; - - let casing_format = self.get_casing_format(); - let tagged_format = self.get_tagged_format(); - - let schema = self.type_of.generate_schema( - name, - extract_comment(&self.attrs), - casing_format, - tagged_format, - ); - let partial_schema = self.type_of.generate_partial_schema(name, &partial_name); - - tokens.extend(quote! { - #[automatically_derived] - impl schematic::Schematic for #name { - fn generate_schema() -> schematic::SchemaType { - use schematic::schema::*; - #schema - } - } - - #[automatically_derived] - impl schematic::Schematic for #partial_name { - fn generate_schema() -> schematic::SchemaType { - use schematic::schema::*; - #partial_schema - } - } - }); - } - - #[cfg(not(feature = "schema"))] - { - tokens.extend(quote! { - #[automatically_derived] - impl schematic::Schematic for #name {} - - #[automatically_derived] - impl schematic::Schematic for #partial_name {} - }); - } - } -} diff --git a/crates/macros/src/config/mod.rs b/crates/macros/src/config/mod.rs index ce166233..e301d520 100644 --- a/crates/macros/src/config/mod.rs +++ b/crates/macros/src/config/mod.rs @@ -1,61 +1,159 @@ -#[allow(clippy::module_inception)] -pub mod config; pub mod container; pub mod field; pub mod field_value; pub mod variant; -use crate::common::{Container, ContainerSerdeArgs, Field, Variant}; -use crate::config::config::{Config, ConfigArgs}; -use crate::utils::extract_common_attrs; -use darling::FromDeriveInput; -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput, Fields}; - -// #[derive(Config)] -pub fn macro_impl(item: TokenStream) -> TokenStream { - let input: DeriveInput = parse_macro_input!(item); - let args = ConfigArgs::from_derive_input(&input).expect("Failed to parse arguments."); - let serde_args = ContainerSerdeArgs::from_derive_input(&input).unwrap_or_default(); - - let config_type = match &input.data { - Data::Struct(data) => match &data.fields { - Fields::Named(fields) => Container::NamedStruct { - fields: fields.named.iter().map(Field::from).collect::>(), - }, - Fields::Unnamed(_) => { - panic!("Unnamed structs are not supported."); +use crate::common::Macro; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; + +pub struct ConfigMacro<'l>(pub Macro<'l>); + +impl<'l> ToTokens for ConfigMacro<'l> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let cfg = &self.0; + let name = cfg.name; + let env_prefix = cfg.args.env_prefix.as_ref(); + + // Generate the partial implementation + let partial_name = format_ident!("Partial{}", cfg.name); + let partial_attrs = cfg.get_partial_attrs(); + let partial = cfg.type_of.generate_partial(&partial_name, &partial_attrs); + + tokens.extend(quote! { + #partial + }); + + // Generate implementations + let meta = cfg.get_meta_struct(); + let default_values = cfg.type_of.generate_default_values(); + let env_values = cfg.type_of.generate_env_values(env_prefix); + let extends_from = cfg.type_of.generate_extends_from(); + let finalize = cfg.type_of.generate_finalize(); + let merge = cfg.type_of.generate_merge(); + let validate = cfg.type_of.generate_validate(); + let from_partial = cfg.type_of.generate_from_partial(&partial_name); + + let context = match cfg.args.context.as_ref() { + Some(ctx) => quote! { #ctx }, + None => quote! { () }, + }; + + tokens.extend(quote! { + #[automatically_derived] + impl schematic::PartialConfig for #partial_name { + type Context = #context; + + fn default_values(context: &Self::Context) -> Result, schematic::ConfigError> { + #default_values + } + + fn env_values() -> Result, schematic::ConfigError> { + #env_values + } + + fn extends_from(&self) -> Option { + #extends_from + } + + fn finalize(self, context: &Self::Context) -> Result { + #finalize + } + + fn merge( + &mut self, + context: &Self::Context, + mut next: Self, + ) -> Result<(), schematic::ConfigError> { + #merge + } + + fn validate_with_path( + &self, + context: &Self::Context, + path: schematic::Path + ) -> Result<(), schematic::ValidatorError> { + let mut errors: Vec = vec![]; + + #validate + + if !errors.is_empty() { + return Err(schematic::ValidatorError { + errors, + path, + }); + } + + Ok(()) + } } - Fields::Unit => { - panic!("Unit structs are not supported."); + + #[automatically_derived] + impl Default for #name { + fn default() -> Self { + let context = <::Partial as schematic::PartialConfig>::Context::default(); + + let defaults = <::Partial as schematic::PartialConfig>::default_values(&context).unwrap().unwrap_or_default(); + + ::from_partial(defaults) + } } - }, - Data::Enum(data) => Container::Enum { - variants: data - .variants - .iter() - .map(|variant| { - if matches!(variant.fields, Fields::Named(_)) { - panic!("Named enum variants are not supported."); + + #[automatically_derived] + impl schematic::Config for #name { + type Partial = #partial_name; + + const META: schematic::Meta = #meta; + + fn from_partial(partial: Self::Partial) -> Self { + #from_partial + } + } + }); + + #[cfg(feature = "schema")] + { + use crate::utils::extract_comment; + + let casing_format = cfg.get_casing_format(); + let tagged_format = cfg.get_tagged_format(); + + let schema = cfg.type_of.generate_schema( + name, + extract_comment(&cfg.attrs), + casing_format, + tagged_format, + ); + let partial_schema = cfg.type_of.generate_partial_schema(name, &partial_name); + + tokens.extend(quote! { + #[automatically_derived] + impl schematic::Schematic for #name { + fn generate_schema() -> schematic::SchemaType { + use schematic::schema::*; + #schema } + } - Variant::from(variant) - }) - .collect(), - }, - Data::Union(_) => { - panic!("Unions are not supported."); + #[automatically_derived] + impl schematic::Schematic for #partial_name { + fn generate_schema() -> schematic::SchemaType { + use schematic::schema::*; + #partial_schema + } + } + }); } - }; - let config = Config { - args, - serde_args, - attrs: extract_common_attrs(&input.attrs), - name: &input.ident, - type_of: config_type, - }; + #[cfg(not(feature = "schema"))] + { + tokens.extend(quote! { + #[automatically_derived] + impl schematic::Schematic for #name {} - quote! { #config }.into() + #[automatically_derived] + impl schematic::Schematic for #partial_name {} + }); + } + } } diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index fe06431c..cf7ac356 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -6,12 +6,18 @@ mod utils; #[cfg(feature = "schema")] mod schematic; +use common::Macro; use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; // #[derive(Config)] #[proc_macro_derive(Config, attributes(config, setting))] pub fn config(item: TokenStream) -> TokenStream { - config::macro_impl(item) + let input: DeriveInput = parse_macro_input!(item); + let output = config::ConfigMacro(Macro::from(&input)); + + quote! { #output }.into() } // #[derive(ConfigEnum)] From 761fc4dfeddded89952513bb6ee93515da0c77de Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 9 Oct 2023 16:08:58 -0700 Subject: [PATCH 8/9] Add proc macro. --- CHANGELOG.md | 4 ++ crates/config/tests/schematic_enum_test.rs | 24 ++++++++++++ crates/macros/src/common/container.rs | 2 +- crates/macros/src/lib.rs | 15 +++++--- crates/macros/src/schematic/mod.rs | 45 +++++++++++++--------- 5 files changed, 65 insertions(+), 25 deletions(-) create mode 100644 crates/config/tests/schematic_enum_test.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 120cf201..e695c787 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +#### 🚀 Updates + +- Added a `Schematic` derive macro that only implements the `schematic::Schematic` trait. + #### ⚙️ Internal - Updated Rust to v1.73. diff --git a/crates/config/tests/schematic_enum_test.rs b/crates/config/tests/schematic_enum_test.rs new file mode 100644 index 00000000..0c0d995c --- /dev/null +++ b/crates/config/tests/schematic_enum_test.rs @@ -0,0 +1,24 @@ +#![allow(dead_code, deprecated)] + +use schematic::Schematic; +use std::collections::HashMap; + +#[derive(Default, Schematic)] +pub enum SomeEnum { + #[default] + A, + B, + C, +} + +#[derive(Schematic)] +#[schematic(rename_all = "snake_case")] +pub struct ValueTypes { + boolean: bool, + string: String, + number: usize, + vector: Vec, + map: HashMap, + enums: SomeEnum, + s3_value: String, +} diff --git a/crates/macros/src/common/container.rs b/crates/macros/src/common/container.rs index 427adee6..91e0440a 100644 --- a/crates/macros/src/common/container.rs +++ b/crates/macros/src/common/container.rs @@ -55,7 +55,7 @@ impl<'l> Container<'l> { Self::Enum { variants } => { let variants_types = variants .iter() - .map(|s| s.generate_schema_type(casing_format, &tagged_format)) + .map(|v| v.generate_schema_type(casing_format, &tagged_format)) .collect::>(); quote! { diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index cf7ac356..ff7c4c2a 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -26,9 +26,12 @@ pub fn config_enum(item: TokenStream) -> TokenStream { config_enum::macro_impl(item) } -// // #[derive(Schematic)] -// #[cfg(feature = "schema")] -// #[proc_macro_derive(Schematic, attributes(schematic))] -// pub fn schematic(item: TokenStream) -> TokenStream { -// schematic::macro_impl(item) -// } +// #[derive(Schematic)] +#[cfg(feature = "schema")] +#[proc_macro_derive(Schematic, attributes(schematic, field, variant))] +pub fn schematic(item: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(item); + let output = schematic::SchematicMacro(Macro::from(&input)); + + quote! { #output }.into() +} diff --git a/crates/macros/src/schematic/mod.rs b/crates/macros/src/schematic/mod.rs index 447d24a7..1e561a4d 100644 --- a/crates/macros/src/schematic/mod.rs +++ b/crates/macros/src/schematic/mod.rs @@ -1,20 +1,29 @@ -// use crate::common_schema::*; -// use darling::{FromDeriveInput, FromMeta}; -// use proc_macro2::{Ident, TokenStream}; -// use quote::{format_ident, quote, ToTokens}; -// use syn::{Attribute, ExprPath}; +use crate::common::Macro; +use crate::utils::extract_comment; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; -// // #[schematic()] -// #[derive(FromDeriveInput, Default)] -// #[darling(default, attributes(schematic), supports(struct_named, enum_any))] -// pub struct SchematicArgs { -// allow_unknown_fields: bool, -// context: Option, -// env_prefix: Option, -// file: Option, +pub struct SchematicMacro<'l>(pub Macro<'l>); -// // serde -// rename: Option, -// rename_all: Option, -// serde: SerdeMeta, -// } +impl<'l> ToTokens for SchematicMacro<'l> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let cfg = &self.0; + let name = cfg.name; + let schema = cfg.type_of.generate_schema( + name, + extract_comment(&cfg.attrs), + cfg.get_casing_format(), + cfg.get_tagged_format(), + ); + + tokens.extend(quote! { + #[automatically_derived] + impl schematic::Schematic for #name { + fn generate_schema() -> schematic::SchemaType { + use schematic::schema::*; + #schema + } + } + }); + } +} From 1a1b8ef1a89ed436633bb3fac25a754cf82d948e Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Mon, 9 Oct 2023 16:15:40 -0700 Subject: [PATCH 9/9] Move serde around. --- crates/macros/src/common/field.rs | 11 ++++++++++- crates/macros/src/common/macros.rs | 17 ++++++++++++++++- crates/macros/src/common/mod.rs | 28 +--------------------------- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/crates/macros/src/common/field.rs b/crates/macros/src/common/field.rs index e46e9d77..ec18c3c0 100644 --- a/crates/macros/src/common/field.rs +++ b/crates/macros/src/common/field.rs @@ -1,4 +1,4 @@ -use crate::common::{FieldSerdeArgs, FieldValue}; +use crate::common::FieldValue; use crate::utils::{ extract_comment, extract_common_attrs, format_case, has_attr, preserve_str_literal, }; @@ -7,6 +7,15 @@ use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; use syn::{Attribute, Expr, ExprPath, Field as NativeField, Lit, Type}; +// #[serde()] +#[derive(FromAttributes, Default)] +#[darling(default, allow_unknown_fields, attributes(serde))] +pub struct FieldSerdeArgs { + pub alias: Option, + pub rename: Option, + pub skip: bool, +} + // #[field()], #[setting()] #[derive(FromAttributes, Default)] #[darling(default, attributes(field, setting))] diff --git a/crates/macros/src/common/macros.rs b/crates/macros/src/common/macros.rs index 18557972..2a0b9005 100644 --- a/crates/macros/src/common/macros.rs +++ b/crates/macros/src/common/macros.rs @@ -1,10 +1,25 @@ -use crate::common::{Container, ContainerSerdeArgs, Field, SerdeMeta, TaggedFormat, Variant}; +use crate::common::{Container, Field, SerdeMeta, TaggedFormat, Variant}; use crate::utils::extract_common_attrs; use darling::FromDeriveInput; use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{Attribute, Data, DeriveInput, ExprPath, Fields}; +// #[serde()] +#[derive(FromDeriveInput, Default)] +#[darling(default, allow_unknown_fields, attributes(serde))] +pub struct ContainerSerdeArgs { + // struct + pub rename: Option, + pub rename_all: Option, + + // enum + pub content: Option, + pub expecting: Option, + pub tag: Option, + pub untagged: bool, +} + // #[config()], #[schematic()] #[derive(FromDeriveInput, Default)] #[darling( diff --git a/crates/macros/src/common/mod.rs b/crates/macros/src/common/mod.rs index 7c0326f2..e283477c 100644 --- a/crates/macros/src/common/mod.rs +++ b/crates/macros/src/common/mod.rs @@ -10,33 +10,7 @@ pub use field_value::*; pub use macros::*; pub use variant::*; -use darling::{FromAttributes, FromDeriveInput, FromMeta}; - -// #[serde()] -#[derive(FromDeriveInput, Default)] -#[darling(default, allow_unknown_fields, attributes(serde))] -pub struct ContainerSerdeArgs { - // struct - pub rename: Option, - pub rename_all: Option, - - // enum - pub content: Option, - pub expecting: Option, - pub tag: Option, - pub untagged: bool, -} - -// #[serde()] -#[derive(FromAttributes, Default)] -#[darling(default, allow_unknown_fields, attributes(serde))] -pub struct FieldSerdeArgs { - pub alias: Option, - pub rename: Option, - pub skip: bool, -} - -#[derive(FromMeta, Default)] +#[derive(darling::FromMeta, Default)] #[darling(default)] pub struct SerdeMeta { pub content: Option,