Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ members = [
resolver = "3"

[workspace.package]
version = "5.5.0"
version = "5.5.1"
repository = "https://github.com/cloudflare/foundations"
edition = "2024"
authors = ["Cloudflare"]
Expand Down Expand Up @@ -37,8 +37,8 @@ check-cfg = [

[workspace.dependencies]
anyhow = "1.0.75"
foundations = { version = "5.5.0", path = "./foundations" }
foundations-macros = { version = "=5.5.0", path = "./foundations-macros", default-features = false }
foundations = { version = "5.5.1", path = "./foundations" }
foundations-macros = { version = "=5.5.1", path = "./foundations-macros", default-features = false }
bindgen = { version = "0.72", default-features = false }
cc = "1.0"
cf-rustracing = "1.2.1"
Expand Down
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@

5.5.1
- 2026-03-06 Fix regression in serde argument parsing

5.5.0
- 2026-03-05 Release 5.5.0
- 2026-03-05 Update list of allowed licenses for dependencies
- 2026-03-05 chore: Upgrade internal dependency to avoid dupes
- 2026-03-05 Mention libclang-dev dependency for bindgen
Expand Down
57 changes: 34 additions & 23 deletions foundations-macros/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,19 @@ impl Parse for Options {
}

#[derive(FromMeta)]
#[darling(allow_unknown_fields)]
struct SerdeArgs {
flatten: Flag,
default: Option<Path>,
}

impl SerdeArgs {
fn parse_from_attrs(attrs: &[Attribute]) -> Option<Self> {
let attr = attrs.iter().find(|a| a.path().is_ident("serde"))?;
Self::from_meta(&attr.meta).ok()
fn parse_from_attrs(attrs: &[Attribute]) -> Result<Option<Self>> {
let Some(attr) = attrs.iter().find(|a| a.path().is_ident("serde")) else {
return Ok(None);
};

Ok(Some(Self::from_meta(&attr.meta)?))
}
}

Expand Down Expand Up @@ -166,7 +170,7 @@ fn expand_struct(options: Options, item: &mut ItemStruct) -> Result<proc_macro2:
let impl_settings = impl_settings_trait(&options, item)?;

let impl_default = if options.impl_default {
impl_serde_aware_default(item)
impl_serde_aware_default(item)?
} else {
quote!()
};
Expand Down Expand Up @@ -341,40 +345,47 @@ fn is_array_zst(ty: &Type) -> bool {

/// Returns whether `attrs` contains `serde(flatten)`.
fn is_serde_flattened(attrs: &[Attribute]) -> bool {
SerdeArgs::parse_from_attrs(attrs).is_some_and(|a| a.flatten.is_present())
matches!(
SerdeArgs::parse_from_attrs(attrs),
Ok(Some(args)) if args.flatten.is_present(),
)
}

fn impl_serde_aware_default(item: &ItemStruct) -> proc_macro2::TokenStream {
fn impl_serde_aware_default(item: &ItemStruct) -> Result<proc_macro2::TokenStream> {
let name = &item.ident;
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();

let initializers = item.fields.iter().map(|field| {
let name = field
.ident
.as_ref()
.expect("should not generate field docs for tuple struct");
let initializers = item
.fields
.iter()
.map(|field| {
let name = field
.ident
.as_ref()
.expect("should not generate field docs for tuple struct");

let span = field.ty.span();
let span = field.ty.span();

let cfg_attrs = field
.attrs
.iter()
.filter(|attr| attr.path().is_ident("cfg"));
let cfg_attrs = field
.attrs
.iter()
.filter(|attr| attr.path().is_ident("cfg"));

let function_path = SerdeArgs::parse_from_attrs(&field.attrs)
.and_then(|a| Some(a.default?.into_token_stream()))
.unwrap_or_else(|| quote_spanned! { span=> Default::default });
let function_path = SerdeArgs::parse_from_attrs(&field.attrs)?
.and_then(|a| Some(a.default?.into_token_stream()))
.unwrap_or_else(|| quote_spanned! { span=> Default::default });

quote_spanned! { span=> #(#cfg_attrs)* #name: #function_path() }
});
Ok(quote_spanned! { span=> #(#cfg_attrs)* #name: #function_path() })
})
.collect::<Result<Vec<_>>>()?;

quote! {
Ok(quote! {
impl #impl_generics Default for #name #ty_generics #where_clause {
fn default() -> Self {
Self { #(#initializers,)* }
}
}
}
})
}

#[cfg(test)]
Expand Down
19 changes: 18 additions & 1 deletion foundations/tests/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ struct WithMap {
struct WithOption {
/// Optional field
optional: Option<NestedStruct>,
/// A field that is not parsed at all.
#[serde(skip, default = "WithOption::default_a")]
a: u32,
}

impl WithOption {
fn default_a() -> u32 {
123
}
}

impl Default for NoDefaultStruct {
Expand Down Expand Up @@ -228,6 +237,10 @@ fn defaults() {

assert_eq!(nested_struct.inner.b, 0xb);
assert_eq!(nested_struct.x, 1);

let struct_with_skip = WithOption::default();
assert!(struct_with_skip.optional.is_none());
assert_eq!(struct_with_skip.a, 123);
}

#[test]
Expand Down Expand Up @@ -259,11 +272,15 @@ fn map() {
fn option() {
let s = WithOption {
optional: Some(NestedStruct { a: 1, b: 2, c: 3 }),
a: 4,
};

assert_ser_eq!(s, "data/with_option_some.yaml");

let s = WithOption { optional: None };
let s = WithOption {
optional: None,
a: 4,
};

assert_ser_eq!(s, "data/with_option_none.yaml");
}
Expand Down