Skip to content

Commit fbc946c

Browse files
authored
Merge pull request softprops#137 from Veetaha/feat/cleanup-from-into-attrs-traits
Cleanup From/IntoAttributes traits
2 parents 9ba849c + 40b9d87 commit fbc946c

14 files changed

+695
-154
lines changed

dynomite-derive/src/attr.rs

Lines changed: 124 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,24 @@ use syn::{
66
Ident, LitStr, Token,
77
};
88

9+
/// Represents a parsed attribute that appears in `#[dynomite(...)]`.
910
#[derive(Clone)]
10-
pub(crate) struct Attr {
11+
pub(crate) struct Attr<Kind> {
12+
/// The identifier part of the attribute (e.g. `rename` in `#[dynomite(rename = "foo"`))
1113
pub(crate) ident: Ident,
12-
pub(crate) kind: AttrKind,
14+
/// More specific information about the metadata entry.
15+
pub(crate) kind: Kind,
1316
}
1417

18+
/// Attribute that appears on record fields (struct fields and enum record variant fields)
19+
pub(crate) type FieldAttr = Attr<FieldAttrKind>;
20+
/// Attribute that appears on the top level of an enum
21+
pub(crate) type EnumAttr = Attr<EnumAttrKind>;
22+
/// Attribute that appears on enum varinats
23+
pub(crate) type VariantAttr = Attr<VariantAttrKind>;
24+
1525
#[derive(Clone)]
16-
pub(crate) enum AttrKind {
26+
pub(crate) enum FieldAttrKind {
1727
/// Denotes field should be replaced with Default impl when absent in ddb
1828
Default,
1929
/// Denotes field should be renamed to value of ListStr
@@ -26,51 +36,122 @@ pub(crate) enum AttrKind {
2636
Flatten,
2737
}
2838

29-
impl Attr {
30-
fn new(
31-
ident: Ident,
32-
kind: AttrKind,
33-
) -> Self {
34-
Self { ident, kind }
39+
impl DynomiteAttr for FieldAttrKind {
40+
const KVS: Kvs<Self> = &[("rename", FieldAttrKind::Rename)];
41+
const KEYS: Keys<Self> = &[
42+
("default", FieldAttrKind::Default),
43+
("partition_key", FieldAttrKind::PartitionKey),
44+
("sort_key", FieldAttrKind::SortKey),
45+
("flatten", FieldAttrKind::Flatten),
46+
];
47+
}
48+
49+
#[derive(Clone)]
50+
pub(crate) enum EnumAttrKind {
51+
// FIXME: implement content attribute to support non-map values in enum variants
52+
// (adjacently tagged enums: https://serde.rs/enum-representations.html#adjacently-tagged)
53+
// Content(LitStr),
54+
/// The name of the tag field for an internally-tagged enum
55+
Tag(LitStr),
56+
}
57+
58+
impl DynomiteAttr for EnumAttrKind {
59+
const KVS: Kvs<Self> = &[("tag", EnumAttrKind::Tag)];
60+
}
61+
#[derive(Clone)]
62+
pub(crate) enum VariantAttrKind {
63+
// TODO: add default for enum variants?
64+
Rename(LitStr),
65+
}
66+
67+
impl DynomiteAttr for VariantAttrKind {
68+
const KVS: Kvs<Self> = &[("rename", VariantAttrKind::Rename)];
69+
}
70+
71+
type Kvs<T> = &'static [(&'static str, fn(syn::LitStr) -> T)];
72+
type Keys<T> = &'static [(&'static str, T)];
73+
74+
/// Helper to ease defining `#[dynomite(key)` and `#[dynomite(key = "val")` attributes
75+
pub(crate) trait DynomiteAttr: Clone + Sized + 'static {
76+
/// List of `("attr_name", enum_variant_constructor)` to define attributes
77+
/// that require a value string literal (e.g. `rename = "foo"`)
78+
const KVS: Kvs<Self> = &[];
79+
/// List of `("attr_name", enum_variant_value)` entires to define attributes
80+
/// that should not have any value (e.g. `default` or `flatten`)
81+
const KEYS: Keys<Self> = &[];
82+
}
83+
84+
impl<A: DynomiteAttr> Parse for Attr<A> {
85+
fn parse(input: ParseStream) -> syn::Result<Self> {
86+
let entry: MetadataEntry = input.parse()?;
87+
let kind = entry
88+
.try_attr_with_val(A::KVS)
89+
.or_else(|| entry.try_attr_without_val(A::KEYS))
90+
.unwrap_or_else(|| abort!(entry.key, "unexpected dynomite attribute: {}", entry.key));
91+
Ok(Attr {
92+
ident: entry.key,
93+
kind,
94+
})
95+
}
96+
}
97+
98+
struct MetadataEntry {
99+
key: Ident,
100+
val: Option<LitStr>,
101+
}
102+
103+
impl MetadataEntry {
104+
/// Attempt to map the parsed entry to an identifier-only attribute from the list
105+
fn try_attr_without_val<T: Clone>(
106+
&self,
107+
mappings: Keys<T>,
108+
) -> Option<T> {
109+
let Self { key, val } = self;
110+
let key_str = key.to_string();
111+
mappings
112+
.iter()
113+
.find(|(key_pat, _)| *key_pat == key_str)
114+
.map(|(_, enum_val)| match val {
115+
Some(_) => abort!(key, "expected no value for dynomite attribute `{}`", key),
116+
None => enum_val.clone(),
117+
})
118+
}
119+
120+
/// Attempt to map the parsed entry to a key-value attribute from the list
121+
fn try_attr_with_val<T>(
122+
&self,
123+
mappings: Kvs<T>,
124+
) -> Option<T> {
125+
let Self { key, val } = self;
126+
let key_str = key.to_string();
127+
mappings
128+
.iter()
129+
.find(|(key_pat, _)| *key_pat == key_str)
130+
.map(|(_, to_enum)| match val {
131+
Some(it) => to_enum(it.clone()),
132+
None => abort!(
133+
key,
134+
"expected a value for dynomite attribute: `{} = \"foo\"`",
135+
key
136+
),
137+
})
35138
}
36139
}
37140

