From de889f839f2d59654d3bd1f6b421f867aa97679e Mon Sep 17 00:00:00 2001 From: Randolf Jung Date: Sat, 9 May 2026 05:54:21 -0700 Subject: [PATCH] fix!: bump buffa to 0.5.2, update edition to 2024, all deps to latest - patch.crates-io now pins buffa v0.5.2 (4c264dd) and bumps the dep versions in every crate from 0.4 to 0.5. - workspace edition 2024 + rust-version 1.95 (latest stable). - connectrpc bumped 0.3 -> 0.4. - codegen: remove `ref` patterns from emitted oneof / cel match arms so the generated code parses under edition 2024 (binding modes are now inferred when matching through implicit borrows). - collapse newly-flagged `clippy::collapsible_if` sites in the codegen modules using `let` chains rather than allowing the lint. - conformance: 2872/2872 still pass against the upstream harness. BREAKING CHANGE: requires Rust 1.95+ and edition 2024; depends on buffa 0.5 (was 0.4) and connectrpc 0.4 (was 0.3). --- Cargo.lock | 124 ++-- Cargo.toml | 14 +- .../protoc-gen-protovalidate-buffa/Cargo.toml | 4 +- .../src/emit/cel.rs | 16 +- .../src/emit/field.rs | 209 +++--- .../src/emit/oneof.rs | 6 +- .../src/emit/repeated.rs | 677 +++++++++--------- .../src/scan.rs | 66 +- .../Cargo.toml | 10 +- crates/protovalidate-buffa-macros/Cargo.toml | 2 +- crates/protovalidate-buffa-macros/src/lib.rs | 49 +- crates/protovalidate-buffa-protos/Cargo.toml | 6 +- crates/protovalidate-buffa/Cargo.toml | 4 +- crates/protovalidate-buffa/src/cel.rs | 4 +- crates/protovalidate-buffa/src/rules.rs | 8 +- .../protovalidate-buffa/tests/connect_impl.rs | 2 +- 16 files changed, 594 insertions(+), 607 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0fd6529..468c3ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,8 +108,8 @@ checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] name = "buffa" -version = "0.3.0" -source = "git+https://github.com/anthropics/buffa?rev=b065d14dbd545a25ba30974384e505b4bc072771#b065d14dbd545a25ba30974384e505b4bc072771" +version = "0.5.2" +source = "git+https://github.com/anthropics/buffa?rev=4c264ddd6fdd286f237dac0551fd420907b4864c#4c264ddd6fdd286f237dac0551fd420907b4864c" dependencies = [ "base64", "bytes", @@ -120,38 +120,22 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "buffa" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe57265ac5858082bf9a450654fa56e97fceda045d3e573eed4c2d3cf0b8dc0f" -dependencies = [ - "bytes", - "hashbrown 0.15.5", - "once_cell", - "serde", - "serde_json", - "thiserror 2.0.18", -] - [[package]] name = "buffa-build" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c5dfbfc51f615a6d67db6143811fa72bb4f1c71402b776948eaea1940200cc" +version = "0.5.2" +source = "git+https://github.com/anthropics/buffa?rev=4c264ddd6fdd286f237dac0551fd420907b4864c#4c264ddd6fdd286f237dac0551fd420907b4864c" dependencies = [ - "buffa 0.4.0", + "buffa", "buffa-codegen", "tempfile", ] [[package]] name = "buffa-codegen" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e4d72f8cdc202c1939f8f74db0baf9c1035c7354214f2d8051ad99a9dfef53" +version = "0.5.2" +source = "git+https://github.com/anthropics/buffa?rev=4c264ddd6fdd286f237dac0551fd420907b4864c#4c264ddd6fdd286f237dac0551fd420907b4864c" dependencies = [ - "buffa 0.4.0", + "buffa", "buffa-descriptor", "prettyplease", "proc-macro2", @@ -162,20 +146,18 @@ dependencies = [ [[package]] name = "buffa-descriptor" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8473359a7a8cbad218bd2f0aa635d59a51b9a5dc2de3e50b125679dd411371a2" +version = "0.5.2" +source = "git+https://github.com/anthropics/buffa?rev=4c264ddd6fdd286f237dac0551fd420907b4864c#4c264ddd6fdd286f237dac0551fd420907b4864c" dependencies = [ - "buffa 0.4.0", + "buffa", ] [[package]] name = "buffa-types" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4e05bf231bdefa79fdb7326be05e3b5be3809ec70bb2cb5dbd8b7f6944ed53" +version = "0.5.2" +source = "git+https://github.com/anthropics/buffa?rev=4c264ddd6fdd286f237dac0551fd420907b4864c#4c264ddd6fdd286f237dac0551fd420907b4864c" dependencies = [ - "buffa 0.4.0", + "buffa", "bytes", "serde", "serde_json", @@ -270,13 +252,13 @@ checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "connectrpc" -version = "0.3.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9191b8c90cfc0d27f3df209ea83fa1cb1f9b7e81480bc9d89661be41350778" +checksum = "55cb80f9eb09b593a96880eb1c2588ac6319b129519de8c672f9b8b4ffdeaece" dependencies = [ "async-compression", "base64", - "buffa 0.3.0", + "buffa", "bytes", "flate2", "futures", @@ -292,6 +274,7 @@ dependencies = [ "tokio-util", "tower", "tracing", + "wasm-bindgen-futures", "zstd", ] @@ -346,6 +329,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -599,10 +583,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -802,7 +788,7 @@ name = "protoc-gen-protovalidate-buffa" version = "0.1.2" dependencies = [ "anyhow", - "buffa 0.4.0", + "buffa", "buffa-codegen", "prettyplease", "proc-macro2", @@ -815,7 +801,7 @@ dependencies = [ name = "protovalidate-buffa" version = "0.1.0" dependencies = [ - "buffa 0.4.0", + "buffa", "cel", "chrono", "connectrpc", @@ -834,7 +820,7 @@ name = "protovalidate-buffa-conformance" version = "0.0.3" dependencies = [ "anyhow", - "buffa 0.4.0", + "buffa", "buffa-build", "buffa-codegen", "buffa-types", @@ -860,7 +846,7 @@ dependencies = [ name = "protovalidate-buffa-protos" version = "0.1.2" dependencies = [ - "buffa 0.4.0", + "buffa", "buffa-build", "buffa-types", ] @@ -1143,9 +1129,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "pin-project-lite", @@ -1282,9 +1268,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -1293,11 +1279,21 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1305,9 +1301,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -1318,9 +1314,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -1551,6 +1547,12 @@ dependencies = [ "syn", ] +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + [[package]] name = "zmij" version = "1.0.21" @@ -1584,23 +1586,3 @@ dependencies = [ "cc", "pkg-config", ] - -[[patch.unused]] -name = "buffa-build" -version = "0.3.0" -source = "git+https://github.com/anthropics/buffa?rev=b065d14dbd545a25ba30974384e505b4bc072771#b065d14dbd545a25ba30974384e505b4bc072771" - -[[patch.unused]] -name = "buffa-codegen" -version = "0.3.0" -source = "git+https://github.com/anthropics/buffa?rev=b065d14dbd545a25ba30974384e505b4bc072771#b065d14dbd545a25ba30974384e505b4bc072771" - -[[patch.unused]] -name = "buffa-descriptor" -version = "0.3.0" -source = "git+https://github.com/anthropics/buffa?rev=b065d14dbd545a25ba30974384e505b4bc072771#b065d14dbd545a25ba30974384e505b4bc072771" - -[[patch.unused]] -name = "buffa-types" -version = "0.3.0" -source = "git+https://github.com/anthropics/buffa?rev=b065d14dbd545a25ba30974384e505b4bc072771#b065d14dbd545a25ba30974384e505b4bc072771" diff --git a/Cargo.toml b/Cargo.toml index e091a37..dc456b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ members = [ ] [workspace.package] -edition = "2021" -rust-version = "1.83" +edition = "2024" +rust-version = "1.95" license = "Apache-2.0 OR MIT" authors = ["Mathematic Inc"] repository = "https://github.com/mathematic-inc/protovalidate-buffa" @@ -24,8 +24,8 @@ pedantic = { level = "deny", priority = -1 } nursery = { level = "deny", priority = -1 } [patch.crates-io] -buffa = { git = "https://github.com/anthropics/buffa", rev = "b065d14dbd545a25ba30974384e505b4bc072771" } -buffa-types = { git = "https://github.com/anthropics/buffa", rev = "b065d14dbd545a25ba30974384e505b4bc072771" } -buffa-codegen = { git = "https://github.com/anthropics/buffa", rev = "b065d14dbd545a25ba30974384e505b4bc072771" } -buffa-build = { git = "https://github.com/anthropics/buffa", rev = "b065d14dbd545a25ba30974384e505b4bc072771" } -buffa-descriptor = { git = "https://github.com/anthropics/buffa", rev = "b065d14dbd545a25ba30974384e505b4bc072771" } +buffa = { git = "https://github.com/anthropics/buffa", rev = "4c264ddd6fdd286f237dac0551fd420907b4864c" } +buffa-types = { git = "https://github.com/anthropics/buffa", rev = "4c264ddd6fdd286f237dac0551fd420907b4864c" } +buffa-codegen = { git = "https://github.com/anthropics/buffa", rev = "4c264ddd6fdd286f237dac0551fd420907b4864c" } +buffa-build = { git = "https://github.com/anthropics/buffa", rev = "4c264ddd6fdd286f237dac0551fd420907b4864c" } +buffa-descriptor = { git = "https://github.com/anthropics/buffa", rev = "4c264ddd6fdd286f237dac0551fd420907b4864c" } diff --git a/crates/protoc-gen-protovalidate-buffa/Cargo.toml b/crates/protoc-gen-protovalidate-buffa/Cargo.toml index 793098e..97db38a 100644 --- a/crates/protoc-gen-protovalidate-buffa/Cargo.toml +++ b/crates/protoc-gen-protovalidate-buffa/Cargo.toml @@ -22,8 +22,8 @@ name = "protoc-gen-protovalidate-buffa" path = "src/main.rs" [dependencies] -buffa = "0.4" -buffa-codegen = "0.4" +buffa = "0.5" +buffa-codegen = "0.5" protovalidate-buffa-protos = { path = "../protovalidate-buffa-protos", version = "0.1.2" } proc-macro2 = "1" quote = "1" diff --git a/crates/protoc-gen-protovalidate-buffa/src/emit/cel.rs b/crates/protoc-gen-protovalidate-buffa/src/emit/cel.rs index 44491d4..a1cfa89 100644 --- a/crates/protoc-gen-protovalidate-buffa/src/emit/cel.rs +++ b/crates/protoc-gen-protovalidate-buffa/src/emit/cel.rs @@ -43,10 +43,10 @@ pub fn emit_message_level(msg: &MessageValidators) -> (Vec, Vec (Vec, Vec quote! { - if let Some(ref v) = self.#field_ident { + if let Some(v) = &self.#field_ident { if let Err(viol) = #ident.#method( ::protovalidate_buffa::cel::to_cel_value(v), #fp, @@ -493,7 +493,7 @@ pub fn emit_as_cel_value(msg: &MessageValidators, rust_path: &Path) -> Result quote! { - if let Some(ref v) = self.#field_ident { + if let Some(v) = &self.#field_ident { map.insert( ::std::string::String::from(#field_name), ::protovalidate_buffa::cel::to_cel_value(v), @@ -574,8 +574,8 @@ fn field_to_cel_value_expr(f: &FieldValidator, field_ident: &syn::Ident) -> Toke FieldKind::Optional(_) => { // EXPLICIT-presence scalar: `Option`. Map None→Null, Some(v)→to_cel_value. quote! { - match self.#field_ident { - Some(ref v) => ::protovalidate_buffa::cel::to_cel_value(v), + match &self.#field_ident { + Some(v) => ::protovalidate_buffa::cel::to_cel_value(v), None => ::protovalidate_buffa::cel_core::Value::Null, } } diff --git a/crates/protoc-gen-protovalidate-buffa/src/emit/field.rs b/crates/protoc-gen-protovalidate-buffa/src/emit/field.rs index 689672d..ffdcc84 100644 --- a/crates/protoc-gen-protovalidate-buffa/src/emit/field.rs +++ b/crates/protoc-gen-protovalidate-buffa/src/emit/field.rs @@ -337,37 +337,38 @@ pub fn emit(field: &FieldValidator) -> Result { }); } // google.protobuf.Duration rules. - if full_name == "google.protobuf.Duration" { - if let Some(d) = &field.standard.duration { - blocks.extend(emit_duration_rules( - &accessor, - name_lit, - field.field_number, - d, - )); - } + if full_name == "google.protobuf.Duration" + && let Some(d) = &field.standard.duration + { + blocks.extend(emit_duration_rules( + &accessor, + name_lit, + field.field_number, + d, + )); } // google.protobuf.Timestamp rules. - if full_name == "google.protobuf.Timestamp" { - if let Some(t) = &field.standard.timestamp { - blocks.extend(emit_timestamp_rules( - &accessor, - name_lit, - field.field_number, - t, - )); - } + if full_name == "google.protobuf.Timestamp" + && let Some(t) = &field.standard.timestamp + { + blocks.extend(emit_timestamp_rules( + &accessor, + name_lit, + field.field_number, + t, + )); } // google.protobuf.FieldMask rules. - if full_name == "google.protobuf.FieldMask" { - if let Some(fm) = &field.standard.field_mask { - let fp_msg = field_path_scalar(name_lit, field.field_number, "Message"); - if let Some(expected) = &fm.r#const { - let fp_c = &fp_msg; - let rule = rule_path_scalar("field_mask", 28, "const", 1, "Message"); - let expected_lits = expected.iter().map(String::as_str); - let msg_str = format!("must equal paths [{}]", expected.join(", ")); - blocks.push(quote! { + if full_name == "google.protobuf.FieldMask" + && let Some(fm) = &field.standard.field_mask + { + let fp_msg = field_path_scalar(name_lit, field.field_number, "Message"); + if let Some(expected) = &fm.r#const { + let fp_c = &fp_msg; + let rule = rule_path_scalar("field_mask", 28, "const", 1, "Message"); + let expected_lits = expected.iter().map(String::as_str); + let msg_str = format!("must equal paths [{}]", expected.join(", ")); + blocks.push(quote! { if let Some(inner) = self.#accessor.as_option() { const EXPECTED: &[&str] = &[ #( #expected_lits ),* ]; let actual: ::std::vec::Vec<&str> = inner.paths.iter().map(|s| s.as_str()).collect(); @@ -383,12 +384,12 @@ pub fn emit(field: &FieldValidator) -> Result { } } }); - } - if !fm.in_set.is_empty() { - let fp_i = &fp_msg; - let rule = rule_path_scalar("field_mask", 28, "in", 2, "String"); - let allowed = fm.in_set.iter().map(String::as_str); - blocks.push(quote! { + } + if !fm.in_set.is_empty() { + let fp_i = &fp_msg; + let rule = rule_path_scalar("field_mask", 28, "in", 2, "String"); + let allowed = fm.in_set.iter().map(String::as_str); + blocks.push(quote! { if let Some(inner) = self.#accessor.as_option() { const ALLOWED: &[&str] = &[ #( #allowed ),* ]; let ok = inner.paths.iter().all(|p| { @@ -404,12 +405,12 @@ pub fn emit(field: &FieldValidator) -> Result { } } }); - } - if !fm.not_in.is_empty() { - let fp_n = &fp_msg; - let rule = rule_path_scalar("field_mask", 28, "not_in", 3, "String"); - let denied = fm.not_in.iter().map(String::as_str); - blocks.push(quote! { + } + if !fm.not_in.is_empty() { + let fp_n = &fp_msg; + let rule = rule_path_scalar("field_mask", 28, "not_in", 3, "String"); + let denied = fm.not_in.iter().map(String::as_str); + blocks.push(quote! { if let Some(inner) = self.#accessor.as_option() { const DENIED: &[&str] = &[ #( #denied ),* ]; let bad = inner.paths.iter().any(|p| { @@ -426,49 +427,47 @@ pub fn emit(field: &FieldValidator) -> Result { } } }); - } } } // google.protobuf.Any `type_url` checks. - if full_name == "google.protobuf.Any" { - if let Some(a) = &field.standard.any_rules { - let field_path = field_path_scalar(name_lit, field.field_number, "Message"); - if !a.in_set.is_empty() { - let set = &a.in_set; - let rule = rule_path_scalar("any", 20, "in", 2, "String"); - blocks.push(quote! { - if let Some(inner) = self.#accessor.as_option() { - const ALLOWED: &[&str] = &[ #( #set ),* ]; - if !ALLOWED.iter().any(|s| *s == inner.type_url.as_str()) { - violations.push(::protovalidate_buffa::Violation { - field: #field_path, rule: #rule, - rule_id: ::std::borrow::Cow::Borrowed("any.in"), - message: ::std::borrow::Cow::Borrowed(""), - for_key: false, - }); - } + if full_name == "google.protobuf.Any" + && let Some(a) = &field.standard.any_rules + { + let field_path = field_path_scalar(name_lit, field.field_number, "Message"); + if !a.in_set.is_empty() { + let set = &a.in_set; + let rule = rule_path_scalar("any", 20, "in", 2, "String"); + blocks.push(quote! { + if let Some(inner) = self.#accessor.as_option() { + const ALLOWED: &[&str] = &[ #( #set ),* ]; + if !ALLOWED.iter().any(|s| *s == inner.type_url.as_str()) { + violations.push(::protovalidate_buffa::Violation { + field: #field_path, rule: #rule, + rule_id: ::std::borrow::Cow::Borrowed("any.in"), + message: ::std::borrow::Cow::Borrowed(""), + for_key: false, + }); } - }); - } - if !a.not_in.is_empty() { - let set = &a.not_in; - let field_path2 = - field_path_scalar(name_lit, field.field_number, "Message"); - let rule = rule_path_scalar("any", 20, "not_in", 3, "String"); - blocks.push(quote! { - if let Some(inner) = self.#accessor.as_option() { - const DISALLOWED: &[&str] = &[ #( #set ),* ]; - if DISALLOWED.iter().any(|s| *s == inner.type_url.as_str()) { - violations.push(::protovalidate_buffa::Violation { - field: #field_path2, rule: #rule, - rule_id: ::std::borrow::Cow::Borrowed("any.not_in"), - message: ::std::borrow::Cow::Borrowed(""), - for_key: false, - }); - } + } + }); + } + if !a.not_in.is_empty() { + let set = &a.not_in; + let field_path2 = field_path_scalar(name_lit, field.field_number, "Message"); + let rule = rule_path_scalar("any", 20, "not_in", 3, "String"); + blocks.push(quote! { + if let Some(inner) = self.#accessor.as_option() { + const DISALLOWED: &[&str] = &[ #( #set ),* ]; + if DISALLOWED.iter().any(|s| *s == inner.type_url.as_str()) { + violations.push(::protovalidate_buffa::Violation { + field: #field_path2, rule: #rule, + rule_id: ::std::borrow::Cow::Borrowed("any.not_in"), + message: ::std::borrow::Cow::Borrowed(""), + for_key: false, + }); } - }); - } + } + }); } } let _ = full_name; @@ -953,21 +952,21 @@ fn emit_optional_inner( } } FieldKind::Bool => { - if let Some(b) = &field.standard.bool_rules { - if let Some(c) = b.r#const { - let fp = field_path_scalar(name_lit, field.field_number, "Bool"); - let rp = rule_path_scalar("bool", 13, "const", 1, "Bool"); - out.push(quote! { - if #v != #c { - violations.push(::protovalidate_buffa::Violation { - field: #fp, rule: #rp, - rule_id: ::std::borrow::Cow::Borrowed("bool.const"), - message: ::std::borrow::Cow::Borrowed(""), - for_key: false, - }); - } - }); - } + if let Some(b) = &field.standard.bool_rules + && let Some(c) = b.r#const + { + let fp = field_path_scalar(name_lit, field.field_number, "Bool"); + let rp = rule_path_scalar("bool", 13, "const", 1, "Bool"); + out.push(quote! { + if #v != #c { + violations.push(::protovalidate_buffa::Violation { + field: #fp, rule: #rp, + rule_id: ::std::borrow::Cow::Borrowed("bool.const"), + message: ::std::borrow::Cow::Borrowed(""), + for_key: false, + }); + } + }); } } FieldKind::Enum { .. } @@ -4108,19 +4107,19 @@ fn emit_wrapper_inner( } } FieldKind::Bool => { - if let Some(b) = &std.bool_rules { - if let Some(c) = b.r#const { - push_v( - &mut out, - "bool", - 13, - "const", - 1, - "Bool", - "bool.const", - quote! { #v != #c }, - ); - } + if let Some(b) = &std.bool_rules + && let Some(c) = b.r#const + { + push_v( + &mut out, + "bool", + 13, + "const", + 1, + "Bool", + "bool.const", + quote! { #v != #c }, + ); } } _ => {} diff --git a/crates/protoc-gen-protovalidate-buffa/src/emit/oneof.rs b/crates/protoc-gen-protovalidate-buffa/src/emit/oneof.rs index 3ac615d..fd1f70f 100644 --- a/crates/protoc-gen-protovalidate-buffa/src/emit/oneof.rs +++ b/crates/protoc-gen-protovalidate-buffa/src/emit/oneof.rs @@ -223,7 +223,7 @@ fn has_field_rules(f: &FieldValidator) -> bool { || matches!(f.field_type, FieldKind::Message { ref full_name } if !full_name.starts_with("google.protobuf.")) } -/// Emit a `Some(Variant(ref v)) => { ... }` match arm for a single oneof field. +/// Emit a `Some(Variant(v)) => { ... }` match arm for a single oneof field. fn emit_variant_arm(v: &OneofValidator, f: &FieldValidator) -> Result { if matches!(f.ignore, crate::scan::Ignore::Always) { return Ok(quote! {}); @@ -321,14 +321,14 @@ fn emit_variant_arm(v: &OneofValidator, f: &FieldValidator) -> Result { + Some(__buffa::oneof::#module_ident::#oneof_enum_ident::#variant_ident(__oneof_val)) => { let #val_ident = *__oneof_val; #( #checks )* } }) } else { Ok(quote! { - Some(__buffa::oneof::#module_ident::#oneof_enum_ident::#variant_ident(ref #val_ident)) => { + Some(__buffa::oneof::#module_ident::#oneof_enum_ident::#variant_ident(#val_ident)) => { #( #checks )* } }) diff --git a/crates/protoc-gen-protovalidate-buffa/src/emit/repeated.rs b/crates/protoc-gen-protovalidate-buffa/src/emit/repeated.rs index 2a58fd1..7b258dd 100644 --- a/crates/protoc-gen-protovalidate-buffa/src/emit/repeated.rs +++ b/crates/protoc-gen-protovalidate-buffa/src/emit/repeated.rs @@ -923,19 +923,19 @@ fn emit_repeated_items_checks( } } FieldKind::Bool => { - if let Some(b) = &rules.bool_rules { - if let Some(c) = b.r#const { - push( - &mut out, - "bool", - 13, - "const", - 1, - "Bool", - "bool.const".to_string(), - quote! { *#elem_ident != #c }, - ); - } + if let Some(b) = &rules.bool_rules + && let Some(c) = b.r#const + { + push( + &mut out, + "bool", + 13, + "const", + 1, + "Bool", + "bool.const".to_string(), + quote! { *#elem_ident != #c }, + ); } } FieldKind::String => { @@ -1358,279 +1358,278 @@ pub fn emit_repeated( } // Per-element WKT (Any, Duration) rules. - if let Some(items) = &spec.items { - if !matches!(items.ignore, crate::scan::Ignore::Always) { - if let FieldKind::Message { full_name } = element_kind { - if full_name == "google.protobuf.Duration" { - if let Some(d) = &items.standard.duration { - let fnum = field_number; - let nl = name_lit; - let emit_dur = |out: &mut Vec, - inner_name: &str, - inner_num: i32, - bound: &(i64, i32), - op: &str, - rule_id: &str, - msg: &str| { - let (secs, nano) = *bound; - let cond: TokenStream = match op { - "gte" => { - quote! { elem_ns < #secs as i128 * 1_000_000_000 + #nano as i128 } - } - "gt" => { - quote! { elem_ns <= #secs as i128 * 1_000_000_000 + #nano as i128 } - } - "lte" => { - quote! { elem_ns > #secs as i128 * 1_000_000_000 + #nano as i128 } - } - "lt" => { - quote! { elem_ns >= #secs as i128 * 1_000_000_000 + #nano as i128 } - } - _ => return, - }; - let inner_name_s = inner_name.to_string(); - let rid = rule_id.to_string(); - let ms = msg.to_string(); - out.push(quote! { - for (idx, elem) in self.#accessor.iter().enumerate() { - let elem_ns: i128 = elem.seconds as i128 * 1_000_000_000 + elem.nanos as i128; - if #cond { - violations.push(::protovalidate_buffa::Violation { - field: ::protovalidate_buffa::FieldPath { - elements: ::std::vec![::protovalidate_buffa::FieldPathElement { - field_number: Some(#fnum), - field_name: Some(::std::borrow::Cow::Borrowed(#nl)), - field_type: Some(::protovalidate_buffa::FieldType::Message), - key_type: None, value_type: None, - subscript: Some(::protovalidate_buffa::Subscript::Index(idx as u64)), - }], - }, - rule: ::protovalidate_buffa::FieldPath { - elements: ::std::vec![ - ::protovalidate_buffa::FieldPathElement { field_number: Some(18i32), field_name: Some(::std::borrow::Cow::Borrowed("repeated")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(4i32), field_name: Some(::std::borrow::Cow::Borrowed("items")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(21i32), field_name: Some(::std::borrow::Cow::Borrowed("duration")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(#inner_num), field_name: Some(::std::borrow::Cow::Borrowed(#inner_name_s)), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ], - }, - rule_id: ::std::borrow::Cow::Borrowed(#rid), - message: ::std::borrow::Cow::Borrowed(#ms), - for_key: false, - }); - } - } + if let Some(items) = &spec.items + && !matches!(items.ignore, crate::scan::Ignore::Always) + && let FieldKind::Message { full_name } = element_kind + { + if full_name == "google.protobuf.Duration" + && let Some(d) = &items.standard.duration + { + let fnum = field_number; + let nl = name_lit; + let emit_dur = |out: &mut Vec, + inner_name: &str, + inner_num: i32, + bound: &(i64, i32), + op: &str, + rule_id: &str, + msg: &str| { + let (secs, nano) = *bound; + let cond: TokenStream = match op { + "gte" => { + quote! { elem_ns < #secs as i128 * 1_000_000_000 + #nano as i128 } + } + "gt" => { + quote! { elem_ns <= #secs as i128 * 1_000_000_000 + #nano as i128 } + } + "lte" => { + quote! { elem_ns > #secs as i128 * 1_000_000_000 + #nano as i128 } + } + "lt" => { + quote! { elem_ns >= #secs as i128 * 1_000_000_000 + #nano as i128 } + } + _ => return, + }; + let inner_name_s = inner_name.to_string(); + let rid = rule_id.to_string(); + let ms = msg.to_string(); + out.push(quote! { + for (idx, elem) in self.#accessor.iter().enumerate() { + let elem_ns: i128 = elem.seconds as i128 * 1_000_000_000 + elem.nanos as i128; + if #cond { + violations.push(::protovalidate_buffa::Violation { + field: ::protovalidate_buffa::FieldPath { + elements: ::std::vec![::protovalidate_buffa::FieldPathElement { + field_number: Some(#fnum), + field_name: Some(::std::borrow::Cow::Borrowed(#nl)), + field_type: Some(::protovalidate_buffa::FieldType::Message), + key_type: None, value_type: None, + subscript: Some(::protovalidate_buffa::Subscript::Index(idx as u64)), + }], + }, + rule: ::protovalidate_buffa::FieldPath { + elements: ::std::vec![ + ::protovalidate_buffa::FieldPathElement { field_number: Some(18i32), field_name: Some(::std::borrow::Cow::Borrowed("repeated")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(4i32), field_name: Some(::std::borrow::Cow::Borrowed("items")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(21i32), field_name: Some(::std::borrow::Cow::Borrowed("duration")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(#inner_num), field_name: Some(::std::borrow::Cow::Borrowed(#inner_name_s)), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ], + }, + rule_id: ::std::borrow::Cow::Borrowed(#rid), + message: ::std::borrow::Cow::Borrowed(#ms), + for_key: false, }); - }; - if let Some(b) = &d.gte { - emit_dur( - &mut out, - "gte", - 6, - b, - "gte", - "duration.gte", - "must be greater than or equal to 0.001s", - ); - } - if let Some(b) = &d.gt { - emit_dur(&mut out, "gt", 5, b, "gt", "duration.gt", ""); - } - if let Some(b) = &d.lte { - emit_dur(&mut out, "lte", 4, b, "lte", "duration.lte", ""); - } - if let Some(b) = &d.lt { - emit_dur(&mut out, "lt", 3, b, "lt", "duration.lt", ""); } } - } - if full_name == "google.protobuf.Timestamp" { - if let Some(t) = &items.standard.timestamp { - let fnum = field_number; - let nl = name_lit; - let emit_ts = |out: &mut Vec, - inner_name: &str, - inner_num: i32, - bound: &(i64, i32), - op: &str, - rule_id: &str| { - let (secs, nano) = *bound; - let cond: TokenStream = match op { - "gte" => { - quote! { elem_ns < #secs as i128 * 1_000_000_000 + #nano as i128 } - } - "gt" => { - quote! { elem_ns <= #secs as i128 * 1_000_000_000 + #nano as i128 } - } - "lte" => { - quote! { elem_ns > #secs as i128 * 1_000_000_000 + #nano as i128 } - } - "lt" => { - quote! { elem_ns >= #secs as i128 * 1_000_000_000 + #nano as i128 } - } - _ => return, - }; - let inner_name_s = inner_name.to_string(); - let rid = rule_id.to_string(); - out.push(quote! { - for (idx, elem) in self.#accessor.iter().enumerate() { - let elem_ns: i128 = elem.seconds as i128 * 1_000_000_000 + elem.nanos as i128; - if #cond { - violations.push(::protovalidate_buffa::Violation { - field: ::protovalidate_buffa::FieldPath { - elements: ::std::vec![::protovalidate_buffa::FieldPathElement { - field_number: Some(#fnum), - field_name: Some(::std::borrow::Cow::Borrowed(#nl)), - field_type: Some(::protovalidate_buffa::FieldType::Message), - key_type: None, value_type: None, - subscript: Some(::protovalidate_buffa::Subscript::Index(idx as u64)), - }], - }, - rule: ::protovalidate_buffa::FieldPath { - elements: ::std::vec![ - ::protovalidate_buffa::FieldPathElement { field_number: Some(18i32), field_name: Some(::std::borrow::Cow::Borrowed("repeated")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(4i32), field_name: Some(::std::borrow::Cow::Borrowed("items")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(22i32), field_name: Some(::std::borrow::Cow::Borrowed("timestamp")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(#inner_num), field_name: Some(::std::borrow::Cow::Borrowed(#inner_name_s)), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ], - }, - rule_id: ::std::borrow::Cow::Borrowed(#rid), - message: ::std::borrow::Cow::Borrowed(""), - for_key: false, - }); - } - } + }); + }; + if let Some(b) = &d.gte { + emit_dur( + &mut out, + "gte", + 6, + b, + "gte", + "duration.gte", + "must be greater than or equal to 0.001s", + ); + } + if let Some(b) = &d.gt { + emit_dur(&mut out, "gt", 5, b, "gt", "duration.gt", ""); + } + if let Some(b) = &d.lte { + emit_dur(&mut out, "lte", 4, b, "lte", "duration.lte", ""); + } + if let Some(b) = &d.lt { + emit_dur(&mut out, "lt", 3, b, "lt", "duration.lt", ""); + } + } + if full_name == "google.protobuf.Timestamp" + && let Some(t) = &items.standard.timestamp + { + let fnum = field_number; + let nl = name_lit; + let emit_ts = |out: &mut Vec, + inner_name: &str, + inner_num: i32, + bound: &(i64, i32), + op: &str, + rule_id: &str| { + let (secs, nano) = *bound; + let cond: TokenStream = match op { + "gte" => { + quote! { elem_ns < #secs as i128 * 1_000_000_000 + #nano as i128 } + } + "gt" => { + quote! { elem_ns <= #secs as i128 * 1_000_000_000 + #nano as i128 } + } + "lte" => { + quote! { elem_ns > #secs as i128 * 1_000_000_000 + #nano as i128 } + } + "lt" => { + quote! { elem_ns >= #secs as i128 * 1_000_000_000 + #nano as i128 } + } + _ => return, + }; + let inner_name_s = inner_name.to_string(); + let rid = rule_id.to_string(); + out.push(quote! { + for (idx, elem) in self.#accessor.iter().enumerate() { + let elem_ns: i128 = elem.seconds as i128 * 1_000_000_000 + elem.nanos as i128; + if #cond { + violations.push(::protovalidate_buffa::Violation { + field: ::protovalidate_buffa::FieldPath { + elements: ::std::vec![::protovalidate_buffa::FieldPathElement { + field_number: Some(#fnum), + field_name: Some(::std::borrow::Cow::Borrowed(#nl)), + field_type: Some(::protovalidate_buffa::FieldType::Message), + key_type: None, value_type: None, + subscript: Some(::protovalidate_buffa::Subscript::Index(idx as u64)), + }], + }, + rule: ::protovalidate_buffa::FieldPath { + elements: ::std::vec![ + ::protovalidate_buffa::FieldPathElement { field_number: Some(18i32), field_name: Some(::std::borrow::Cow::Borrowed("repeated")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(4i32), field_name: Some(::std::borrow::Cow::Borrowed("items")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(22i32), field_name: Some(::std::borrow::Cow::Borrowed("timestamp")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(#inner_num), field_name: Some(::std::borrow::Cow::Borrowed(#inner_name_s)), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ], + }, + rule_id: ::std::borrow::Cow::Borrowed(#rid), + message: ::std::borrow::Cow::Borrowed(""), + for_key: false, }); - }; - if let Some(b) = &t.gte { - emit_ts(&mut out, "gte", 6, b, "gte", "timestamp.gte"); - } - if let Some(b) = &t.gt { - emit_ts(&mut out, "gt", 5, b, "gt", "timestamp.gt"); - } - if let Some(b) = &t.lte { - emit_ts(&mut out, "lte", 4, b, "lte", "timestamp.lte"); - } - if let Some(b) = &t.lt { - emit_ts(&mut out, "lt", 3, b, "lt", "timestamp.lt"); } } - } - if full_name == "google.protobuf.Any" { - if let Some(a) = &items.standard.any_rules { - if !a.in_set.is_empty() { - let set = &a.in_set; - let fnum = field_number; - let nl = name_lit; - out.push(quote! { - for (idx, elem) in self.#accessor.iter().enumerate() { - const ALLOWED: &[&str] = &[ #( #set ),* ]; - if !ALLOWED.iter().any(|s| *s == elem.type_url.as_str()) { - violations.push(::protovalidate_buffa::Violation { - field: ::protovalidate_buffa::FieldPath { - elements: ::std::vec![::protovalidate_buffa::FieldPathElement { - field_number: Some(#fnum), - field_name: Some(::std::borrow::Cow::Borrowed(#nl)), - field_type: Some(::protovalidate_buffa::FieldType::Message), - key_type: None, value_type: None, - subscript: Some(::protovalidate_buffa::Subscript::Index(idx as u64)), - }], - }, - rule: ::protovalidate_buffa::FieldPath { - elements: ::std::vec![ - ::protovalidate_buffa::FieldPathElement { field_number: Some(18i32), field_name: Some(::std::borrow::Cow::Borrowed("repeated")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(4i32), field_name: Some(::std::borrow::Cow::Borrowed("items")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(20i32), field_name: Some(::std::borrow::Cow::Borrowed("any")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(2i32), field_name: Some(::std::borrow::Cow::Borrowed("in")), field_type: Some(::protovalidate_buffa::FieldType::String), key_type: None, value_type: None, subscript: None }, - ], - }, - rule_id: ::std::borrow::Cow::Borrowed("any.in"), - message: ::std::borrow::Cow::Borrowed("type URL must be in the allow list"), - for_key: false, - }); - } - } + }); + }; + if let Some(b) = &t.gte { + emit_ts(&mut out, "gte", 6, b, "gte", "timestamp.gte"); + } + if let Some(b) = &t.gt { + emit_ts(&mut out, "gt", 5, b, "gt", "timestamp.gt"); + } + if let Some(b) = &t.lte { + emit_ts(&mut out, "lte", 4, b, "lte", "timestamp.lte"); + } + if let Some(b) = &t.lt { + emit_ts(&mut out, "lt", 3, b, "lt", "timestamp.lt"); + } + } + if full_name == "google.protobuf.Any" + && let Some(a) = &items.standard.any_rules + { + if !a.in_set.is_empty() { + let set = &a.in_set; + let fnum = field_number; + let nl = name_lit; + out.push(quote! { + for (idx, elem) in self.#accessor.iter().enumerate() { + const ALLOWED: &[&str] = &[ #( #set ),* ]; + if !ALLOWED.iter().any(|s| *s == elem.type_url.as_str()) { + violations.push(::protovalidate_buffa::Violation { + field: ::protovalidate_buffa::FieldPath { + elements: ::std::vec![::protovalidate_buffa::FieldPathElement { + field_number: Some(#fnum), + field_name: Some(::std::borrow::Cow::Borrowed(#nl)), + field_type: Some(::protovalidate_buffa::FieldType::Message), + key_type: None, value_type: None, + subscript: Some(::protovalidate_buffa::Subscript::Index(idx as u64)), + }], + }, + rule: ::protovalidate_buffa::FieldPath { + elements: ::std::vec![ + ::protovalidate_buffa::FieldPathElement { field_number: Some(18i32), field_name: Some(::std::borrow::Cow::Borrowed("repeated")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(4i32), field_name: Some(::std::borrow::Cow::Borrowed("items")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(20i32), field_name: Some(::std::borrow::Cow::Borrowed("any")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(2i32), field_name: Some(::std::borrow::Cow::Borrowed("in")), field_type: Some(::protovalidate_buffa::FieldType::String), key_type: None, value_type: None, subscript: None }, + ], + }, + rule_id: ::std::borrow::Cow::Borrowed("any.in"), + message: ::std::borrow::Cow::Borrowed("type URL must be in the allow list"), + for_key: false, }); } - if !a.not_in.is_empty() { - let set = &a.not_in; - let fnum = field_number; - let nl = name_lit; - out.push(quote! { - for (idx, elem) in self.#accessor.iter().enumerate() { - const DENIED: &[&str] = &[ #( #set ),* ]; - if DENIED.iter().any(|s| *s == elem.type_url.as_str()) { - violations.push(::protovalidate_buffa::Violation { - field: ::protovalidate_buffa::FieldPath { - elements: ::std::vec![::protovalidate_buffa::FieldPathElement { - field_number: Some(#fnum), - field_name: Some(::std::borrow::Cow::Borrowed(#nl)), - field_type: Some(::protovalidate_buffa::FieldType::Message), - key_type: None, value_type: None, - subscript: Some(::protovalidate_buffa::Subscript::Index(idx as u64)), - }], - }, - rule: ::protovalidate_buffa::FieldPath { - elements: ::std::vec![ - ::protovalidate_buffa::FieldPathElement { field_number: Some(18i32), field_name: Some(::std::borrow::Cow::Borrowed("repeated")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(4i32), field_name: Some(::std::borrow::Cow::Borrowed("items")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(20i32), field_name: Some(::std::borrow::Cow::Borrowed("any")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, - ::protovalidate_buffa::FieldPathElement { field_number: Some(3i32), field_name: Some(::std::borrow::Cow::Borrowed("not_in")), field_type: Some(::protovalidate_buffa::FieldType::String), key_type: None, value_type: None, subscript: None }, - ], - }, - rule_id: ::std::borrow::Cow::Borrowed("any.not_in"), - message: ::std::borrow::Cow::Borrowed("type URL must not be in the block list"), - for_key: false, - }); - } - } + } + }); + } + if !a.not_in.is_empty() { + let set = &a.not_in; + let fnum = field_number; + let nl = name_lit; + out.push(quote! { + for (idx, elem) in self.#accessor.iter().enumerate() { + const DENIED: &[&str] = &[ #( #set ),* ]; + if DENIED.iter().any(|s| *s == elem.type_url.as_str()) { + violations.push(::protovalidate_buffa::Violation { + field: ::protovalidate_buffa::FieldPath { + elements: ::std::vec![::protovalidate_buffa::FieldPathElement { + field_number: Some(#fnum), + field_name: Some(::std::borrow::Cow::Borrowed(#nl)), + field_type: Some(::protovalidate_buffa::FieldType::Message), + key_type: None, value_type: None, + subscript: Some(::protovalidate_buffa::Subscript::Index(idx as u64)), + }], + }, + rule: ::protovalidate_buffa::FieldPath { + elements: ::std::vec![ + ::protovalidate_buffa::FieldPathElement { field_number: Some(18i32), field_name: Some(::std::borrow::Cow::Borrowed("repeated")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(4i32), field_name: Some(::std::borrow::Cow::Borrowed("items")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(20i32), field_name: Some(::std::borrow::Cow::Borrowed("any")), field_type: Some(::protovalidate_buffa::FieldType::Message), key_type: None, value_type: None, subscript: None }, + ::protovalidate_buffa::FieldPathElement { field_number: Some(3i32), field_name: Some(::std::borrow::Cow::Borrowed("not_in")), field_type: Some(::protovalidate_buffa::FieldType::String), key_type: None, value_type: None, subscript: None }, + ], + }, + rule_id: ::std::borrow::Cow::Borrowed("any.not_in"), + message: ::std::borrow::Cow::Borrowed("type URL must not be in the block list"), + for_key: false, }); } } - } + }); } } } // Per-element predefined (extension-based) rules (`repeated.items..`). - if let Some(items) = &spec.items { - if !matches!(items.ignore, crate::scan::Ignore::Always) - && !items.standard.predefined.is_empty() - { - let element_ty_ident = format_ident!("{}", element_type_variant); - let family = crate::emit::cel::predef_family_for(element_kind, &items.standard); - for (pi, rule) in items.standard.predefined.iter().enumerate() { - let id = rule.id.as_str(); - let msg = rule.message.as_str(); - let expr = rule.expression.as_str(); - let ext_num = rule.ext_number; - let ext_name = &rule.ext_name; - let ext_ty_ident = format_ident!("{}", rule.ext_field_type); - let rule_value: TokenStream = syn::parse_str(&rule.rule_value_expr) - .unwrap_or_else(|_| quote! { ::protovalidate_buffa::cel_core::Value::Null }); - let Some(fam) = family else { continue }; - let fam_name = fam.name; - let fam_num = fam.number; - let ext_bracketed = format!("[buf.validate.conformance.cases.{ext_name}]"); - let static_ident = format_ident!( - "__ITEMS_PRED_{}_{}", - pi, - id.replace(|c: char| !c.is_ascii_alphanumeric(), "_") - .to_uppercase() - ); - let as_value: TokenStream = match element_kind { - FieldKind::Message { full_name } - if full_name.starts_with("google.protobuf.") - && full_name.ends_with("Value") => - { - // Wrapper (FloatValue, Int32Value, etc.) — use inner .value. - quote! { ::protovalidate_buffa::cel::to_cel_value(&elem.value) } - } - FieldKind::Message { .. } => { - quote! { ::protovalidate_buffa::cel::AsCelValue::as_cel_value(elem) } - } - _ => quote! { ::protovalidate_buffa::cel::to_cel_value(elem) }, - }; - out.push(quote! { + if let Some(items) = &spec.items + && !matches!(items.ignore, crate::scan::Ignore::Always) + && !items.standard.predefined.is_empty() + { + let element_ty_ident = format_ident!("{}", element_type_variant); + let family = crate::emit::cel::predef_family_for(element_kind, &items.standard); + for (pi, rule) in items.standard.predefined.iter().enumerate() { + let id = rule.id.as_str(); + let msg = rule.message.as_str(); + let expr = rule.expression.as_str(); + let ext_num = rule.ext_number; + let ext_name = &rule.ext_name; + let ext_ty_ident = format_ident!("{}", rule.ext_field_type); + let rule_value: TokenStream = syn::parse_str(&rule.rule_value_expr) + .unwrap_or_else(|_| quote! { ::protovalidate_buffa::cel_core::Value::Null }); + let Some(fam) = family else { continue }; + let fam_name = fam.name; + let fam_num = fam.number; + let ext_bracketed = format!("[buf.validate.conformance.cases.{ext_name}]"); + let static_ident = format_ident!( + "__ITEMS_PRED_{}_{}", + pi, + id.replace(|c: char| !c.is_ascii_alphanumeric(), "_") + .to_uppercase() + ); + let as_value: TokenStream = match element_kind { + FieldKind::Message { full_name } + if full_name.starts_with("google.protobuf.") + && full_name.ends_with("Value") => + { + // Wrapper (FloatValue, Int32Value, etc.) — use inner .value. + quote! { ::protovalidate_buffa::cel::to_cel_value(&elem.value) } + } + FieldKind::Message { .. } => { + quote! { ::protovalidate_buffa::cel::AsCelValue::as_cel_value(elem) } + } + _ => quote! { ::protovalidate_buffa::cel::to_cel_value(elem) }, + }; + out.push(quote! { { static #static_ident: ::protovalidate_buffa::cel::CelConstraint = ::protovalidate_buffa::cel::CelConstraint::new(#id, #msg, #expr); @@ -1658,25 +1657,26 @@ pub fn emit_repeated( } } }); - } } } // Per-element CEL rules (`repeated.items.cel`). - if let Some(items) = &spec.items { - if !matches!(items.ignore, crate::scan::Ignore::Always) && !items.cel.is_empty() { - let element_ty_ident = format_ident!("{}", element_type_variant); - for (idx, rule) in items.cel.iter().enumerate() { - let id = rule.id.as_str(); - let msg = rule.message.as_str(); - let expr = rule.expression.as_str(); - let idx_lit = idx as u64; - let as_value: TokenStream = if matches!(element_kind, FieldKind::Message { .. }) { - quote! { ::protovalidate_buffa::cel::AsCelValue::as_cel_value(elem) } - } else { - quote! { ::protovalidate_buffa::cel::to_cel_value(elem) } - }; - out.push(quote! { + if let Some(items) = &spec.items + && !matches!(items.ignore, crate::scan::Ignore::Always) + && !items.cel.is_empty() + { + let element_ty_ident = format_ident!("{}", element_type_variant); + for (idx, rule) in items.cel.iter().enumerate() { + let id = rule.id.as_str(); + let msg = rule.message.as_str(); + let expr = rule.expression.as_str(); + let idx_lit = idx as u64; + let as_value: TokenStream = if matches!(element_kind, FieldKind::Message { .. }) { + quote! { ::protovalidate_buffa::cel::AsCelValue::as_cel_value(elem) } + } else { + quote! { ::protovalidate_buffa::cel::to_cel_value(elem) } + }; + out.push(quote! { { static __ITEMS_CEL: ::protovalidate_buffa::cel::CelConstraint = ::protovalidate_buffa::cel::CelConstraint::new(#id, #msg, #expr); @@ -1697,34 +1697,33 @@ pub fn emit_repeated( } } }); - } } } // Per-element message recursion. Skip google.protobuf.* (WKTs we don't // validate) and any cross-package messages where we can't guarantee an // impl Validate exists. - if let FieldKind::Message { full_name } = element_kind { - if !full_name.starts_with("google.protobuf.") { - let fnum = field_number; - out.push(quote! { - for (idx, elem) in self.#accessor.iter().enumerate() { - if let Err(sub) = elem.validate() { - violations.extend(sub.violations.into_iter().map(|mut v| { - v.field.elements.insert(0, ::protovalidate_buffa::FieldPathElement { - field_number: Some(#fnum), - field_name: Some(::std::borrow::Cow::Borrowed(#name_lit)), - field_type: Some(::protovalidate_buffa::FieldType::Message), - key_type: None, - value_type: None, - subscript: Some(::protovalidate_buffa::Subscript::Index(idx as u64)), - }); - v - })); - } + if let FieldKind::Message { full_name } = element_kind + && !full_name.starts_with("google.protobuf.") + { + let fnum = field_number; + out.push(quote! { + for (idx, elem) in self.#accessor.iter().enumerate() { + if let Err(sub) = elem.validate() { + violations.extend(sub.violations.into_iter().map(|mut v| { + v.field.elements.insert(0, ::protovalidate_buffa::FieldPathElement { + field_number: Some(#fnum), + field_name: Some(::std::borrow::Cow::Borrowed(#name_lit)), + field_type: Some(::protovalidate_buffa::FieldType::Message), + key_type: None, + value_type: None, + subscript: Some(::protovalidate_buffa::Subscript::Index(idx as u64)), + }); + v + })); } - }); - } + } + }); } Ok(quote! { #( #out )* }) @@ -1966,43 +1965,45 @@ pub fn emit_map( }); } }; - if let Some(k) = spec.keys.as_deref() { - if !matches!(k.ignore, crate::scan::Ignore::Always) && !k.cel.is_empty() { - emit_map_cel(&mut out, &k.cel, true); - } + if let Some(k) = spec.keys.as_deref() + && !matches!(k.ignore, crate::scan::Ignore::Always) + && !k.cel.is_empty() + { + emit_map_cel(&mut out, &k.cel, true); } - if let Some(v) = spec.values.as_deref() { - if !matches!(v.ignore, crate::scan::Ignore::Always) && !v.cel.is_empty() { - emit_map_cel(&mut out, &v.cel, false); - } + if let Some(v) = spec.values.as_deref() + && !matches!(v.ignore, crate::scan::Ignore::Always) + && !v.cel.is_empty() + { + emit_map_cel(&mut out, &v.cel, false); } // Recurse into message-typed map values so nested validators fire. - if let FieldKind::Message { full_name } = value_kind { - if !full_name.starts_with("google.protobuf.") { - let key_subscript_opt = - kind_variant_to_subscript(crate::emit::field::kind_to_field_type(key_kind)); - if let Some(key_subscript) = key_subscript_opt { - let kt = format_ident!("{}", crate::emit::field::kind_to_field_type(key_kind)); - let vt = format_ident!("{}", crate::emit::field::kind_to_field_type(value_kind)); - out.push(quote! { - for (key, value) in self.#accessor.iter() { - if let Err(sub) = value.validate() { - violations.extend(sub.violations.into_iter().map(|mut v| { - v.field.elements.insert(0, ::protovalidate_buffa::FieldPathElement { - field_number: Some(#field_number), - field_name: Some(::std::borrow::Cow::Borrowed(#name_lit)), - field_type: Some(::protovalidate_buffa::FieldType::Message), - key_type: Some(::protovalidate_buffa::FieldType::#kt), - value_type: Some(::protovalidate_buffa::FieldType::#vt), - subscript: Some(#key_subscript), - }); - v - })); - } + if let FieldKind::Message { full_name } = value_kind + && !full_name.starts_with("google.protobuf.") + { + let key_subscript_opt = + kind_variant_to_subscript(crate::emit::field::kind_to_field_type(key_kind)); + if let Some(key_subscript) = key_subscript_opt { + let kt = format_ident!("{}", crate::emit::field::kind_to_field_type(key_kind)); + let vt = format_ident!("{}", crate::emit::field::kind_to_field_type(value_kind)); + out.push(quote! { + for (key, value) in self.#accessor.iter() { + if let Err(sub) = value.validate() { + violations.extend(sub.violations.into_iter().map(|mut v| { + v.field.elements.insert(0, ::protovalidate_buffa::FieldPathElement { + field_number: Some(#field_number), + field_name: Some(::std::borrow::Cow::Borrowed(#name_lit)), + field_type: Some(::protovalidate_buffa::FieldType::Message), + key_type: Some(::protovalidate_buffa::FieldType::#kt), + value_type: Some(::protovalidate_buffa::FieldType::#vt), + subscript: Some(#key_subscript), + }); + v + })); } - }); - } + } + }); } } diff --git a/crates/protoc-gen-protovalidate-buffa/src/scan.rs b/crates/protoc-gen-protovalidate-buffa/src/scan.rs index 836d0a3..0cd2985 100644 --- a/crates/protoc-gen-protovalidate-buffa/src/scan.rs +++ b/crates/protoc-gen-protovalidate-buffa/src/scan.rs @@ -8,8 +8,8 @@ use buffa::ExtensionSet; use buffa_codegen::generated::{ compiler::CodeGeneratorRequest, descriptor::{ - feature_set, field_descriptor_proto, DescriptorProto, FieldDescriptorProto, - FileDescriptorProto, OneofDescriptorProto, + DescriptorProto, FieldDescriptorProto, FileDescriptorProto, OneofDescriptorProto, + feature_set, field_descriptor_proto, }, }; use protovalidate_buffa_protos::buf::validate::{ @@ -677,7 +677,9 @@ fn scan_predefined_on( UnknownFieldData::LengthDelimited(data), ) => { let s = String::from_utf8_lossy(data).to_string(); - format!("::protovalidate_buffa::cel_core::Value::String(::std::sync::Arc::new({s:?}.to_string()))") + format!( + "::protovalidate_buffa::cel_core::Value::String(::std::sync::Arc::new({s:?}.to_string()))" + ) } _ => continue, }; @@ -1102,7 +1104,9 @@ fn decode_wrapper_value(ty: field_descriptor_proto::Type, bytes: &[u8]) -> Optio return None; } let s = String::from_utf8_lossy(&r[..len]).to_string(); - Some(format!("::protovalidate_buffa::cel_core::Value::String(::std::sync::Arc::new({s:?}.to_string()))")) + Some(format!( + "::protovalidate_buffa::cel_core::Value::String(::std::sync::Arc::new({s:?}.to_string()))" + )) } Type::TYPE_BYTES => { let (len, r) = varint_decode(buf)?; @@ -1111,7 +1115,9 @@ fn decode_wrapper_value(ty: field_descriptor_proto::Type, bytes: &[u8]) -> Optio return None; } let bytes: Vec = r[..len].to_vec(); - Some(format!("::protovalidate_buffa::cel_core::Value::Bytes(::std::sync::Arc::new(vec!{bytes:?}))")) + Some(format!( + "::protovalidate_buffa::cel_core::Value::Bytes(::std::sync::Arc::new(vec!{bytes:?}))" + )) } _ => None, }; @@ -1591,31 +1597,31 @@ fn classify_field( if label == Label::LABEL_REPEATED { // Map detection: a `map` compiles to a `repeated MessageType` where // the referenced MessageType has `option map_entry = true`. - if proto_type == Type::TYPE_MESSAGE { - if let Some(inner) = find_map_entry(file, msg, type_name) { - // inner is the synthetic MapEntry DescriptorProto. - // field[0] = key, field[1] = value. - let key_field = inner - .field - .first() - .ok_or_else(|| anyhow!("map entry has no key field"))?; - let val_field = inner - .field - .get(1) - .ok_or_else(|| anyhow!("map entry has no value field"))?; - let key_kind = scalar_kind( - key_field.r#type.unwrap_or(Type::TYPE_STRING), - key_field.type_name.as_deref().unwrap_or(""), - ); - let val_kind = scalar_kind( - val_field.r#type.unwrap_or(Type::TYPE_STRING), - val_field.type_name.as_deref().unwrap_or(""), - ); - return Ok(FieldKind::Map { - key: Box::new(key_kind), - value: Box::new(val_kind), - }); - } + if proto_type == Type::TYPE_MESSAGE + && let Some(inner) = find_map_entry(file, msg, type_name) + { + // inner is the synthetic MapEntry DescriptorProto. + // field[0] = key, field[1] = value. + let key_field = inner + .field + .first() + .ok_or_else(|| anyhow!("map entry has no key field"))?; + let val_field = inner + .field + .get(1) + .ok_or_else(|| anyhow!("map entry has no value field"))?; + let key_kind = scalar_kind( + key_field.r#type.unwrap_or(Type::TYPE_STRING), + key_field.type_name.as_deref().unwrap_or(""), + ); + let val_kind = scalar_kind( + val_field.r#type.unwrap_or(Type::TYPE_STRING), + val_field.type_name.as_deref().unwrap_or(""), + ); + return Ok(FieldKind::Map { + key: Box::new(key_kind), + value: Box::new(val_kind), + }); } // Regular repeated. let item_kind = scalar_kind(proto_type, type_name); diff --git a/crates/protovalidate-buffa-conformance/Cargo.toml b/crates/protovalidate-buffa-conformance/Cargo.toml index 64806ec..26ebb23 100644 --- a/crates/protovalidate-buffa-conformance/Cargo.toml +++ b/crates/protovalidate-buffa-conformance/Cargo.toml @@ -20,18 +20,18 @@ name = "protovalidate-buffa-conformance" path = "src/main.rs" [dependencies] -buffa = "0.4" -buffa-types = "0.4" +buffa = "0.5" +buffa-types = "0.5" protovalidate-buffa = { path = "../protovalidate-buffa", version = "0.1.0", default-features = false } protovalidate-buffa-protos = { path = "../protovalidate-buffa-protos", version = "0.1.2" } anyhow = "1" regex = "1" [build-dependencies] -buffa-build = "0.4" +buffa-build = "0.5" protoc-gen-protovalidate-buffa = { path = "../protoc-gen-protovalidate-buffa", version = "0.1.2" } -buffa = "0.4" -buffa-codegen = "0.4" +buffa = "0.5" +buffa-codegen = "0.5" anyhow = "1" prettyplease = "0.2" syn = { version = "2", features = ["full"] } diff --git a/crates/protovalidate-buffa-macros/Cargo.toml b/crates/protovalidate-buffa-macros/Cargo.toml index cc02568..6bda1b0 100644 --- a/crates/protovalidate-buffa-macros/Cargo.toml +++ b/crates/protovalidate-buffa-macros/Cargo.toml @@ -22,4 +22,4 @@ proc-macro2 = "1" quote = "1" syn = { version = "2", features = ["full"] } -connectrpc = "0.3" +connectrpc = "0.4" diff --git a/crates/protovalidate-buffa-macros/src/lib.rs b/crates/protovalidate-buffa-macros/src/lib.rs index 82af2ed..54cc274 100644 --- a/crates/protovalidate-buffa-macros/src/lib.rs +++ b/crates/protovalidate-buffa-macros/src/lib.rs @@ -9,7 +9,7 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::{parse_macro_input, Error, FnArg, ImplItem, ItemImpl, PatType, Type, TypePath}; +use syn::{Error, FnArg, ImplItem, ItemImpl, PatType, Type, TypePath, parse_macro_input}; #[proc_macro_attribute] pub fn connect_impl(attr: TokenStream, input: TokenStream) -> TokenStream { @@ -25,22 +25,22 @@ pub fn connect_impl(attr: TokenStream, input: TokenStream) -> TokenStream { let mut item = parse_macro_input!(input as ItemImpl); for impl_item in &mut item.items { - if let ImplItem::Fn(f) = impl_item { - if let Some(arg_ident) = find_owned_view_arg(&f.sig) { - let pv_ident = - proc_macro2::Ident::new("__protovalidate_buffa_req_owned", arg_ident.span()); + if let ImplItem::Fn(f) = impl_item + && let Some(arg_ident) = find_owned_view_arg(&f.sig) + { + let pv_ident = + proc_macro2::Ident::new("__protovalidate_buffa_req_owned", arg_ident.span()); - let decode: syn::Stmt = syn::parse_quote! { - let #pv_ident = #arg_ident.to_owned_message(); - }; - let validate: syn::Stmt = syn::parse_quote! { - <_ as ::protovalidate_buffa::Validate>::validate(&#pv_ident) - .map_err(::protovalidate_buffa::ValidationError::into_connect_error)?; - }; + let decode: syn::Stmt = syn::parse_quote! { + let #pv_ident = #arg_ident.to_owned_message(); + }; + let validate: syn::Stmt = syn::parse_quote! { + <_ as ::protovalidate_buffa::Validate>::validate(&#pv_ident) + .map_err(::protovalidate_buffa::ValidationError::into_connect_error)?; + }; - f.block.stmts.insert(0, decode); - f.block.stmts.insert(1, validate); - } + f.block.stmts.insert(0, decode); + f.block.stmts.insert(1, validate); } } @@ -52,22 +52,21 @@ pub fn connect_impl(attr: TokenStream, input: TokenStream) -> TokenStream { /// Non-handler methods that lack such a parameter return `None`. fn find_owned_view_arg(sig: &syn::Signature) -> Option { for arg in &sig.inputs { - if let FnArg::Typed(PatType { pat, ty, .. }) = arg { - if is_owned_view(ty) { - if let syn::Pat::Ident(pat_ident) = pat.as_ref() { - return Some(pat_ident.ident.clone()); - } - } + if let FnArg::Typed(PatType { pat, ty, .. }) = arg + && is_owned_view(ty) + && let syn::Pat::Ident(pat_ident) = pat.as_ref() + { + return Some(pat_ident.ident.clone()); } } None } fn is_owned_view(ty: &Type) -> bool { - if let Type::Path(TypePath { path, .. }) = ty { - if let Some(last) = path.segments.last() { - return last.ident == "OwnedView"; - } + if let Type::Path(TypePath { path, .. }) = ty + && let Some(last) = path.segments.last() + { + return last.ident == "OwnedView"; } false } diff --git a/crates/protovalidate-buffa-protos/Cargo.toml b/crates/protovalidate-buffa-protos/Cargo.toml index c98a600..8faa3e2 100644 --- a/crates/protovalidate-buffa-protos/Cargo.toml +++ b/crates/protovalidate-buffa-protos/Cargo.toml @@ -15,8 +15,8 @@ keywords.workspace = true workspace = true [dependencies] -buffa = "0.4" -buffa-types = "0.4" +buffa = "0.5" +buffa-types = "0.5" [build-dependencies] -buffa-build = "0.4" +buffa-build = "0.5" diff --git a/crates/protovalidate-buffa/Cargo.toml b/crates/protovalidate-buffa/Cargo.toml index 50c353c..5ea936f 100644 --- a/crates/protovalidate-buffa/Cargo.toml +++ b/crates/protovalidate-buffa/Cargo.toml @@ -19,7 +19,7 @@ default = ["connect"] connect = ["dep:connectrpc"] [dependencies] -buffa = "0.4" +buffa = "0.5" protovalidate-buffa-macros = { path = "../protovalidate-buffa-macros", version = "0.1.0" } regex = "1" chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } @@ -28,6 +28,6 @@ uuid = "1" ulid = "1" ipnet = "2" fluent-uri = "0.4" -connectrpc = { version = "0.3", optional = true } +connectrpc = { version = "0.4", optional = true } percent-encoding = "2" http = "1" diff --git a/crates/protovalidate-buffa/src/cel.rs b/crates/protovalidate-buffa/src/cel.rs index 7b91633..55aedff 100644 --- a/crates/protovalidate-buffa/src/cel.rs +++ b/crates/protovalidate-buffa/src/cel.rs @@ -4,11 +4,11 @@ use std::{ }; use crate::{ + FieldPath, Violation, cel_core::{ - extractors::{Arguments, This}, Context, Program, Value, + extractors::{Arguments, This}, }, - FieldPath, Violation, }; pub struct CelConstraint { diff --git a/crates/protovalidate-buffa/src/rules.rs b/crates/protovalidate-buffa/src/rules.rs index ed639b1..a73e73e 100644 --- a/crates/protovalidate-buffa/src/rules.rs +++ b/crates/protovalidate-buffa/src/rules.rs @@ -137,10 +137,10 @@ pub mod string { return false; } } - if let Some(last) = labels.last() { - if last.bytes().all(|b| b.is_ascii_digit()) { - return false; - } + if let Some(last) = labels.last() + && last.bytes().all(|b| b.is_ascii_digit()) + { + return false; } true } diff --git a/crates/protovalidate-buffa/tests/connect_impl.rs b/crates/protovalidate-buffa/tests/connect_impl.rs index 59e1111..c8ad02b 100644 --- a/crates/protovalidate-buffa/tests/connect_impl.rs +++ b/crates/protovalidate-buffa/tests/connect_impl.rs @@ -18,7 +18,7 @@ use std::cell::Cell; -use protovalidate_buffa::{connect_impl, FieldPath, Validate, ValidationError, Violation}; +use protovalidate_buffa::{FieldPath, Validate, ValidationError, Violation, connect_impl}; struct FakeOwned { valid: bool,