mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 03:46:37 +00:00
Reconstructing Notify module
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
use super::pattern;
|
||||
use crate::arn::{ArnError, TargetIDError, ARN};
|
||||
use crate::event::EventName;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::io::Read;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParseConfigError {
|
||||
#[error("XML parsing error:{0}")]
|
||||
XmlError(#[from] quick_xml::errors::Error),
|
||||
#[error("Invalid filter value:{0}")]
|
||||
InvalidFilterValue(String),
|
||||
#[error("Invalid filter name: {0}, only 'prefix' or 'suffix' is allowed")]
|
||||
InvalidFilterName(String),
|
||||
#[error("There can only be one 'prefix' in the filter rule")]
|
||||
DuplicatePrefixFilter,
|
||||
#[error("There can only be one 'suffix' in the filter rule")]
|
||||
DuplicateSuffixFilter,
|
||||
#[error("Missing event name")]
|
||||
MissingEventName,
|
||||
#[error("Duplicate event name:{0}")]
|
||||
DuplicateEventName(String), // EventName is usually an enum, and here String is used to represent its text
|
||||
#[error("Repeated queue configuration: ID={0:?}, ARN={1}")]
|
||||
DuplicateQueueConfiguration(Option<String>, String),
|
||||
#[error("Unsupported configuration types (e.g. Lambda, Topic)")]
|
||||
UnsupportedConfiguration,
|
||||
#[error("ARN not found:{0}")]
|
||||
ArnNotFound(String),
|
||||
#[error("Unknown area:{0}")]
|
||||
UnknownRegion(String),
|
||||
#[error("ARN parsing error:{0}")]
|
||||
ArnParseError(#[from] ArnError),
|
||||
#[error("TargetID parsing error:{0}")]
|
||||
TargetIDParseError(#[from] TargetIDError),
|
||||
#[error("IO Error:{0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Region mismatch: Configure region {config_region}, current region {current_region}")]
|
||||
RegionMismatch { config_region: String, current_region: String },
|
||||
#[error("ARN {0} Not found in the provided list")]
|
||||
ArnValidation(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct FilterRule {
|
||||
#[serde(rename = "Name")]
|
||||
pub name: String,
|
||||
#[serde(rename = "Value")]
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl FilterRule {
|
||||
fn validate(&self) -> Result<(), ParseConfigError> {
|
||||
if self.name != "prefix" && self.name != "suffix" {
|
||||
return Err(ParseConfigError::InvalidFilterName(self.name.clone()));
|
||||
}
|
||||
// ValidateFilterRuleValue from Go:
|
||||
// no "." or ".." path segments, <= 1024 chars, valid UTF-8, no '\'.
|
||||
for segment in self.value.split('/') {
|
||||
if segment == "." || segment == ".." {
|
||||
return Err(ParseConfigError::InvalidFilterValue(self.value.clone()));
|
||||
}
|
||||
}
|
||||
if self.value.len() > 1024 || self.value.contains('\\') || std::str::from_utf8(self.value.as_bytes()).is_err() {
|
||||
return Err(ParseConfigError::InvalidFilterValue(self.value.clone()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
|
||||
pub struct FilterRuleList {
|
||||
#[serde(rename = "FilterRule", default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub rules: Vec<FilterRule>,
|
||||
}
|
||||
|
||||
impl FilterRuleList {
|
||||
pub fn validate(&self) -> Result<(), ParseConfigError> {
|
||||
let mut has_prefix = false;
|
||||
let mut has_suffix = false;
|
||||
for rule in &self.rules {
|
||||
rule.validate()?;
|
||||
if rule.name == "prefix" {
|
||||
if has_prefix {
|
||||
return Err(ParseConfigError::DuplicatePrefixFilter);
|
||||
}
|
||||
has_prefix = true;
|
||||
} else if rule.name == "suffix" {
|
||||
if has_suffix {
|
||||
return Err(ParseConfigError::DuplicateSuffixFilter);
|
||||
}
|
||||
has_suffix = true;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pattern(&self) -> String {
|
||||
let mut prefix_val: Option<&str> = None;
|
||||
let mut suffix_val: Option<&str> = None;
|
||||
|
||||
for rule in &self.rules {
|
||||
if rule.name == "prefix" {
|
||||
prefix_val = Some(&rule.value);
|
||||
} else if rule.name == "suffix" {
|
||||
suffix_val = Some(&rule.value);
|
||||
}
|
||||
}
|
||||
pattern::new_pattern(prefix_val, suffix_val)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.rules.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
|
||||
pub struct S3KeyFilter {
|
||||
#[serde(rename = "FilterRuleList", default, skip_serializing_if = "FilterRuleList::is_empty")]
|
||||
pub filter_rule_list: FilterRuleList,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct QueueConfig {
|
||||
#[serde(rename = "Id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
#[serde(rename = "Queue")] // This is ARN in XML
|
||||
pub arn: ARN,
|
||||
#[serde(rename = "Event", default)] // XML has multiple <Event> tags
|
||||
pub events: Vec<EventName>, // EventName needs to handle XML (de)serialization if not string
|
||||
#[serde(rename = "Filter", default, skip_serializing_if = "s3key_filter_is_empty")]
|
||||
pub filter: S3KeyFilter,
|
||||
}
|
||||
|
||||
fn s3key_filter_is_empty(f: &S3KeyFilter) -> bool {
|
||||
f.filter_rule_list.is_empty()
|
||||
}
|
||||
|
||||
impl QueueConfig {
|
||||
pub fn validate(&self, region: &str, arn_list: &[String]) -> Result<(), ParseConfigError> {
|
||||
if self.events.is_empty() {
|
||||
return Err(ParseConfigError::MissingEventName);
|
||||
}
|
||||
let mut event_set = HashSet::new();
|
||||
for event in &self.events {
|
||||
// EventName::to_string() or similar for uniqueness check
|
||||
if !event_set.insert(event.to_string()) {
|
||||
return Err(ParseConfigError::DuplicateEventName(event.to_string()));
|
||||
}
|
||||
}
|
||||
self.filter.filter_rule_list.validate()?;
|
||||
|
||||
// Validate ARN (similar to Go's Queue.Validate)
|
||||
// The Go code checks targetList.Exists(q.ARN.TargetID)
|
||||
// Here we check against a provided arn_list
|
||||
let _config_arn_str = self.arn.to_arn_string();
|
||||
if !self.arn.region.is_empty() && self.arn.region != region {
|
||||
return Err(ParseConfigError::UnknownRegion(self.arn.region.clone()));
|
||||
}
|
||||
|
||||
// Construct the ARN string that would be in arn_list
|
||||
// The arn_list contains ARNs like "arn:rustfs:sqs:REGION:ID:NAME"
|
||||
// We need to ensure self.arn (potentially with region adjusted) is in arn_list
|
||||
let effective_arn = ARN {
|
||||
target_id: self.arn.target_id.clone(),
|
||||
region: if self.arn.region.is_empty() {
|
||||
region.to_string()
|
||||
} else {
|
||||
self.arn.region.clone()
|
||||
},
|
||||
service: self.arn.service.clone(), // or default "sqs"
|
||||
partition: self.arn.partition.clone(), // or default "rustfs"
|
||||
};
|
||||
|
||||
if !arn_list.contains(&effective_arn.to_arn_string()) {
|
||||
return Err(ParseConfigError::ArnNotFound(effective_arn.to_arn_string()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the region if it's not already set in the ARN.
|
||||
pub fn set_region_if_empty(&mut self, region: &str) {
|
||||
if self.arn.region.is_empty() {
|
||||
self.arn.region = region.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponding to the `lambda` structure in the Go code.
|
||||
/// Used to parse <CloudFunction> ARN from inside the <CloudFunctionConfiguration> tag.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
|
||||
pub struct LambdaConfigDetail {
|
||||
#[serde(rename = "CloudFunction")]
|
||||
pub arn: String,
|
||||
// 根据 AWS S3 文档,<CloudFunctionConfiguration> 通常还包含 Id, Event, Filter
|
||||
// 但为了严格对应提供的 Go `lambda` 结构体,这里只包含 ARN。
|
||||
// 如果需要完整支持,可以添加其他字段。
|
||||
// 例如:
|
||||
// #[serde(rename = "Id", skip_serializing_if = "Option::is_none")]
|
||||
// pub id: Option<String>,
|
||||
// #[serde(rename = "Event", default, skip_serializing_if = "Vec::is_empty")]
|
||||
// pub events: Vec<EventName>,
|
||||
// #[serde(rename = "Filter", default, skip_serializing_if = "S3KeyFilterIsEmpty")]
|
||||
// pub filter: S3KeyFilter,
|
||||
}
|
||||
|
||||
/// Corresponding to the `topic` structure in the Go code.
|
||||
/// Used to parse <Topic> ARN from inside the <TopicConfiguration> tag.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
|
||||
pub struct TopicConfigDetail {
|
||||
#[serde(rename = "Topic")]
|
||||
pub arn: String,
|
||||
// 类似于 LambdaConfigDetail,可以根据需要扩展以包含 Id, Event, Filter 等字段。
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
|
||||
#[serde(rename = "NotificationConfiguration")]
|
||||
pub struct NotificationConfiguration {
|
||||
#[serde(rename = "xmlns", skip_serializing_if = "Option::is_none")]
|
||||
pub xmlns: Option<String>,
|
||||
#[serde(rename = "QueueConfiguration", default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub queue_list: Vec<QueueConfig>,
|
||||
#[serde(
|
||||
rename = "CloudFunctionConfiguration", // Tags for each lambda configuration item in XML
|
||||
default,
|
||||
skip_serializing_if = "Vec::is_empty"
|
||||
)]
|
||||
pub lambda_list: Vec<LambdaConfigDetail>, // Modify: Use a new structure
|
||||
#[serde(
|
||||
rename = "TopicConfiguration", // Tags for each topic configuration item in XML
|
||||
default,
|
||||
skip_serializing_if = "Vec::is_empty"
|
||||
)]
|
||||
pub topic_list: Vec<TopicConfigDetail>, // Modify: Use a new structure
|
||||
}
|
||||
|
||||
impl NotificationConfiguration {
|
||||
pub fn from_reader<R: Read>(reader: R) -> Result<Self, ParseConfigError> {
|
||||
let config: NotificationConfiguration = quick_xml::reader::Reader::from_reader(reader)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn validate(&self, current_region: &str, arn_list: &[String]) -> Result<(), ParseConfigError> {
|
||||
// Verification logic remains the same: if lambda_list or topic_list is not empty, it is considered an unsupported configuration
|
||||
if !self.lambda_list.is_empty() || !self.topic_list.is_empty() {
|
||||
return Err(ParseConfigError::UnsupportedConfiguration);
|
||||
}
|
||||
|
||||
let mut unique_queues = HashSet::new();
|
||||
for queue_config in &self.queue_list {
|
||||
queue_config.validate(current_region, arn_list)?;
|
||||
let queue_key = (
|
||||
queue_config.id.clone(),
|
||||
queue_config.arn.to_arn_string(), // Assuming that the ARN structure implements Display or ToString
|
||||
);
|
||||
if !unique_queues.insert(queue_key.clone()) {
|
||||
return Err(ParseConfigError::DuplicateQueueConfiguration(queue_key.0, queue_key.1));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_defaults(&mut self, region: &str) {
|
||||
for queue_config in &mut self.queue_list {
|
||||
queue_config.set_region_if_empty(region);
|
||||
}
|
||||
if self.xmlns.is_none() {
|
||||
self.xmlns = Some("http://s3.amazonaws.com/doc/2006-03-01/".to_string());
|
||||
}
|
||||
// 注意:如果 LambdaConfigDetail 和 TopicConfigDetail 将来包含区域等信息,
|
||||
// 也可能需要在这里设置默认值。但根据当前定义,它们只包含 ARN 字符串。
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user