Skip to content
Open
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
33 changes: 33 additions & 0 deletions buffa-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,39 @@ impl Config {
self
}

/// Add a custom attribute to generated oneof enums only (not message
/// structs, not regular enums) matching a proto path prefix.
///
/// Same path-matching semantics as [`type_attribute`](Self::type_attribute),
/// matched against the oneof's fully-qualified path
/// (`.my.pkg.MyMessage.my_oneof`). Useful when a oneof needs a different
/// attribute set than the surrounding types — for example to keep
/// `#[derive(serde::Serialize)]` on messages and oneofs while
/// [`enum_attribute`](Self::enum_attribute) gives the regular enums a
/// different serde derive.
///
/// # Example
///
/// ```rust,ignore
/// buffa_build::Config::new()
/// .oneof_attribute(".my.pkg", "#[derive(serde::Serialize)]")
/// .files(&["proto/my_service.proto"])
/// .includes(&["proto/"])
/// .compile()
/// .unwrap();
/// ```
#[must_use]
pub fn oneof_attribute(
mut self,
path: impl Into<String>,
attribute: impl Into<String>,
) -> Self {
self.codegen_config
.oneof_attributes
.push((normalize_attr_path(path.into()), attribute.into()));
self
}

/// Use `buf build` instead of `protoc` for descriptor generation.
///
/// `buf` is often easier to install and keep current than `protoc`
Expand Down
11 changes: 11 additions & 0 deletions buffa-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,16 @@ pub struct CodeGenConfig {
/// `#[derive(strum::EnumIter)]` when the user does not want to apply the
/// same attribute to every message in the matched scope.
pub enum_attributes: Vec<(String, String)>,
/// Custom attributes to inject on generated oneof enums only (not messages,
/// not regular enums).
///
/// Same path-matching semantics as `type_attributes`, matched against the
/// oneof's fully-qualified path (`.pkg.Message.oneof_name`). Useful when a
/// oneof needs a different attribute set than the surrounding types — e.g.
/// keeping `#[derive(serde::Serialize)]` on messages and oneofs while a
/// separate `enum_attributes` entry puts a different serde derive on the
/// regular enums.
pub oneof_attributes: Vec<(String, String)>,
/// Wrap generated `impl`s in `#[cfg(feature = "...")]` instead of
/// emitting them unconditionally.
///
Expand Down Expand Up @@ -613,6 +623,7 @@ impl Default for CodeGenConfig {
field_attributes: Vec::new(),
message_attributes: Vec::new(),
enum_attributes: Vec::new(),
oneof_attributes: Vec::new(),
gate_impls_on_crate_features: false,
generate_with_setters: true,
generate_reflection: false,
Expand Down
3 changes: 3 additions & 0 deletions buffa-codegen/src/oneof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ pub fn generate_oneof_enum(
crate::comments::doc_attrs_resolved(ctx.comment(&oneof_fqn), proto_fqn, &ctx.type_map);
let custom_type_attrs =
CodeGenContext::matching_attributes(&ctx.config.type_attributes, &oneof_fqn)?;
let custom_oneof_attrs =
CodeGenContext::matching_attributes(&ctx.config.oneof_attributes, &oneof_fqn)?;

// Variants whose field is `[debug_redact = true]` print a placeholder
// instead of their payload. The `Debug` derive is swapped for a manual
Expand Down Expand Up @@ -354,6 +356,7 @@ pub fn generate_oneof_enum(
#debug_derive
#arbitrary_derive
#custom_type_attrs
#custom_oneof_attrs
pub enum #rust_enum_ident {
#(#variants,)*
}
Expand Down
27 changes: 27 additions & 0 deletions buffa-codegen/src/tests/custom_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,33 @@ fn test_field_attribute_reaches_oneof_variant() {
);
}

#[test]
fn test_oneof_attribute_on_oneof_not_message_or_enum() {
let mut file = proto3_file("mix.proto");
file.package = Some("pkg".to_string());
file.message_type
.push(oneof_message("Msg", "payload", &["a", "b"]));
file.enum_type.push(EnumDescriptorProto {
name: Some("Color".to_string()),
value: vec![enum_value("RED", 0)],
..Default::default()
});
let config = CodeGenConfig {
generate_views: false,
oneof_attributes: vec![(".".to_string(), "#[derive(serde::Serialize)]".to_string())],
..CodeGenConfig::default()
};
let files = generate(&[file], &["mix.proto".to_string()], &config).expect("should generate");
let content = &joined(&files);
// Appears exactly once: on the oneof enum, not on the message struct or the
// plain enum.
assert_eq!(
content.matches("derive(serde::Serialize)").count(),
1,
"oneof_attribute should appear only on the oneof enum: {content}"
);
}

// ── malformed attributes fail loudly ────────────────────────────────

#[test]
Expand Down