4 Commits

Author SHA1 Message Date
57403929b6 Release v0.3.0 2024-11-06 21:24:04 +01:00
ec3cad1a05 Format README 2024-11-06 20:49:21 +01:00
ef0d9f4395 Only pass cfg attribute to impl blocks 2024-11-06 20:49:09 +01:00
7bd24ff0e7 Add check for every feature combination 2024-11-06 20:49:09 +01:00
7 changed files with 78 additions and 89 deletions

View File

@@ -13,7 +13,7 @@ categories.workspace = true
readme.workspace = true readme.workspace = true
[workspace.package] [workspace.package]
version = "0.4.0" version = "0.3.0"
edition = "2021" edition = "2021"
rust-version = "1.81" rust-version = "1.81"
@@ -26,7 +26,7 @@ categories = ["api-bindings"]
readme = "README.md" readme = "README.md"
[dependencies] [dependencies]
aws-macros = { path = "./aws_macros", version = "0.4.*" } aws-macros = { path = "./aws_macros", version = "0.3.*" }
aws-config = { version = "1.*", default-features = false } aws-config = { version = "1.*", default-features = false }
aws-sdk-ec2 = { version = "1.*", default-features = false, features = [ aws-sdk-ec2 = { version = "1.*", default-features = false, features = [
"rustls", "rustls",

View File

@@ -29,7 +29,6 @@ syn = { version = "2.*", default-features = false, features = [
"parsing", "parsing",
"printing", "printing",
"clone-impls", "clone-impls",
"derive",
] } ] }
[lints] [lints]

View File

@@ -15,7 +15,8 @@ pub fn Tags(attr: TokenStream, item: TokenStream) -> TokenStream {
tags::transform(attr, item) tags::transform(attr, item)
} }
#[proc_macro_derive(Tag, attributes(tag))] #[proc_macro_attribute]
pub fn tag(input: TokenStream) -> TokenStream { #[expect(non_snake_case, reason = "attribute proc macros should be capitalized")]
tag::transform(input) pub fn Tag(attr: TokenStream, item: TokenStream) -> TokenStream {
tag::transform(attr, item)
} }

View File

@@ -18,18 +18,19 @@ enum Translator {
Transparent(TransparentKind), Transparent(TransparentKind),
} }
fn parse_enum_attributes(attrs: &[syn::Attribute]) -> Option<syn::LitStr> { fn parse_enum_attributes(attrs: &mut Vec<syn::Attribute>) -> Option<syn::LitStr> {
let index_of_tag_attribute = attrs let index_of_tag_attribute = attrs
.iter() .iter()
.filter(|attr| attr.style == syn::AttrStyle::Outer) .enumerate()
.find_map(|attr| match attr.meta { .filter(|&(_i, attr)| attr.style == syn::AttrStyle::Outer)
.find_map(|(i, attr)| match attr.meta {
syn::Meta::List(ref meta_list) => { syn::Meta::List(ref meta_list) => {
if let (Some(name), 1) = ( if let (Some(name), 1) = (
meta_list.path.segments.first(), meta_list.path.segments.first(),
meta_list.path.segments.len(), meta_list.path.segments.len(),
) { ) {
if name.ident == "tag" { if name.ident == "tag" {
Some(meta_list.clone()) Some((i, meta_list.clone()))
} else { } else {
None None
} }
@@ -41,7 +42,11 @@ fn parse_enum_attributes(attrs: &[syn::Attribute]) -> Option<syn::LitStr> {
}); });
match index_of_tag_attribute { match index_of_tag_attribute {
Some(meta_list) => { Some((i, meta_list)) => {
// `i` came from `enumerate()` and is guaranteed to be in bounds
let removed_attribute = attrs.remove(i);
drop(removed_attribute);
let expr: syn::Expr = let expr: syn::Expr =
syn::parse(meta_list.tokens.into()).expect("expected expr in macro attribute"); syn::parse(meta_list.tokens.into()).expect("expected expr in macro attribute");
@@ -74,10 +79,10 @@ fn parse_enum_attributes(attrs: &[syn::Attribute]) -> Option<syn::LitStr> {
} }
} }
fn parse_transparent_enum(e: &syn::DataEnum) -> Translator { fn parse_transparent_enum(mut e: syn::ItemEnum) -> (Translator, syn::Item) {
let variants = e let variants = e
.variants .variants
.iter() .iter_mut()
.map(|variant| { .map(|variant| {
assert!( assert!(
variant.discriminant.is_none(), variant.discriminant.is_none(),
@@ -87,16 +92,25 @@ fn parse_transparent_enum(e: &syn::DataEnum) -> Translator {
syn::Fields::Unit => (), syn::Fields::Unit => (),
_ => panic!("enum cannot have fields in variants"), _ => panic!("enum cannot have fields in variants"),
} }
let rename = parse_enum_attributes(&variant.attrs); let rename = parse_enum_attributes(&mut variant.attrs);
(variant.ident.clone(), rename) (variant.ident.clone(), rename)
}) })
.collect::<Vec<(syn::Ident, Option<syn::LitStr>)>>(); .collect::<Vec<(syn::Ident, Option<syn::LitStr>)>>();
Translator::Transparent(TransparentKind::SimpleEnum { variants }) (
Translator::Transparent(TransparentKind::SimpleEnum { variants }),
e.into(),
)
} }
fn parse_tag_attribute(expr: syn::Expr, elem: &syn::Data) -> Translator { pub(crate) fn transform(attr: TokenStream, item: TokenStream) -> TokenStream {
let root = quote! {::aws_lib};
let expr: syn::Expr = syn::parse(attr).expect("expected expr in macro attribute");
let elem = syn::parse_macro_input!(item as syn::Item);
let syn::Expr::Assign(assign) = expr else { let syn::Expr::Assign(assign) = expr else {
panic!("invalid expression in macro attribute") panic!("invalid expression in macro attribute")
}; };
@@ -113,7 +127,7 @@ fn parse_tag_attribute(expr: syn::Expr, elem: &syn::Data) -> Translator {
_ => panic!("invalid expression in tag field attribute, left side"), _ => panic!("invalid expression in tag field attribute, left side"),
} }
match *assign.right { let (translator, elem) = match *assign.right {
syn::Expr::Path(ref exprpath) => { syn::Expr::Path(ref exprpath) => {
let segments = &exprpath.path.segments; let segments = &exprpath.path.segments;
let (Some(segment), 1) = (segments.first(), segments.len()) else { let (Some(segment), 1) = (segments.first(), segments.len()) else {
@@ -121,71 +135,45 @@ fn parse_tag_attribute(expr: syn::Expr, elem: &syn::Data) -> Translator {
}; };
match segment.ident.to_string().as_str() { match segment.ident.to_string().as_str() {
"serde" => Translator::Serde, "serde" => (Translator::Serde, elem),
"manual" => Translator::Manual, "manual" => (Translator::Manual, elem),
"transparent" => "transparent" => match elem {
{ syn::Item::Struct(ref s) => match s.fields {
#[expect( syn::Fields::Unnamed(ref fields) => {
clippy::match_wildcard_for_single_variants, let (Some(field), 1) = (fields.unnamed.first(), fields.unnamed.len())
reason = "just by chance is there only one additional variant" else {
)] panic!(
match *elem { "transparent translation is only available for newtype-style macros"
syn::Data::Struct(ref s) => match s.fields { )
syn::Fields::Unnamed(ref fields) => { };
let (Some(field), 1) = (
(fields.unnamed.first(), fields.unnamed.len())
else {
panic!(
"transparent translation is only available for newtype-style macros"
)
};
Translator::Transparent(TransparentKind::NewtypeStruct { Translator::Transparent(TransparentKind::NewtypeStruct {
ty: field.ty.clone(), ty: field.ty.clone(),
}) }),
} elem,
_ => panic!( )
"transparent translation is only available for newtype-style macros"
),
},
syn::Data::Enum(ref e) => parse_transparent_enum(e),
_ => {
panic!("transparent translation is only available for newtype-style macros")
} }
_ => panic!(
"transparent translation is only available for newtype-style macros"
),
},
syn::Item::Enum(e) => parse_transparent_enum(e),
_ => {
panic!("transparent translation is only available for newtype-style macros")
} }
} },
t => panic!("invalid translator {t}"), t => panic!("invalid translator {t}"),
} }
} }
_ => panic!("invalid expression in tag field attribute, left side"), _ => panic!("invalid expression in tag field attribute, left side"),
} };
}
pub(crate) fn transform(input: TokenStream) -> TokenStream { let name = match elem {
let root = quote! {::aws_lib}; syn::Item::Struct(ref s) => &s.ident,
syn::Item::Enum(ref e) => &e.ident,
let input = syn::parse_macro_input!(input as syn::DeriveInput); _ => panic!("only applicable to structs/enums"),
};
let expr = input
.attrs
.into_iter()
.find_map(|attr| match attr.meta {
syn::Meta::List(meta_list) => {
if meta_list.path.is_ident("tag") {
Some(
syn::parse2::<syn::Expr>(meta_list.tokens)
.expect("invalid expression in tag attribute"),
)
} else {
None
}
}
_ => None,
})
.expect("Tag derive macro requires a tag attribute");
let translator = parse_tag_attribute(expr, &input.data);
let name = input.ident;
let translator = match translator { let translator = match translator {
Translator::Serde => quote! { Translator::Serde => quote! {
@@ -283,6 +271,7 @@ pub(crate) fn transform(input: TokenStream) -> TokenStream {
}; };
quote! { quote! {
#elem
#translator #translator
} }
.into() .into()

View File

@@ -337,9 +337,9 @@ impl fmt::Display for SubnetId {
macro_rules! string_newtype { macro_rules! string_newtype {
($name:ident) => { ($name:ident) => {
#[Tag(translate = transparent)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Eq, PartialEq, Tag)] #[derive(Debug, Clone, Eq, PartialEq)]
#[tag(translate = transparent)]
pub struct $name(String); pub struct $name(String);
impl std::fmt::Display for $name { impl std::fmt::Display for $name {
@@ -422,9 +422,9 @@ impl TryFrom<aws_sdk_ec2::types::Image> for Ami {
} }
} }
#[Tag(translate = manual)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Tag, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[tag(translate = manual)]
pub struct Timestamp(DateTime<Utc>); pub struct Timestamp(DateTime<Utc>);
impl Timestamp { impl Timestamp {

View File

@@ -51,14 +51,14 @@ strategies:
- `impl From<T> for RawTagValue`: How to turn `T` back into the value of a - `impl From<T> for RawTagValue`: How to turn `T` back into the value of a
tag. This cannot fail. tag. This cannot fail.
There is a [`macro@Tag`] derive macro that selects the strategy, which can then There is a [`macro@Tag`] macro that selects the strategy, which can then be used
be used in a struct that is using `#[Tags]`: in a struct that is using `#[Tags]`:
```rust ```rust
use aws_lib::tags::{Tag, Tags}; use aws_lib::tags::{Tag, Tags};
#[derive(Tag, Debug, Clone, PartialEq, Eq)] #[Tag(translate = transparent)]
#[tag(translate = transparent)] #[derive(Debug, Clone, PartialEq, Eq)]
struct MyTag(String); struct MyTag(String);
#[Tags] #[Tags]

View File

@@ -309,16 +309,16 @@ mod tests {
use super::*; use super::*;
#[derive(Tag, Debug, Clone, PartialEq, Eq)] #[Tag(translate = manual)]
#[tag(translate = manual)] #[derive(Debug, Clone, PartialEq, Eq)]
enum MyTag { enum MyTag {
A, A,
B, B,
} }
#[cfg(feature = "serde-tags")] #[cfg(feature = "serde-tags")]
#[derive(Tag, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[Tag(translate = serde)]
#[tag(translate = serde)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct MyStructTag { struct MyStructTag {
foo: String, foo: String,
bar: bool, bar: bool,
@@ -426,8 +426,8 @@ mod tests {
#[test] #[test]
fn test_transparent_tag() { fn test_transparent_tag() {
#[derive(Tag, PartialEq, Debug)] #[Tag(translate = transparent)]
#[tag(translate = transparent)] #[derive(PartialEq, Debug)]
struct MyTag(String); struct MyTag(String);
assert_eq!( assert_eq!(
@@ -442,8 +442,8 @@ mod tests {
#[test] #[test]
fn test_enums() { fn test_enums() {
#[derive(PartialEq, Debug, Tag)] #[Tag(translate = transparent)]
#[tag(translate = transparent)] #[derive(PartialEq, Debug)]
enum MyCoolioTag { enum MyCoolioTag {
A, A,
#[tag(rename = "C")] #[tag(rename = "C")]