summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcel Müller <m.mueller@ifm.com>2022-05-11 09:05:53 +0200
committerMarcel Müller <m.mueller@ifm.com>2022-05-12 08:51:18 +0200
commit296e0bc82c532acd77a4ceeb83490bc93628442d (patch)
tree5d5ab8b56678f2c5bc65a32cc6fd4ca609de886b
parent28cecb8697b4a5eec80503fe2b13a2d1b4917bbe (diff)
Add Config derive macro
Signed-off-by: Marcel Müller <m.mueller@ifm.com>
-rw-r--r--Cargo.lock148
-rw-r--r--Cargo.toml1
-rw-r--r--crates/core/tedge_api/Cargo.toml2
-rw-r--r--crates/core/tedge_api/examples/print_config.rs63
-rw-r--r--crates/core/tedge_api/src/lib.rs3
-rw-r--r--crates/core/tedge_api/tedge_config_derive/Cargo.toml15
-rw-r--r--crates/core/tedge_api/tedge_config_derive/src/lib.rs359
-rw-r--r--crates/core/tedge_api/tests/derive_config.rs66
8 files changed, 583 insertions, 74 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b0cec5fc..6a8277f5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -72,9 +72,9 @@ checksum = "48ad219abc0c06ca788aface2e3a1970587e3413ab70acd20e54b6ec524c1f8f"
dependencies = [
"argh_shared",
"heck",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -168,9 +168,9 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -179,9 +179,9 @@ version = "0.1.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -675,8 +675,8 @@ version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
dependencies = [
- "quote 1.0.10",
- "syn 1.0.82",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -693,7 +693,7 @@ checksum = "c73af209b6a5dc8ca7cbaba720732304792cddc933cfea3d74509c2b1ef2f436"
dependencies = [
"num-bigint 0.4.3",
"num-traits",
- "syn 1.0.82",
+ "syn 1.0.93",
]
[[package]]
@@ -976,9 +976,9 @@ version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -1404,9 +1404,9 @@ version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a95a48d0bc28f9af628286e8a4da09f96f34a97744a2e9a5a4db9814ad527d"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -1508,9 +1508,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e25b214433f669161f414959594216d8e6ba83b6679d3db96899c0b4639033"
dependencies = [
"cfg-if 1.0.0",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -1874,9 +1874,9 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -2018,9 +2018,9 @@ dependencies = [
[[package]]
name = "pretty_assertions"
-version = "1.0.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc"
+checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563"
dependencies = [
"ansi_term",
"ctor",
@@ -2045,9 +2045,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
"version_check",
]
@@ -2057,8 +2057,8 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
"version_check",
]
@@ -2073,9 +2073,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.32"
+version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
+checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa"
dependencies = [
"unicode-xid 0.2.2",
]
@@ -2123,11 +2123,11 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.10"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
- "proc-macro2 1.0.32",
+ "proc-macro2 1.0.38",
]
[[package]]
@@ -2580,9 +2580,9 @@ version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -2625,9 +2625,9 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -2747,9 +2747,9 @@ checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
dependencies = [
"heck",
"proc-macro-error",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -2765,12 +2765,12 @@ dependencies = [
[[package]]
name = "syn"
-version = "1.0.82"
+version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
+checksum = "04066589568b72ec65f42d65a1a52436e954b168773148893c020269563decf2"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
"unicode-xid 0.2.2",
]
@@ -2861,8 +2861,10 @@ dependencies = [
"miette",
"nu-ansi-term",
"pretty",
+ "pretty_assertions",
"serde",
"static_assertions",
+ "tedge_config_derive",
"termcolor",
"termimad",
"thiserror",
@@ -2903,6 +2905,16 @@ dependencies = [
]
[[package]]
+name = "tedge_config_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
+]
+
+[[package]]
name = "tedge_dummy_plugin"
version = "0.5.2"
dependencies = [
@@ -3021,9 +3033,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7cad0a06f9a61e94355aa3b3dc92d85ab9c83406722b1ca5e918d4297c12c23"
dependencies = [
"cfg-if 1.0.0",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
"version_check",
]
@@ -3072,9 +3084,9 @@ version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -3165,9 +3177,9 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -3291,9 +3303,9 @@ version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
]
[[package]]
@@ -3589,9 +3601,9 @@ dependencies = [
"bumpalo",
"lazy_static",
"log",
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
"wasm-bindgen-shared",
]
@@ -3613,7 +3625,7 @@ version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
dependencies = [
- "quote 1.0.10",
+ "quote 1.0.18",
"wasm-bindgen-macro-support",
]
@@ -3623,9 +3635,9 @@ version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
dependencies = [
- "proc-macro2 1.0.32",
- "quote 1.0.10",
- "syn 1.0.82",
+ "proc-macro2 1.0.38",
+ "quote 1.0.18",
+ "syn 1.0.93",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
diff --git a/Cargo.toml b/Cargo.toml
index 0bd9467e..bf58964b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,7 @@
members = [
"crates/common/*",
"crates/core/*",
+ "crates/core/tedge_api/tedge_config_derive",
"crates/tests/*",
"plugins/tedge_apt_plugin",
"plugins/tedge_dummy_plugin",
diff --git a/crates/core/tedge_api/Cargo.toml b/crates/core/tedge_api/Cargo.toml
index 7fdf5508..619e769e 100644
--- a/crates/core/tedge_api/Cargo.toml
+++ b/crates/core/tedge_api/Cargo.toml
@@ -19,7 +19,9 @@ pretty = { version = "0.11.3", features = ["termcolor"] }
termcolor = "1.1.3"
termimad = "0.20.1"
nu-ansi-term = "0.45.1"
+tedge_config_derive = { version = "0.1.0", path = "tedge_config_derive" }
[dev-dependencies]
+pretty_assertions = "1.2.1"
static_assertions = "1.1.0"
tokio = { version = "1.16.1", features = ["full"] }
diff --git a/crates/core/tedge_api/examples/print_config.rs b/crates/core/tedge_api/examples/print_config.rs
index 9cefa5a1..498a79b7 100644
--- a/crates/core/tedge_api/examples/print_config.rs
+++ b/crates/core/tedge_api/examples/print_config.rs
@@ -2,7 +2,10 @@ use std::collections::HashMap;
use nu_ansi_term::Color;
use pretty::Arena;
-use tedge_api::config::{AsConfig, ConfigDescription, ConfigKind};
+use tedge_api::{
+ config::{AsConfig, ConfigDescription, ConfigKind},
+ Config,
+};
struct Port(u64);
impl AsConfig for Port {
@@ -21,7 +24,7 @@ impl AsConfig for VHost {
fn as_config() -> ConfigDescription {
ConfigDescription::new(
String::from("VHost"),
- ConfigKind::Struct(vec![("name", String::as_config())]),
+ ConfigKind::Struct(vec![("name", None, String::as_config())]),
Some("A virtual host definition"),
)
}
@@ -48,10 +51,10 @@ fn main() {
let doc = ConfigDescription::new(
String::from("ServerConfig"),
ConfigKind::Struct(vec![
- ("port", Port::as_config()),
- ("interface", String::as_config()),
- ("virtual_hosts", Vec::<VHost>::as_config()),
- ("headers", HashMap::<String, String>::as_config()),
+ ("port", None, Port::as_config()),
+ ("interface", None, String::as_config()),
+ ("virtual_hosts", None, Vec::<VHost>::as_config()),
+ ("headers", None, HashMap::<String, String>::as_config()),
]),
Some("Specify how the server should be started\n\n## Note\n\nThis is a reallly really loooooooooooooooooong loooooooooooooooooooong new *line*."),
);
@@ -76,5 +79,53 @@ fn main() {
);
println!("------- Output for ServerConfig");
println!("{}", output);
+ let arena = Arena::new();
+
+ #[derive(Config)]
+ #[config(tag = "type")]
+ /// An Nginx virtual host
+ ///
+ /// # Note
+ ///
+ /// This is an example and as such is nonsense
+ enum NginxVHost {
+ /// A simple host consisting of a string
+ Simple(String),
+ /// A more complex host that can also specify its port
+ Complex {
+ /// the name of the VHost
+ name: String,
+ port: Port,
+ },
+ }
+
+ #[derive(Config)]
+ struct NginxConfig {
+ vhosts: Vec<NginxVHost>,
+ allow_priv_ports: bool,
+ }
+
+ let doc = NginxConfig::as_config();
+ let rendered_doc = doc.as_terminal_doc(&arena);
+
+ let mut output = String::new();
+
+ rendered_doc.render_fmt(80, &mut output).unwrap();
+
+ println!("------- Output for NginxConfig");
+ println!(
+ "Configuration for {} plugin kinds",
+ Color::White.bold().paint(doc.name())
+ );
+ println!(
+ "{}",
+ Color::White.dimmed().bold().paint(format!(
+ "=================={}=============",
+ std::iter::repeat('=')
+ .take(doc.name().len())
+ .collect::<String>()
+ ))
+ );
+ println!("{}", output);
println!("-------");
}
diff --git a/crates/core/tedge_api/src/lib.rs b/crates/core/tedge_api/src/lib.rs
index a6e6b665..5a9f77bb 100644
--- a/crates/core/tedge_api/src/lib.rs
+++ b/crates/core/tedge_api/src/lib.rs
@@ -30,6 +30,9 @@ pub use message::CoreMessages;
///
pub use tokio_util::sync::CancellationToken;
+/// Derive macro for self-describing configurations
+pub use tedge_config_derive::Config;
+
#[doc(hidden)]
pub mod _internal {
pub use futures::future::BoxFuture;
diff --git a/crates/core/tedge_api/tedge_config_derive/Cargo.toml b/crates/core/tedge_api/tedge_config_derive/Cargo.toml
new file mode 100644
index 00000000..975daf1b
--- /dev/null
+++ b/crates/core/tedge_api/tedge_config_derive/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "tedge_config_derive"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0.38"
+quote = "1.0.18"
+syn = { version = "1.0.93", features = ["extra-traits"] }
+proc-macro-error = "1.0.4"
diff --git a/crates/core/tedge_api/tedge_config_derive/src/lib.rs b/crates/core/tedge_api/tedge_config_derive/src/lib.rs
new file mode 100644
index 00000000..a7e87321
--- /dev/null
+++ b/crates/core/tedge_api/tedge_config_derive/src/lib.rs
@@ -0,0 +1,359 @@
+use proc_macro::TokenStream as TS;
+use proc_macro2::TokenStream;
+use proc_macro_error::{abort, proc_macro_error, ResultExt};
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{
+ parse_macro_input, Attribute, DeriveInput, Ident, Lit, LitStr, Meta, MetaNameValue, NestedMeta,
+ Type,
+};
+
+#[derive(Debug)]
+struct ConfigField<'q> {
+ ident: &'q Ident,
+ ty: &'q Type,
+ docs: Option<Vec<LitStr>>,
+}
+
+#[derive(Debug)]
+enum ConfigVariantKind<'q> {
+ Wrapped(&'q Ident, ConfigField<'q>),
+ Struct(&'q Ident, Vec<ConfigField<'q>>),
+}
+
+#[derive(Debug)]
+struct ConfigVariant<'q> {
+ kind: ConfigVariantKind<'q>,
+ docs: Option<Vec<LitStr>>,
+}
+
+#[derive(Debug)]
+enum ConfigEnumKind {
+ Tagged(LitStr),
+ Untagged,
+}
+
+#[derive(Debug)]
+enum ConfigQuoteKind<'q> {
+ Wrapped(&'q Type),
+ Struct(Vec<ConfigField<'q>>),
+ Enum(ConfigEnumKind, Vec<ConfigVariant<'q>>),
+}
+
+#[derive(Debug)]
+struct ConfigQuote<'q> {
+ ident: &'q Ident,
+ docs: Option<Vec<LitStr>>,
+ kind: ConfigQuoteKind<'q>,
+}
+
+fn lit_strings_to_string_quoted(docs: &Option<Vec<LitStr>>) -> TokenStream {
+ if let Some(docs) = docs {
+ let docs = docs
+ .iter()
+ .map(|litstr| litstr.value().trim().to_string())
+ .collect::<Vec<_>>()
+ .join("\n");
+ quote!(Some(#docs))
+ } else {
+ quote!(None)
+ }
+}
+
+fn extract_docs_from_attributes<'a>(
+ attrs: impl Iterator<Item = &'a Attribute>,
+) -> Option<Vec<LitStr>> {
+ let attrs = attrs
+ .filter_map(|attr| {
+ if let Ok(Meta::NameValue(meta)) = attr.parse_meta() {
+ if meta.path.is_ident("doc") {
+ if let Lit::Str(litstr) = meta.lit {
+ return Some(litstr);
+ }
+ }
+ }
+ None
+ })
+ .collect::<Vec<_>>();
+
+ if attrs.is_empty() {
+ None
+ } else {
+ Some(attrs)
+ }
+}
+
+impl<'q> ToTokens for ConfigQuote<'q> {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ let ident_name = self.ident.to_string();
+ let outer_docs = lit_strings_to_string_quoted(&self.docs);
+
+ tokens.append_all(match &self.kind {
+ ConfigQuoteKind::Wrapped(ty) => {
+ quote! {
+ ::tedge_api::config::ConfigDescription::new(
+ ::std::string::String::from(#ident_name),
+ ::tedge_api::config::ConfigKind::Wrapped(
+ ::std::boxed::Box::new(<#ty as ::tedge_api::AsConfig>::as_config())
+ ),
+ #outer_docs
+ )
+ }
+ }
+ ConfigQuoteKind::Struct(fields) => {
+ let ident = fields.iter().map(|f| f.ident.to_string());
+ let ty = fields.iter().map(|f| f.ty);
+ let docs = fields.iter().map(|f| lit_strings_to_string_quoted(&f.docs));
+
+ quote! {
+ ::tedge_api::config::ConfigDescription::new(
+ ::std::string::String::from(#ident_name),
+ ::tedge_api::config::ConfigKind::Struct(
+ vec![
+ #(
+ (#ident, #docs, <#ty as ::tedge_api::AsConfig>::as_config())
+ ),*
+ ]
+ ),
+ #outer_docs
+ )
+ }
+ }
+ ConfigQuoteKind::Enum(kind, variants) => {
+ let kind = match kind {
+ ConfigEnumKind::Tagged(tag) => {
+ quote! {
+ ::tedge_api::config::ConfigEnumKind::Tagged(#tag)
+ }
+ }
+ ConfigEnumKind::Untagged => {
+ quote! {
+ ::tedge_api::config::ConfigEnumKind::Untagged
+ }
+ }
+ };
+
+ let variants = variants.iter().map(|var| {
+ let docs = lit_strings_to_string_quoted(&var.docs);
+ match &var.kind {
+ ConfigVariantKind::Wrapped(ident, ConfigField { ty, .. }) => {
+ // we ignore the above docs since the outer docs ar ethe important ones
+ // TODO: Emit an error if an inner type in a enum is annotated
+ let ident = ident.to_string();
+ quote!{
+ (
+ #ident,
+ #docs,
+ ::tedge_api::config::ConfigDescription::new(
+ ::std::string::String::from(#ident),
+ ::tedge_api::config::ConfigKind::Wrapped(
+ std::boxed::Box::new(<#ty as ::tedge_api::AsConfig>::as_config())
+ ),
+ None,
+ )
+ )
+ }
+ }
+ ConfigVariantKind::Struct(ident, fields) => {
+ let ident = ident.to_string();
+ let idents = fields.iter().map(|f| f.ident.to_string());
+ let field_docs = fields.iter().map(|f| lit_strings_to_string_quoted(&f.docs));
+ let tys = fields.iter().map(|f| f.ty);
+
+ quote! {
+ (
+ #ident,
+ #docs,
+ ::tedge_api::config::ConfigDescription::new(
+ ::std::string::String::from(#ident),
+ ::tedge_api::config::ConfigKind::Struct(
+ vec![
+ #(
+ (#idents, #field_docs, <#tys as ::tedge_api::AsConfig>::as_config())
+ ),*
+ ]
+ ),
+ None
+ )
+ )
+ }
+ }
+ }
+ });
+
+ quote! {
+ ::tedge_api::config::ConfigDescription::new(
+ ::std::string::String::from(#ident_name),
+ ::tedge_api::config::ConfigKind::Enum(
+ #kind,
+ vec![#(#variants),*]
+ ),
+ #outer_docs
+ )
+ }
+ }
+ });
+ }
+}
+
+#[proc_macro_derive(Config, attributes(config))]
+#[proc_macro_error]
+pub fn derive_config(input: TS) -> TS {
+ let input = parse_macro_input!(input as DeriveInput);
+
+ let ident = &input.ident;
+
+ let config_desc_kind: ConfigQuoteKind = match &input.data {
+ syn::Data::Struct(data) => match &data.fields {
+ syn::Fields::Named(fields) => ConfigQuoteKind::Struct(
+ fields
+ .named
+ .iter()
+ .map(|f| ConfigField {
+ ident: &f.ident.as_ref().unwrap(),
+ ty: &f.ty,
+ docs: extract_docs_from_attributes(f.attrs.iter()),
+ })
+ .collect(),
+ ),
+ syn::Fields::Unnamed(fields) => {
+ if fields.unnamed.len() != 1 {
+ abort!(
+ fields,
+ "Tuple structs should only contain a single variant."
+ )
+ }
+ ConfigQuoteKind::Wrapped(&fields.unnamed.first().unwrap().ty)
+ }
+ syn::Fields::Unit => abort!(
+ ident,
+ "Unit structs are not supported as they cannot be represented"
+ ),
+ },
+ syn::Data::Enum(data) => {
+ let enum_kind: ConfigEnumKind = {
+ let potential_kind = input
+ .attrs
+ .iter()
+ .find(|attr| attr.path.is_ident("config"))
+ .unwrap_or_else(|| {
+ abort!(ident, "Enums need to specify what kind of tagging they use";
+ help = "Use #[config(untagged)] for untagged enums, and #[config(tag = \"type\")] for internally tagged variants. Other kinds are not supported.")
+ });
+
+ macro_rules! abort_parse_enum_kind {
+ ($kind:expr) => {
+ abort!($kind, "Could not parse enum tag kind.";
+ help = "Accepted kinds are 'untagged' and 'tag = \"type\'")
+ }
+ }
+
+ match potential_kind
+ .parse_meta()
+ .expect_or_abort("Could not parse #[config] meta attribute.")
+ {
+ syn::Meta::Path(kind) => {
+ if kind.is_ident("untagged") {
+ ConfigEnumKind::Untagged
+ } else {
+ abort_parse_enum_kind!(kind)
+ }
+ }
+ syn::Meta::List(kind) => {
+ if kind.nested.len() != 1 {
+ abort_parse_enum_kind!(kind)
+ }
+
+ if let Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue {
+ path,
+ lit: Lit::Str(lit_str),
+ ..
+ }))) = kind.nested.first()
+ {
+ if path.is_ident("tag") {
+ ConfigEnumKind::Tagged(lit_str.clone())
+ } else {
+ abort_parse_enum_kind!(kind)
+ }
+ } else {
+ abort_parse_enum_kind!(kind)
+ }
+ }
+ syn::Meta::NameValue(kind) => abort!(
+ kind,
+ "The #[config] attribute cannot be used as a name-value attribute.";
+ help = "Maybe you meant #[config(tag = \"type\")] to describe that this enum has an internal tag?"
+ ),
+ }
+ };
+
+ let variants = data
+ .variants
+ .iter()
+ .map(|var| {
+ let kind = match &var.fields {
+ syn::Fields::Named(fields) => ConfigVariantKind::Struct(
+ &var.ident,
+ fields
+ .named
+ .iter()
+ .map(|f| ConfigField {
+ ident: &f.ident.as_ref().unwrap(),
+ ty: &f.ty,
+ docs: extract_docs_from_attributes(f.attrs.iter()),
+ })
+ .collect(),
+ ),
+ syn::Fields::Unnamed(fields) => {
+ if fields.unnamed.len() != 1 {
+ abort!(
+ fields,
+ "Tuple structs should only contain a single variant."
+ )
+ }
+ ConfigVariantKind::Wrapped(
+ &var.ident,
+ ConfigField {
+ ident: &var.ident,
+ ty: &fields.unnamed.first().unwrap().ty,
+ docs: extract_docs_from_attributes(var.attrs.iter()),
+ },
+ )
+ }
+ syn::Fields::Unit => abort!(
+ ident,
+ "Unit structs are not supported as they cannot be represented"
+ ),
+ };
+ let docs = extract_docs_from_attributes(var.attrs.iter());
+ ConfigVariant { kind, docs }
+ })
+ .collect();
+
+ ConfigQuoteKind::Enum(enum_kind, variants)
+ }
+ syn::Data::Union(_) => {
+ abort!(
+ ident,
+ "Untagged unions are not supported. Consider using an enum instead."
+ );
+ }
+ };
+
+ let docs = extract_docs_from_attributes(input.attrs.iter());
+
+ let config_desc = ConfigQuote {
+ kind: config_desc_kind,
+ docs,
+ ident,
+ };
+
+ let expanded = quote! {
+ impl ::tedge_api::config::AsConfig for #ident {
+ fn as_config() -> ::tedge_api::config::ConfigDescription {
+ #config_desc
+ }
+ }
+ };
+
+ TS::from(expanded)
+}
diff --git a/crates/core/tedge_api/tests/derive_config.rs b/crates/core/tedge_api/tests/derive_config.rs
new file mode 100644
index 00000000..dfe57ef3
--- /dev/null
+++ b/crates/core/tedge_api/tests/derive_config.rs
@@ -0,0 +1,66 @@
+#![allow(unused, dead_code)]
+
+use pretty_assertions::assert_eq;
+use tedge_api::{AsConfig, Config, ConfigDescription, ConfigKind};
+
+/// Some Config
+#[derive(Debug, Config)]
+struct SimpleConfig {
+ /// The port to connect to
+ port: Port,
+ name: String,
+ /// A nested configuration
+ ///
+ /// # This also includes markdown
+ ///
+ /// And can go over several _lines_
+ nested: NestedConfig,
+}
+
+#[derive(Debug, Config)]
+/// Nested configuration can have its own documentation
+struct NestedConfig {
+ num: EnumConfig,
+}
+
+#[derive(Debug, Config)]
+struct Port(u16);
+
+#[derive(Debug, Config)]
+#[config(tag = "type")]
+/// An enum configuration
+enum EnumConfig {
+ String(String),
+ Num(u64),
+ /// Some docs on the complex type
+ Complex {
+ /// The port of the inner complex type
+ port: Port,