From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 4B1821FF0E0 for ; Thu, 09 Jul 2026 13:59:55 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id EF15621706; Thu, 09 Jul 2026 13:58:11 +0200 (CEST) From: Lukas Wagner To: pbs-devel@lists.proxmox.com, pve-devel@lists.proxmox.com Subject: [PATCH proxmox 09/29] notify: matcher: add InlineFieldMatcher Date: Thu, 9 Jul 2026 13:56:56 +0200 Message-ID: <20260709115716.299836-10-l.wagner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260709115716.299836-1-l.wagner@proxmox.com> References: <20260709115716.299836-1-l.wagner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1783598238600 X-SPAM-LEVEL: Spam detection results: 0 DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: MCW4GSSNTGZRJMKTZPK7NWHTDVNXN4D2 X-Message-ID-Hash: MCW4GSSNTGZRJMKTZPK7NWHTDVNXN4D2 X-MailFrom: l.wagner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: This move the inline serialization format into a new-type wrapper around FieldMatcher. This allows us to keep the existing single-line serialization format for the old configuration keys (match-field), while deriving a regular Serializer for FieldMatcher that will be used in the new expression based matcher. Signed-off-by: Lukas Wagner --- proxmox-notify/src/matcher/field.rs | 55 +++++++++++++++++++---------- proxmox-notify/src/matcher/mod.rs | 4 +-- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/proxmox-notify/src/matcher/field.rs b/proxmox-notify/src/matcher/field.rs index 8165b1f4..ad04b388 100644 --- a/proxmox-notify/src/matcher/field.rs +++ b/proxmox-notify/src/matcher/field.rs @@ -16,10 +16,10 @@ const_regex! { } pub const MATCH_FIELD_ENTRY_FORMAT: ApiStringFormat = - ApiStringFormat::VerifyFn(verify_field_matcher); + ApiStringFormat::VerifyFn(verify_inline_field_matcher); -fn verify_field_matcher(s: &str) -> Result<(), anyhow::Error> { - let _: FieldMatcher = s.parse()?; +fn verify_inline_field_matcher(s: &str) -> Result<(), anyhow::Error> { + let _: InlineFieldMatcher = s.parse()?; Ok(()) } @@ -75,12 +75,25 @@ impl MatchDirective for FieldMatcher { } } -impl fmt::Display for FieldMatcher { +/// Check if the notification metadata fields match +/// +/// This is a wrapper that serializes into an inline format that can be used +/// in a section config key-value pair. +#[derive(Clone, Debug)] +pub struct InlineFieldMatcher(FieldMatcher); + +impl MatchDirective for InlineFieldMatcher { + fn matches(&self, notification: &Notification) -> Result { + self.0.matches(notification) + } +} + +impl fmt::Display for InlineFieldMatcher { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Attention, Display is used to implement Serialize, do not // change the format. - match self { + match &self.0 { FieldMatcher::Exact { field, matched_values, @@ -99,7 +112,7 @@ impl fmt::Display for FieldMatcher { } } -impl FromStr for FieldMatcher { +impl FromStr for InlineFieldMatcher { type Err = Error; fn from_str(s: &str) -> Result { if !MATCH_FIELD_ENTRY_REGEX.is_match(s) { @@ -117,10 +130,10 @@ impl FromStr for FieldMatcher { let regex = Regex::new(expected_value_regex) .map_err(|err| Error::FilterFailed(format!("invalid regex: {err}")))?; - Ok(Self::Regex { + Ok(Self(FieldMatcher::Regex { field: field.into(), matched_regex: regex, - }) + })) } } } else if let Some(remaining) = s.strip_prefix("exact:") { @@ -134,10 +147,10 @@ impl FromStr for FieldMatcher { .map(str::trim) .map(String::from) .collect(); - Ok(Self::Exact { + Ok(Self(FieldMatcher::Exact { field: field.into(), matched_values: values, - }) + })) } } } else { @@ -148,8 +161,14 @@ impl FromStr for FieldMatcher { } } -proxmox_serde::forward_deserialize_to_from_str!(FieldMatcher); -proxmox_serde::forward_serialize_to_display!(FieldMatcher); +impl InlineFieldMatcher { + pub fn into_inner(self) -> FieldMatcher { + self.0 + } +} + +proxmox_serde::forward_deserialize_to_from_str!(InlineFieldMatcher); +proxmox_serde::forward_serialize_to_display!(InlineFieldMatcher); #[cfg(test)] mod tests { @@ -169,16 +188,16 @@ mod tests { let notification = Notification::from_template(Severity::Notice, "test", Value::Null, fields); - let matcher: FieldMatcher = "exact:foo=bar".parse().unwrap(); + let matcher: InlineFieldMatcher = "exact:foo=bar".parse().unwrap(); assert!(matcher.matches(¬ification).unwrap()); - let matcher: FieldMatcher = "regex:foo=b.*".parse().unwrap(); + let matcher: InlineFieldMatcher = "regex:foo=b.*".parse().unwrap(); assert!(matcher.matches(¬ification).unwrap()); - let matcher: FieldMatcher = "regex:notthere=b.*".parse().unwrap(); + let matcher: InlineFieldMatcher = "regex:notthere=b.*".parse().unwrap(); assert!(!matcher.matches(¬ification).unwrap()); - let matcher: FieldMatcher = "exact:foo=bar,test".parse().unwrap(); + let matcher: InlineFieldMatcher = "exact:foo=bar,test".parse().unwrap(); assert!(matcher.matches(¬ification).unwrap()); let mut fields = HashMap::new(); @@ -195,7 +214,7 @@ mod tests { Notification::from_template(Severity::Notice, "test", Value::Null, fields); assert!(!matcher.matches(¬ification).unwrap()); - assert!("regex:'3=b.*".parse::().is_err()); - assert!("invalid:'bar=b.*".parse::().is_err()); + assert!("regex:'3=b.*".parse::().is_err()); + assert!("invalid:'bar=b.*".parse::().is_err()); } } diff --git a/proxmox-notify/src/matcher/mod.rs b/proxmox-notify/src/matcher/mod.rs index bf2efceb..0267a2b9 100644 --- a/proxmox-notify/src/matcher/mod.rs +++ b/proxmox-notify/src/matcher/mod.rs @@ -15,7 +15,7 @@ pub mod field; pub mod severity; use calendar::CalendarMatcher; -use field::FieldMatcher; +use field::InlineFieldMatcher; use severity::InlineSeverityMatcher; pub const MATCHER_TYPENAME: &str = "matcher"; @@ -102,7 +102,7 @@ pub struct MatcherConfig { /// List of matched metadata fields. #[serde(default, skip_serializing_if = "Vec::is_empty")] #[updater(serde(skip_serializing_if = "Option::is_none"))] - pub match_field: Vec, + pub match_field: Vec, /// List of matched severity levels. #[serde(default, skip_serializing_if = "Vec::is_empty")] -- 2.47.3