Skip to content

Commit 7e80c16

Browse files
clauderyo33
authored andcommitted
Add parse_ext container attribute for ParseDocument derive
Add support for parsing struct fields from extension namespace ($ext-type) instead of record fields. When #[eure(parse_ext)] is specified on a struct, the generated code uses ctx.parse_extension() and ext.parse_ext() instead of ctx.parse_record() and rec.parse_field(). This allows types like ParsedRecordFieldSchema to be derived rather than manually implemented.
1 parent 9ce7385 commit 7e80c16

File tree

4 files changed

+103
-0
lines changed

4 files changed

+103
-0
lines changed

crates/eure-macros/src/attrs/container.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ pub struct ContainerAttrs {
1212
/// Renames all struct variant fields in an enum.
1313
/// Unlike `rename_all`, this only applies to fields within struct variants.
1414
pub rename_all_fields: Option<RenameAll>,
15+
/// Parse fields from extension namespace ($ext-type) instead of record fields.
16+
/// When true, uses `ctx.parse_extension()` and `ext.parse_ext()` instead of
17+
/// `ctx.parse_record()` and `rec.parse_field()`.
18+
pub parse_ext: bool,
1519
}

crates/eure-macros/src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ pub struct MacroConfig {
77
pub document_crate: TokenStream,
88
pub rename_all: Option<RenameAll>,
99
pub rename_all_fields: Option<RenameAll>,
10+
/// Parse fields from extension namespace instead of record fields.
11+
pub parse_ext: bool,
1012
}
1113

1214
impl MacroConfig {
@@ -20,6 +22,7 @@ impl MacroConfig {
2022
document_crate,
2123
rename_all: attrs.rename_all,
2224
rename_all_fields: attrs.rename_all_fields,
25+
parse_ext: attrs.parse_ext,
2326
}
2427
}
2528
}

crates/eure-macros/src/parse_document/parse_record.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ fn generate_named_struct(
2424
context: &MacroContext,
2525
ident: &syn::Ident,
2626
fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
27+
) -> TokenStream {
28+
if context.config.parse_ext {
29+
generate_named_struct_from_ext(context, ident, fields)
30+
} else {
31+
generate_named_struct_from_record(context, ident, fields)
32+
}
33+
}
34+
35+
fn generate_named_struct_from_record(
36+
context: &MacroContext,
37+
ident: &syn::Ident,
38+
fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
2739
) -> TokenStream {
2840
let field_names: Vec<_> = fields
2941
.iter()
@@ -44,6 +56,30 @@ fn generate_named_struct(
4456
})
4557
}
4658

59+
fn generate_named_struct_from_ext(
60+
context: &MacroContext,
61+
ident: &syn::Ident,
62+
fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
63+
) -> TokenStream {
64+
let field_names: Vec<_> = fields
65+
.iter()
66+
.map(|f| f.ident.as_ref().expect("named fields must have names"))
67+
.collect();
68+
let field_name_strs: Vec<_> = field_names
69+
.iter()
70+
.map(|n| context.apply_rename(&n.to_string()))
71+
.collect();
72+
73+
context.impl_parse_document(quote! {
74+
let mut ext = ctx.parse_extension();
75+
let value = #ident {
76+
#(#field_names: ext.parse_ext(#field_name_strs)?),*
77+
};
78+
ext.allow_unknown_extensions();
79+
Ok(value)
80+
})
81+
}
82+
4783
fn generate_unit_struct(context: &MacroContext, ident: &syn::Ident) -> TokenStream {
4884
context.impl_parse_document(quote! {
4985
ctx.parse::<()>()?;

crates/eure-macros/src/parse_document/parse_record/tests.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,63 @@ fn test_named_fields_struct_with_rename_all_kebab_case() {
187187
.to_string()
188188
);
189189
}
190+
191+
#[test]
192+
fn test_parse_ext_basic() {
193+
let input = generate(parse_quote! {
194+
#[eure(parse_ext)]
195+
struct ExtFields {
196+
optional: bool,
197+
deprecated: bool,
198+
}
199+
});
200+
assert_eq!(
201+
input.to_string(),
202+
quote! {
203+
impl<'doc,> ::eure::document::parse::ParseDocument<'doc> for ExtFields<> {
204+
type Error = ::eure::document::parse::ParseError;
205+
206+
fn parse(ctx: &::eure::document::parse::ParseContext<'doc>) -> Result<Self, Self::Error> {
207+
let mut ext = ctx.parse_extension();
208+
let value = ExtFields {
209+
optional: ext.parse_ext("optional")?,
210+
deprecated: ext.parse_ext("deprecated")?
211+
};
212+
ext.allow_unknown_extensions();
213+
Ok(value)
214+
}
215+
}
216+
}
217+
.to_string()
218+
);
219+
}
220+
221+
#[test]
222+
fn test_parse_ext_with_rename_all() {
223+
let input = generate(parse_quote! {
224+
#[eure(parse_ext, rename_all = "kebab-case")]
225+
struct ExtFields {
226+
binding_style: String,
227+
deny_untagged: bool,
228+
}
229+
});
230+
assert_eq!(
231+
input.to_string(),
232+
quote! {
233+
impl<'doc,> ::eure::document::parse::ParseDocument<'doc> for ExtFields<> {
234+
type Error = ::eure::document::parse::ParseError;
235+
236+
fn parse(ctx: &::eure::document::parse::ParseContext<'doc>) -> Result<Self, Self::Error> {
237+
let mut ext = ctx.parse_extension();
238+
let value = ExtFields {
239+
binding_style: ext.parse_ext("binding-style")?,
240+
deny_untagged: ext.parse_ext("deny-untagged")?
241+
};
242+
ext.allow_unknown_extensions();
243+
Ok(value)
244+
}
245+
}
246+
}
247+
.to_string()
248+
);
249+
}

0 commit comments

Comments
 (0)