38-
impl Parse for Attr {
141+
impl Parse for MetadataEntry {
39142
fn parse(input: ParseStream) -> syn::Result<Self> {
40-
let name: Ident = input.parse()?;
41-
let name_str = name.to_string();
42-
if input.peek(Token![=]) {
43-
// `name = value` attributes.
44-
let assign = input.parse::<Token![=]>()?; // skip '='
45-
if input.peek(LitStr) {
46-
let lit: LitStr = input.parse()?;
47-
match &*name_str {
48-
"rename" => Ok(Attr::new(name, AttrKind::Rename(lit))),
49-
unsupported => abort! {
50-
name,
51-
"unsupported dynomite {} attribute",
52-
unsupported
53-
},
54-
}
55-
} else {
56-
abort! {
57-
assign,
58-
"expected `string literal` after `=`"
59-
};
60-
}
61-
} else if input.peek(syn::token::Paren) {
143+
let key: Ident = input.parse()?;
144+
if input.peek(syn::token::Paren) {
62145
// `name(...)` attributes.
63-
abort!(name, "unexpected dynomite attribute: {}", name_str);
64-
} else {
65-
// Attributes represented with a sole identifier.
66-
let kind = match name_str.as_ref() {
67-
"default" => AttrKind::Default,
68-
"partition_key" => AttrKind::PartitionKey,
69-
"sort_key" => AttrKind::SortKey,
70-
"flatten" => AttrKind::Flatten,
71-
_ => abort!(name, "unexpected dynomite attribute: {}", name_str),
72-
};
73-
Ok(Attr::new(name, kind))
146+
abort!(key, "unexpected paren in dynomite attribute: {}", key);
74147
}
148+
Ok(Self {
149+
key,
150+
val: input
151+
.parse::<Token![=]>()
152+
.ok()
153+
.map(|_| input.parse())
154+
.transpose()?,
155+
})
75156
}
76157
}

0 commit comments

Comments
 (0)