Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proc macro for tedge config #1936

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
375bc6c
POC proc macro for tedge config
jarhodes314 Apr 28, 2023
fb59fda
Make example realistic
jarhodes314 Apr 28, 2023
5e9b80d
Refactor tests and example into appropriate places
jarhodes314 May 2, 2023
f4c67a1
Ensure missing key error contains the field name
jarhodes314 May 2, 2023
1aab677
Store the key regardless of success/failure
jarhodes314 May 2, 2023
771db4c
Replace all or nothing logic in macro with library function
jarhodes314 May 3, 2023
80fe910
Add doc comments and support for read only configurations
jarhodes314 May 3, 2023
ffff8d1
Begin to use new macro in tedge_config
jarhodes314 May 9, 2023
8d59006
Migrate old accessors to new DTOs
jarhodes314 May 9, 2023
c7490e9
Fix test failures in tedge
jarhodes314 May 9, 2023
1868294
Merge remote-tracking branch 'upstream/main' into feat/tedge-config-r…
jarhodes314 May 9, 2023
86af093
Update tedge config set to use the new dto
jarhodes314 May 9, 2023
28dcee3
Migrate `tedge config unset` to new dto
jarhodes314 May 9, 2023
8cd46ad
Fix clippy warning
jarhodes314 May 9, 2023
6471e1f
Migrate `tedge config list` to new dto/reader
jarhodes314 May 9, 2023
98f4a85
Run formatter
jarhodes314 May 9, 2023
6e2e3d0
Actually support renaming
jarhodes314 May 9, 2023
6556d1c
Update references to renamed keys
jarhodes314 May 9, 2023
98e5201
Migrate `tedge cert` to new dto
jarhodes314 May 10, 2023
5b513a0
Fix integration tests and add some tests for backwards compatibility
jarhodes314 May 10, 2023
4696954
Merge remote-tracking branch 'upstream/main' into feat/tedge-config-r…
jarhodes314 May 11, 2023
5d3e4e4
Finish implementing private reader fields and optional paths
jarhodes314 May 11, 2023
81b7193
Support renaming groups
jarhodes314 May 11, 2023
ce77b22
Log only if we actually migrate tedge.toml
jarhodes314 May 11, 2023
b2ef8fa
Add more documentation and tests for the macro, fix clippy errors
jarhodes314 May 11, 2023
3b95375
Add documentation
jarhodes314 May 12, 2023
501b756
Fix unused dependency error
jarhodes314 May 15, 2023
d2063d5
Merge remote-tracking branch 'upstream/main' into feat/tedge-config-r…
jarhodes314 May 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 109 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions crates/common/tedge_config_macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "tedge_config_macros"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
camino = { version = "*", features = ["serde1"] }
doku = "0.21.0"
once_cell = "*"
serde = "*"
tedge_config_macros-macro = { path = "macro" }
thiserror = "1.0"
tracing = "*"
url = "*"
14 changes: 14 additions & 0 deletions crates/common/tedge_config_macros/impl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "tedge_config_macros-impl"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
darling = "0.14.4"
heck = "0.4.1"
optional-error = "0.1.1"
proc-macro2 = "1"
quote = "1"
syn = { version = "1", features = ["full", "extra-traits"] }
96 changes: 96 additions & 0 deletions crates/common/tedge_config_macros/impl/src/dto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse_quote_spanned;
use syn::spanned::Spanned;

use crate::input::FieldOrGroup;
use crate::prefixed_type_name;

pub fn generate(name: proc_macro2::Ident, items: &[FieldOrGroup]) -> TokenStream {
let mut idents = Vec::new();
let mut tys = Vec::<syn::Type>::new();
let mut sub_dtos = Vec::new();
let mut attrs = Vec::new();

for item in items {
match item {
FieldOrGroup::Field(field) => {
if !field.dto().skip && field.read_only().is_none() {
idents.push(field.ident());
tys.push({
let ty = field.ty();
parse_quote_spanned!(ty.span()=> Option<#ty>)
});
sub_dtos.push(None);
attrs.push(field.attrs().iter().filter(is_preserved).collect());
}
}
FieldOrGroup::Group(group) => {
if !group.dto.skip {
let sub_dto_name = prefixed_type_name(&name, group);
idents.push(&group.ident);
tys.push(parse_quote_spanned!(group.ident.span()=> #sub_dto_name));
sub_dtos.push(Some(generate(sub_dto_name, &group.contents)));
attrs.push(Vec::new());
}
}
}
}

quote! {
#[derive(Debug, Default, ::serde::Deserialize, ::serde::Serialize)]
#[non_exhaustive]
pub struct #name {
#(
#(#attrs)*
#idents: #tys,
)*
}

#(#sub_dtos)*
}
}

fn is_preserved(attr: &&syn::Attribute) -> bool {
match attr.parse_meta() {
// Maybe cfg is useful. Certainly seems sensible to preserve it
Ok(syn::Meta::List(list)) => list.path.is_ident("serde") || list.path.is_ident("cfg"),
Ok(syn::Meta::NameValue(nv)) => nv.path.is_ident("doc"),
_ => false,
}
}

#[cfg(test)]
mod tests {
use syn::parse_quote;

use super::*;

#[test]
fn doc_comments_are_preserved() {
assert!(is_preserved(&&parse_quote!(
/// Test
)))
}

#[test]
fn serde_attributes_are_preserved() {
assert!(is_preserved(&&parse_quote!(
#[serde(rename = "something")]
)))
}

#[test]
fn unrecognised_attributes_are_not_preserved() {
assert!(!is_preserved(&&parse_quote!(
#[unknown_crate(unknown_bool)]
)))
}

#[test]
fn unrecognised_attributes_of_the_wrong_type_are_not_preserved() {
assert!(!is_preserved(&&parse_quote!(
#[unknown_attribute = "some value"]
)))
}
}
15 changes: 15 additions & 0 deletions crates/common/tedge_config_macros/impl/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use optional_error::OptionalError;

pub fn combine_errors<T>(
items: impl Iterator<Item = Result<T, syn::Error>>,
) -> Result<Vec<T>, syn::Error> {
let mut error = OptionalError::default();
let mut successful_values = Vec::new();
for item in items {
match item {
Ok(value) => successful_values.push(value),
Err(e) => error.combine(e),
}
}
error.try_throw().and(Ok(successful_values))
}
4 changes: 4 additions & 0 deletions crates/common/tedge_config_macros/impl/src/input/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod parse;
mod validate;
Comment on lines +1 to +2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't help but remember Parse, don’t validate ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ironically, that's pretty much what validate is doing. It could do with clearer naming to distinguish between "things that can be parsed with syn and darling" and "things that are meaningful for the rest of the application, after e.g. converting boolean flags into enum discriminants" (which is currently in the validate module, even though it's following the idea of "Parse, don't validate").


pub use validate::*;
Loading