mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-29 08:27:06 +00:00
init event crate
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "rustfs-event"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
rustfs-config = { workspace = true, features = ["constants", "notify"] }
|
||||
common = { workspace = true }
|
||||
ecstore = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
reqwest = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
smallvec = { workspace = true, features = ["serde"] }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
tracing = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync", "net", "macros", "signal", "rt-multi-thread"] }
|
||||
tokio-util = { workspace = true }
|
||||
uuid = { workspace = true, features = ["v4", "serde"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,403 @@
|
||||
use thiserror::Error;
|
||||
use tokio::sync::mpsc::error;
|
||||
use tokio::task::JoinError;
|
||||
|
||||
/// The `Error` enum represents all possible errors that can occur in the application.
|
||||
/// It implements the `std::error::Error` trait and provides a way to convert various error types into a single error type.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Join error: {0}")]
|
||||
JoinError(#[from] JoinError),
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Serialization error: {0}")]
|
||||
Serde(#[from] serde_json::Error),
|
||||
#[error("HTTP error: {0}")]
|
||||
Http(#[from] reqwest::Error),
|
||||
#[cfg(all(feature = "kafka", target_os = "linux"))]
|
||||
#[error("Kafka error: {0}")]
|
||||
Kafka(#[from] rdkafka::error::KafkaError),
|
||||
#[cfg(feature = "mqtt")]
|
||||
#[error("MQTT error: {0}")]
|
||||
Mqtt(#[from] rumqttc::ClientError),
|
||||
#[error("Channel send error: {0}")]
|
||||
ChannelSend(#[from] Box<error::SendError<crate::event::Event>>),
|
||||
#[error("Feature disabled: {0}")]
|
||||
FeatureDisabled(&'static str),
|
||||
#[error("Event bus already started")]
|
||||
EventBusStarted,
|
||||
#[error("necessary fields are missing:{0}")]
|
||||
MissingField(&'static str),
|
||||
#[error("field verification failed:{0}")]
|
||||
ValidationError(&'static str),
|
||||
#[error("Custom error: {0}")]
|
||||
Custom(String),
|
||||
#[error("Configuration error: {0}")]
|
||||
ConfigError(String),
|
||||
#[error("create adapter failed error: {0}")]
|
||||
AdapterCreationFailed(String),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn custom(msg: &str) -> Error {
|
||||
Self::Custom(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::error::Error as StdError;
|
||||
use std::io;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[test]
|
||||
fn test_error_display() {
|
||||
// Test error message display
|
||||
let custom_error = Error::custom("test message");
|
||||
assert_eq!(custom_error.to_string(), "Custom error: test message");
|
||||
|
||||
let feature_error = Error::FeatureDisabled("test feature");
|
||||
assert_eq!(feature_error.to_string(), "Feature disabled: test feature");
|
||||
|
||||
let event_bus_error = Error::EventBusStarted;
|
||||
assert_eq!(event_bus_error.to_string(), "Event bus already started");
|
||||
|
||||
let missing_field_error = Error::MissingField("required_field");
|
||||
assert_eq!(missing_field_error.to_string(), "necessary fields are missing:required_field");
|
||||
|
||||
let validation_error = Error::ValidationError("invalid format");
|
||||
assert_eq!(validation_error.to_string(), "field verification failed:invalid format");
|
||||
|
||||
let config_error = Error::ConfigError("invalid config".to_string());
|
||||
assert_eq!(config_error.to_string(), "Configuration error: invalid config");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_debug() {
|
||||
// Test Debug trait implementation
|
||||
let custom_error = Error::custom("debug test");
|
||||
let debug_str = format!("{:?}", custom_error);
|
||||
assert!(debug_str.contains("Custom"));
|
||||
assert!(debug_str.contains("debug test"));
|
||||
|
||||
let feature_error = Error::FeatureDisabled("debug feature");
|
||||
let debug_str = format!("{:?}", feature_error);
|
||||
assert!(debug_str.contains("FeatureDisabled"));
|
||||
assert!(debug_str.contains("debug feature"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_error_creation() {
|
||||
// Test custom error creation
|
||||
let error = Error::custom("test custom error");
|
||||
match error {
|
||||
Error::Custom(msg) => assert_eq!(msg, "test custom error"),
|
||||
_ => panic!("Expected Custom error variant"),
|
||||
}
|
||||
|
||||
// Test empty string
|
||||
let empty_error = Error::custom("");
|
||||
match empty_error {
|
||||
Error::Custom(msg) => assert_eq!(msg, ""),
|
||||
_ => panic!("Expected Custom error variant"),
|
||||
}
|
||||
|
||||
// Test special characters
|
||||
let special_error = Error::custom("Test Chinese 中文 & special chars: !@#$%");
|
||||
match special_error {
|
||||
Error::Custom(msg) => assert_eq!(msg, "Test Chinese 中文 & special chars: !@#$%"),
|
||||
_ => panic!("Expected Custom error variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_error_conversion() {
|
||||
// Test IO error conversion
|
||||
let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
|
||||
let converted_error: Error = io_error.into();
|
||||
|
||||
match converted_error {
|
||||
Error::Io(err) => {
|
||||
assert_eq!(err.kind(), io::ErrorKind::NotFound);
|
||||
assert_eq!(err.to_string(), "file not found");
|
||||
}
|
||||
_ => panic!("Expected Io error variant"),
|
||||
}
|
||||
|
||||
// Test different types of IO errors
|
||||
let permission_error = io::Error::new(io::ErrorKind::PermissionDenied, "access denied");
|
||||
let converted: Error = permission_error.into();
|
||||
assert!(matches!(converted, Error::Io(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde_error_conversion() {
|
||||
// Test serialization error conversion
|
||||
let invalid_json = r#"{"invalid": json}"#;
|
||||
let serde_error = serde_json::from_str::<serde_json::Value>(invalid_json).unwrap_err();
|
||||
let converted_error: Error = serde_error.into();
|
||||
|
||||
match converted_error {
|
||||
Error::Serde(_) => {
|
||||
// Verify error type is correct
|
||||
assert!(converted_error.to_string().contains("Serialization error"));
|
||||
}
|
||||
_ => panic!("Expected Serde error variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_channel_send_error_conversion() {
|
||||
// Test channel send error conversion
|
||||
let (tx, rx) = mpsc::channel::<crate::event::Event>(1);
|
||||
drop(rx); // Close receiver
|
||||
|
||||
// Create a test event
|
||||
use crate::event::{Bucket, Identity, Metadata, Name, Object, Source};
|
||||
use std::collections::HashMap;
|
||||
|
||||
let identity = Identity::new("test-user".to_string());
|
||||
let bucket = Bucket::new("test-bucket".to_string(), identity.clone(), "arn:aws:s3:::test-bucket".to_string());
|
||||
let object = Object::new(
|
||||
"test-key".to_string(),
|
||||
Some(1024),
|
||||
Some("etag123".to_string()),
|
||||
Some("text/plain".to_string()),
|
||||
Some(HashMap::new()),
|
||||
None,
|
||||
"sequencer123".to_string(),
|
||||
);
|
||||
let metadata = Metadata::create("1.0".to_string(), "config1".to_string(), bucket, object);
|
||||
let source = Source::new("localhost".to_string(), "8080".to_string(), "test-agent".to_string());
|
||||
|
||||
let test_event = crate::event::Event::builder()
|
||||
.event_name(Name::ObjectCreatedPut)
|
||||
.s3(metadata)
|
||||
.source(source)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let send_result = tx.send(test_event).await;
|
||||
assert!(send_result.is_err());
|
||||
|
||||
let send_error = send_result.unwrap_err();
|
||||
let boxed_error = Box::new(send_error);
|
||||
let converted_error: Error = boxed_error.into();
|
||||
|
||||
match converted_error {
|
||||
Error::ChannelSend(_) => {
|
||||
assert!(converted_error.to_string().contains("Channel send error"));
|
||||
}
|
||||
_ => panic!("Expected ChannelSend error variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_source_chain() {
|
||||
// 测试错误源链
|
||||
let io_error = io::Error::new(io::ErrorKind::InvalidData, "invalid data");
|
||||
let converted_error: Error = io_error.into();
|
||||
|
||||
// 验证错误源
|
||||
assert!(converted_error.source().is_some());
|
||||
let source = converted_error.source().unwrap();
|
||||
assert_eq!(source.to_string(), "invalid data");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_variants_exhaustive() {
|
||||
// 测试所有错误变体的创建
|
||||
let errors = vec![
|
||||
Error::FeatureDisabled("test"),
|
||||
Error::EventBusStarted,
|
||||
Error::MissingField("field"),
|
||||
Error::ValidationError("validation"),
|
||||
Error::Custom("custom".to_string()),
|
||||
Error::ConfigError("config".to_string()),
|
||||
];
|
||||
|
||||
for error in errors {
|
||||
// 验证每个错误都能正确显示
|
||||
let error_str = error.to_string();
|
||||
assert!(!error_str.is_empty());
|
||||
|
||||
// 验证每个错误都能正确调试
|
||||
let debug_str = format!("{:?}", error);
|
||||
assert!(!debug_str.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_equality_and_matching() {
|
||||
// 测试错误的模式匹配
|
||||
let custom_error = Error::custom("test");
|
||||
match custom_error {
|
||||
Error::Custom(msg) => assert_eq!(msg, "test"),
|
||||
_ => panic!("Pattern matching failed"),
|
||||
}
|
||||
|
||||
let feature_error = Error::FeatureDisabled("feature");
|
||||
match feature_error {
|
||||
Error::FeatureDisabled(feature) => assert_eq!(feature, "feature"),
|
||||
_ => panic!("Pattern matching failed"),
|
||||
}
|
||||
|
||||
let event_bus_error = Error::EventBusStarted;
|
||||
match event_bus_error {
|
||||
Error::EventBusStarted => {} // 正确匹配
|
||||
_ => panic!("Pattern matching failed"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_message_formatting() {
|
||||
// 测试错误消息格式化
|
||||
let test_cases = vec![
|
||||
(Error::FeatureDisabled("kafka"), "Feature disabled: kafka"),
|
||||
(Error::MissingField("bucket_name"), "necessary fields are missing:bucket_name"),
|
||||
(Error::ValidationError("invalid email"), "field verification failed:invalid email"),
|
||||
(Error::ConfigError("missing file".to_string()), "Configuration error: missing file"),
|
||||
];
|
||||
|
||||
for (error, expected_message) in test_cases {
|
||||
assert_eq!(error.to_string(), expected_message);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_memory_efficiency() {
|
||||
// 测试错误类型的内存效率
|
||||
use std::mem;
|
||||
|
||||
let size = mem::size_of::<Error>();
|
||||
// 错误类型应该相对紧凑,考虑到包含多种错误类型,96 字节是合理的
|
||||
assert!(size <= 128, "Error size should be reasonable, got {} bytes", size);
|
||||
|
||||
// 测试 Option<Error>的大小
|
||||
let option_size = mem::size_of::<Option<Error>>();
|
||||
assert!(option_size <= 136, "Option<Error> should be efficient, got {} bytes", option_size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_thread_safety() {
|
||||
// 测试错误类型的线程安全性
|
||||
fn assert_send<T: Send>() {}
|
||||
fn assert_sync<T: Sync>() {}
|
||||
|
||||
assert_send::<Error>();
|
||||
assert_sync::<Error>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_error_edge_cases() {
|
||||
// 测试自定义错误的边界情况
|
||||
let long_message = "a".repeat(1000);
|
||||
let long_error = Error::custom(&long_message);
|
||||
match long_error {
|
||||
Error::Custom(msg) => assert_eq!(msg.len(), 1000),
|
||||
_ => panic!("Expected Custom error variant"),
|
||||
}
|
||||
|
||||
// 测试包含换行符的消息
|
||||
let multiline_error = Error::custom("line1\nline2\nline3");
|
||||
match multiline_error {
|
||||
Error::Custom(msg) => assert!(msg.contains('\n')),
|
||||
_ => panic!("Expected Custom error variant"),
|
||||
}
|
||||
|
||||
// 测试包含 Unicode 字符的消息
|
||||
let unicode_error = Error::custom("🚀 Unicode test 测试 🎉");
|
||||
match unicode_error {
|
||||
Error::Custom(msg) => assert!(msg.contains('🚀')),
|
||||
_ => panic!("Expected Custom error variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_conversion_consistency() {
|
||||
// 测试错误转换的一致性
|
||||
let original_io_error = io::Error::new(io::ErrorKind::TimedOut, "timeout");
|
||||
let error_message = original_io_error.to_string();
|
||||
let converted: Error = original_io_error.into();
|
||||
|
||||
// 验证转换后的错误包含原始错误信息
|
||||
assert!(converted.to_string().contains(&error_message));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_downcast() {
|
||||
// 测试错误的向下转型
|
||||
let io_error = io::Error::other("test error");
|
||||
let converted: Error = io_error.into();
|
||||
|
||||
// 验证可以获取源错误
|
||||
if let Error::Io(ref inner) = converted {
|
||||
assert_eq!(inner.to_string(), "test error");
|
||||
assert_eq!(inner.kind(), io::ErrorKind::Other);
|
||||
} else {
|
||||
panic!("Expected Io error variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_chain_depth() {
|
||||
// 测试错误链的深度
|
||||
let root_cause = io::Error::other("root cause");
|
||||
let converted: Error = root_cause.into();
|
||||
|
||||
let mut depth = 0;
|
||||
let mut current_error: &dyn StdError = &converted;
|
||||
|
||||
while let Some(source) = current_error.source() {
|
||||
depth += 1;
|
||||
current_error = source;
|
||||
// 防止无限循环
|
||||
if depth > 10 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(depth > 0, "Error should have at least one source");
|
||||
assert!(depth <= 3, "Error chain should not be too deep");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_str_lifetime() {
|
||||
// 测试静态字符串生命周期
|
||||
fn create_feature_error() -> Error {
|
||||
Error::FeatureDisabled("static_feature")
|
||||
}
|
||||
|
||||
let error = create_feature_error();
|
||||
match error {
|
||||
Error::FeatureDisabled(feature) => assert_eq!(feature, "static_feature"),
|
||||
_ => panic!("Expected FeatureDisabled error variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_formatting_consistency() {
|
||||
// 测试错误格式化的一致性
|
||||
let errors = vec![
|
||||
Error::FeatureDisabled("test"),
|
||||
Error::MissingField("field"),
|
||||
Error::ValidationError("validation"),
|
||||
Error::Custom("custom".to_string()),
|
||||
];
|
||||
|
||||
for error in errors {
|
||||
let display_str = error.to_string();
|
||||
let debug_str = format!("{:?}", error);
|
||||
|
||||
// Display 和 Debug 都不应该为空
|
||||
assert!(!display_str.is_empty());
|
||||
assert!(!debug_str.is_empty());
|
||||
|
||||
// Debug 输出通常包含更多信息,但不是绝对的
|
||||
// 这里我们只验证两者都有内容即可
|
||||
assert!(!debug_str.is_empty());
|
||||
assert!(!display_str.is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
use crate::Error;
|
||||
use reqwest::dns::Name;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use strum::{Display, EnumString};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A struct representing the identity of the user
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Identity {
|
||||
#[serde(rename = "principalId")]
|
||||
pub principal_id: String,
|
||||
}
|
||||
|
||||
impl Identity {
|
||||
/// Create a new Identity instance
|
||||
pub fn new(principal_id: String) -> Self {
|
||||
Self { principal_id }
|
||||
}
|
||||
|
||||
/// Set the principal ID
|
||||
pub fn set_principal_id(&mut self, principal_id: String) {
|
||||
self.principal_id = principal_id;
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct representing the bucket information
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Bucket {
|
||||
pub name: String,
|
||||
#[serde(rename = "ownerIdentity")]
|
||||
pub owner_identity: Identity,
|
||||
pub arn: String,
|
||||
}
|
||||
|
||||
impl Bucket {
|
||||
/// Create a new Bucket instance
|
||||
pub fn new(name: String, owner_identity: Identity, arn: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
owner_identity,
|
||||
arn,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the name of the bucket
|
||||
pub fn set_name(&mut self, name: String) {
|
||||
self.name = name;
|
||||
}
|
||||
|
||||
/// Set the ARN of the bucket
|
||||
pub fn set_arn(&mut self, arn: String) {
|
||||
self.arn = arn;
|
||||
}
|
||||
|
||||
/// Set the owner identity of the bucket
|
||||
pub fn set_owner_identity(&mut self, owner_identity: Identity) {
|
||||
self.owner_identity = owner_identity;
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct representing the object information
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Object {
|
||||
pub key: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub size: Option<i64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none", rename = "eTag")]
|
||||
pub etag: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none", rename = "contentType")]
|
||||
pub content_type: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none", rename = "userMetadata")]
|
||||
pub user_metadata: Option<HashMap<String, String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none", rename = "versionId")]
|
||||
pub version_id: Option<String>,
|
||||
pub sequencer: String,
|
||||
}
|
||||
|
||||
impl Object {
|
||||
/// Create a new Object instance
|
||||
pub fn new(
|
||||
key: String,
|
||||
size: Option<i64>,
|
||||
etag: Option<String>,
|
||||
content_type: Option<String>,
|
||||
user_metadata: Option<HashMap<String, String>>,
|
||||
version_id: Option<String>,
|
||||
sequencer: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
key,
|
||||
size,
|
||||
etag,
|
||||
content_type,
|
||||
user_metadata,
|
||||
version_id,
|
||||
sequencer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the key
|
||||
pub fn set_key(&mut self, key: String) {
|
||||
self.key = key;
|
||||
}
|
||||
|
||||
/// Set the size
|
||||
pub fn set_size(&mut self, size: Option<i64>) {
|
||||
self.size = size;
|
||||
}
|
||||
|
||||
/// Set the etag
|
||||
pub fn set_etag(&mut self, etag: Option<String>) {
|
||||
self.etag = etag;
|
||||
}
|
||||
|
||||
/// Set the content type
|
||||
pub fn set_content_type(&mut self, content_type: Option<String>) {
|
||||
self.content_type = content_type;
|
||||
}
|
||||
|
||||
/// Set the user metadata
|
||||
pub fn set_user_metadata(&mut self, user_metadata: Option<HashMap<String, String>>) {
|
||||
self.user_metadata = user_metadata;
|
||||
}
|
||||
|
||||
/// Set the version ID
|
||||
pub fn set_version_id(&mut self, version_id: Option<String>) {
|
||||
self.version_id = version_id;
|
||||
}
|
||||
|
||||
/// Set the sequencer
|
||||
pub fn set_sequencer(&mut self, sequencer: String) {
|
||||
self.sequencer = sequencer;
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct representing the metadata of the event
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Metadata {
|
||||
#[serde(rename = "s3SchemaVersion")]
|
||||
pub schema_version: String,
|
||||
#[serde(rename = "configurationId")]
|
||||
pub configuration_id: String,
|
||||
pub bucket: Bucket,
|
||||
pub object: Object,
|
||||
}
|
||||
|
||||
impl Default for Metadata {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
impl Metadata {
|
||||
/// Create a new Metadata instance with default values
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
schema_version: "1.0".to_string(),
|
||||
configuration_id: "default".to_string(),
|
||||
bucket: Bucket::new(
|
||||
"default".to_string(),
|
||||
Identity::new("default".to_string()),
|
||||
"arn:aws:s3:::default".to_string(),
|
||||
),
|
||||
object: Object::new("default".to_string(), None, None, None, None, None, "default".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new Metadata instance
|
||||
pub fn create(schema_version: String, configuration_id: String, bucket: Bucket, object: Object) -> Self {
|
||||
Self {
|
||||
schema_version,
|
||||
configuration_id,
|
||||
bucket,
|
||||
object,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the schema version
|
||||
pub fn set_schema_version(&mut self, schema_version: String) {
|
||||
self.schema_version = schema_version;
|
||||
}
|
||||
|
||||
/// Set the configuration ID
|
||||
pub fn set_configuration_id(&mut self, configuration_id: String) {
|
||||
self.configuration_id = configuration_id;
|
||||
}
|
||||
|
||||
/// Set the bucket
|
||||
pub fn set_bucket(&mut self, bucket: Bucket) {
|
||||
self.bucket = bucket;
|
||||
}
|
||||
|
||||
/// Set the object
|
||||
pub fn set_object(&mut self, object: Object) {
|
||||
self.object = object;
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct representing the source of the event
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Source {
|
||||
pub host: String,
|
||||
pub port: String,
|
||||
#[serde(rename = "userAgent")]
|
||||
pub user_agent: String,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
/// Create a new Source instance
|
||||
pub fn new(host: String, port: String, user_agent: String) -> Self {
|
||||
Self { host, port, user_agent }
|
||||
}
|
||||
|
||||
/// Set the host
|
||||
pub fn set_host(&mut self, host: String) {
|
||||
self.host = host;
|
||||
}
|
||||
|
||||
/// Set the port
|
||||
pub fn set_port(&mut self, port: String) {
|
||||
self.port = port;
|
||||
}
|
||||
|
||||
/// Set the user agent
|
||||
pub fn set_user_agent(&mut self, user_agent: String) {
|
||||
self.user_agent = user_agent;
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for creating an Event.
|
||||
///
|
||||
/// This struct is used to build an Event object with various parameters.
|
||||
/// It provides methods to set each parameter and a build method to create the Event.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct EventBuilder {
|
||||
event_version: Option<String>,
|
||||
event_source: Option<String>,
|
||||
aws_region: Option<String>,
|
||||
event_time: Option<String>,
|
||||
event_name: Option<Name>,
|
||||
user_identity: Option<Identity>,
|
||||
request_parameters: Option<HashMap<String, String>>,
|
||||
response_elements: Option<HashMap<String, String>>,
|
||||
s3: Option<Metadata>,
|
||||
source: Option<Source>,
|
||||
channels: Option<SmallVec<[String; 2]>>,
|
||||
}
|
||||
|
||||
impl EventBuilder {
|
||||
/// create a builder that pre filled default values
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
event_version: Some(Cow::Borrowed("2.0").to_string()),
|
||||
event_source: Some(Cow::Borrowed("aws:s3").to_string()),
|
||||
aws_region: Some("us-east-1".to_string()),
|
||||
event_time: Some(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs().to_string()),
|
||||
event_name: None,
|
||||
user_identity: Some(Identity {
|
||||
principal_id: "anonymous".to_string(),
|
||||
}),
|
||||
request_parameters: Some(HashMap::new()),
|
||||
response_elements: Some(HashMap::new()),
|
||||
s3: None,
|
||||
source: None,
|
||||
channels: Some(Vec::new().into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// verify and set the event version
|
||||
pub fn event_version(mut self, event_version: impl Into<String>) -> Self {
|
||||
let event_version = event_version.into();
|
||||
if !event_version.is_empty() {
|
||||
self.event_version = Some(event_version);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// verify and set the event source
|
||||
pub fn event_source(mut self, event_source: impl Into<String>) -> Self {
|
||||
let event_source = event_source.into();
|
||||
if !event_source.is_empty() {
|
||||
self.event_source = Some(event_source);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// set up aws regions
|
||||
pub fn aws_region(mut self, aws_region: impl Into<String>) -> Self {
|
||||
self.aws_region = Some(aws_region.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// set event time
|
||||
pub fn event_time(mut self, event_time: impl Into<String>) -> Self {
|
||||
self.event_time = Some(event_time.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// set event name
|
||||
pub fn event_name(mut self, event_name: Name) -> Self {
|
||||
self.event_name = Some(event_name);
|
||||
self
|
||||
}
|
||||
|
||||
/// set user identity
|
||||
pub fn user_identity(mut self, user_identity: Identity) -> Self {
|
||||
self.user_identity = Some(user_identity);
|
||||
self
|
||||
}
|
||||
|
||||
/// set request parameters
|
||||
pub fn request_parameters(mut self, request_parameters: HashMap<String, String>) -> Self {
|
||||
self.request_parameters = Some(request_parameters);
|
||||
self
|
||||
}
|
||||
|
||||
/// set response elements
|
||||
pub fn response_elements(mut self, response_elements: HashMap<String, String>) -> Self {
|
||||
self.response_elements = Some(response_elements);
|
||||
self
|
||||
}
|
||||
|
||||
/// setting up s3 metadata
|
||||
pub fn s3(mut self, s3: Metadata) -> Self {
|
||||
self.s3 = Some(s3);
|
||||
self
|
||||
}
|
||||
|
||||
/// set event source information
|
||||
pub fn source(mut self, source: Source) -> Self {
|
||||
self.source = Some(source);
|
||||
self
|
||||
}
|
||||
|
||||
/// set up the sending channel
|
||||
pub fn channels(mut self, channels: Vec<String>) -> Self {
|
||||
self.channels = Some(channels.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a preconfigured builder for common object event scenarios
|
||||
pub fn for_object_creation(s3: Metadata, source: Source) -> Self {
|
||||
Self::new().event_name(Name::ObjectCreatedPut).s3(s3).source(source)
|
||||
}
|
||||
|
||||
/// Create a preconfigured builder for object deletion events
|
||||
pub fn for_object_removal(s3: Metadata, source: Source) -> Self {
|
||||
Self::new().event_name(Name::ObjectRemovedDelete).s3(s3).source(source)
|
||||
}
|
||||
|
||||
/// build event instance
|
||||
///
|
||||
/// Verify the required fields and create a complete Event object
|
||||
pub fn build(self) -> Result<Event, Error> {
|
||||
let event_version = self.event_version.ok_or(Error::MissingField("event_version"))?;
|
||||
|
||||
let event_source = self.event_source.ok_or(Error::MissingField("event_source"))?;
|
||||
|
||||
let aws_region = self.aws_region.ok_or(Error::MissingField("aws_region"))?;
|
||||
|
||||
let event_time = self.event_time.ok_or(Error::MissingField("event_time"))?;
|
||||
|
||||
let event_name = self.event_name.ok_or(Error::MissingField("event_name"))?;
|
||||
|
||||
let user_identity = self.user_identity.ok_or(Error::MissingField("user_identity"))?;
|
||||
|
||||
let request_parameters = self.request_parameters.unwrap_or_default();
|
||||
let response_elements = self.response_elements.unwrap_or_default();
|
||||
|
||||
let s3 = self.s3.ok_or(Error::MissingField("s3"))?;
|
||||
|
||||
let source = self.source.ok_or(Error::MissingField("source"))?;
|
||||
|
||||
let channels = self.channels.unwrap_or_else(|| smallvec![]);
|
||||
|
||||
Ok(Event {
|
||||
event_version,
|
||||
event_source,
|
||||
aws_region,
|
||||
event_time,
|
||||
event_name,
|
||||
user_identity,
|
||||
request_parameters,
|
||||
response_elements,
|
||||
s3,
|
||||
source,
|
||||
id: Uuid::new_v4(),
|
||||
timestamp: SystemTime::now(),
|
||||
channels,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Event {
|
||||
#[serde(rename = "eventVersion")]
|
||||
pub event_version: String,
|
||||
#[serde(rename = "eventSource")]
|
||||
pub event_source: String,
|
||||
#[serde(rename = "awsRegion")]
|
||||
pub aws_region: String,
|
||||
#[serde(rename = "eventTime")]
|
||||
pub event_time: String,
|
||||
#[serde(rename = "eventName")]
|
||||
pub event_name: Name,
|
||||
#[serde(rename = "userIdentity")]
|
||||
pub user_identity: Identity,
|
||||
#[serde(rename = "requestParameters")]
|
||||
pub request_parameters: HashMap<String, String>,
|
||||
#[serde(rename = "responseElements")]
|
||||
pub response_elements: HashMap<String, String>,
|
||||
pub s3: Metadata,
|
||||
pub source: Source,
|
||||
pub id: Uuid,
|
||||
pub timestamp: SystemTime,
|
||||
pub channels: SmallVec<[String; 2]>,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// create a new event builder
|
||||
///
|
||||
/// Returns an EventBuilder instance pre-filled with default values
|
||||
pub fn builder() -> EventBuilder {
|
||||
EventBuilder::new()
|
||||
}
|
||||
|
||||
/// Quickly create Event instances with necessary fields
|
||||
///
|
||||
/// suitable for common s3 event scenarios
|
||||
pub fn create(event_name: Name, s3: Metadata, source: Source, channels: Vec<String>) -> Self {
|
||||
Self::builder()
|
||||
.event_name(event_name)
|
||||
.s3(s3)
|
||||
.source(source)
|
||||
.channels(channels)
|
||||
.build()
|
||||
.expect("Failed to create event, missing necessary parameters")
|
||||
}
|
||||
|
||||
/// a convenient way to create a preconfigured builder
|
||||
pub fn for_object_creation(s3: Metadata, source: Source) -> EventBuilder {
|
||||
EventBuilder::for_object_creation(s3, source)
|
||||
}
|
||||
|
||||
/// a convenient way to create a preconfigured builder
|
||||
pub fn for_object_removal(s3: Metadata, source: Source) -> EventBuilder {
|
||||
EventBuilder::for_object_removal(s3, source)
|
||||
}
|
||||
|
||||
/// Determine whether an event belongs to a specific type
|
||||
pub fn is_type(&self, event_type: Name) -> bool {
|
||||
let mask = event_type.mask();
|
||||
(self.event_name.mask() & mask) != 0
|
||||
}
|
||||
|
||||
/// Determine whether an event needs to be sent to a specific channel
|
||||
pub fn is_for_channel(&self, channel: &str) -> bool {
|
||||
self.channels.iter().any(|c| c == channel)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Log {
|
||||
#[serde(rename = "eventName")]
|
||||
pub event_name: Name,
|
||||
pub key: String,
|
||||
pub records: Vec<Event>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, SerializeDisplay, DeserializeFromStr, Display, EnumString)]
|
||||
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum Name {
|
||||
ObjectAccessedGet,
|
||||
ObjectAccessedGetRetention,
|
||||
ObjectAccessedGetLegalHold,
|
||||
ObjectAccessedHead,
|
||||
ObjectAccessedAttributes,
|
||||
ObjectCreatedCompleteMultipartUpload,
|
||||
ObjectCreatedCopy,
|
||||
ObjectCreatedPost,
|
||||
ObjectCreatedPut,
|
||||
ObjectCreatedPutRetention,
|
||||
ObjectCreatedPutLegalHold,
|
||||
ObjectCreatedPutTagging,
|
||||
ObjectCreatedDeleteTagging,
|
||||
ObjectRemovedDelete,
|
||||
ObjectRemovedDeleteMarkerCreated,
|
||||
ObjectRemovedDeleteAllVersions,
|
||||
ObjectRemovedNoOp,
|
||||
BucketCreated,
|
||||
BucketRemoved,
|
||||
ObjectReplicationFailed,
|
||||
ObjectReplicationComplete,
|
||||
ObjectReplicationMissedThreshold,
|
||||
ObjectReplicationReplicatedAfterThreshold,
|
||||
ObjectReplicationNotTracked,
|
||||
ObjectRestorePost,
|
||||
ObjectRestoreCompleted,
|
||||
ObjectTransitionFailed,
|
||||
ObjectTransitionComplete,
|
||||
ObjectManyVersions,
|
||||
ObjectLargeVersions,
|
||||
PrefixManyFolders,
|
||||
IlmDelMarkerExpirationDelete,
|
||||
ObjectAccessedAll,
|
||||
ObjectCreatedAll,
|
||||
ObjectRemovedAll,
|
||||
ObjectReplicationAll,
|
||||
ObjectRestoreAll,
|
||||
ObjectTransitionAll,
|
||||
ObjectScannerAll,
|
||||
Everything,
|
||||
}
|
||||
|
||||
impl Name {
|
||||
pub fn expand(&self) -> Vec<Name> {
|
||||
match self {
|
||||
Name::ObjectAccessedAll => vec![
|
||||
Name::ObjectAccessedGet,
|
||||
Name::ObjectAccessedHead,
|
||||
Name::ObjectAccessedGetRetention,
|
||||
Name::ObjectAccessedGetLegalHold,
|
||||
Name::ObjectAccessedAttributes,
|
||||
],
|
||||
Name::ObjectCreatedAll => vec![
|
||||
Name::ObjectCreatedCompleteMultipartUpload,
|
||||
Name::ObjectCreatedCopy,
|
||||
Name::ObjectCreatedPost,
|
||||
Name::ObjectCreatedPut,
|
||||
Name::ObjectCreatedPutRetention,
|
||||
Name::ObjectCreatedPutLegalHold,
|
||||
Name::ObjectCreatedPutTagging,
|
||||
Name::ObjectCreatedDeleteTagging,
|
||||
],
|
||||
Name::ObjectRemovedAll => vec![
|
||||
Name::ObjectRemovedDelete,
|
||||
Name::ObjectRemovedDeleteMarkerCreated,
|
||||
Name::ObjectRemovedNoOp,
|
||||
Name::ObjectRemovedDeleteAllVersions,
|
||||
],
|
||||
Name::ObjectReplicationAll => vec![
|
||||
Name::ObjectReplicationFailed,
|
||||
Name::ObjectReplicationComplete,
|
||||
Name::ObjectReplicationNotTracked,
|
||||
Name::ObjectReplicationMissedThreshold,
|
||||
Name::ObjectReplicationReplicatedAfterThreshold,
|
||||
],
|
||||
Name::ObjectRestoreAll => vec![Name::ObjectRestorePost, Name::ObjectRestoreCompleted],
|
||||
Name::ObjectTransitionAll => {
|
||||
vec![Name::ObjectTransitionFailed, Name::ObjectTransitionComplete]
|
||||
}
|
||||
Name::ObjectScannerAll => vec![Name::ObjectManyVersions, Name::ObjectLargeVersions, Name::PrefixManyFolders],
|
||||
Name::Everything => (1..=Name::IlmDelMarkerExpirationDelete as u32)
|
||||
.map(|i| Name::from_repr(i).unwrap())
|
||||
.collect(),
|
||||
_ => vec![*self],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mask(&self) -> u64 {
|
||||
if (*self as u32) < Name::ObjectAccessedAll as u32 {
|
||||
1 << (*self as u32 - 1)
|
||||
} else {
|
||||
self.expand().iter().fold(0, |acc, n| acc | (1 << (*n as u32 - 1)))
|
||||
}
|
||||
}
|
||||
|
||||
fn from_repr(discriminant: u32) -> Option<Self> {
|
||||
match discriminant {
|
||||
1 => Some(Name::ObjectAccessedGet),
|
||||
2 => Some(Name::ObjectAccessedGetRetention),
|
||||
3 => Some(Name::ObjectAccessedGetLegalHold),
|
||||
4 => Some(Name::ObjectAccessedHead),
|
||||
5 => Some(Name::ObjectAccessedAttributes),
|
||||
6 => Some(Name::ObjectCreatedCompleteMultipartUpload),
|
||||
7 => Some(Name::ObjectCreatedCopy),
|
||||
8 => Some(Name::ObjectCreatedPost),
|
||||
9 => Some(Name::ObjectCreatedPut),
|
||||
10 => Some(Name::ObjectCreatedPutRetention),
|
||||
11 => Some(Name::ObjectCreatedPutLegalHold),
|
||||
12 => Some(Name::ObjectCreatedPutTagging),
|
||||
13 => Some(Name::ObjectCreatedDeleteTagging),
|
||||
14 => Some(Name::ObjectRemovedDelete),
|
||||
15 => Some(Name::ObjectRemovedDeleteMarkerCreated),
|
||||
16 => Some(Name::ObjectRemovedDeleteAllVersions),
|
||||
17 => Some(Name::ObjectRemovedNoOp),
|
||||
18 => Some(Name::BucketCreated),
|
||||
19 => Some(Name::BucketRemoved),
|
||||
20 => Some(Name::ObjectReplicationFailed),
|
||||
21 => Some(Name::ObjectReplicationComplete),
|
||||
22 => Some(Name::ObjectReplicationMissedThreshold),
|
||||
23 => Some(Name::ObjectReplicationReplicatedAfterThreshold),
|
||||
24 => Some(Name::ObjectReplicationNotTracked),
|
||||
25 => Some(Name::ObjectRestorePost),
|
||||
26 => Some(Name::ObjectRestoreCompleted),
|
||||
27 => Some(Name::ObjectTransitionFailed),
|
||||
28 => Some(Name::ObjectTransitionComplete),
|
||||
29 => Some(Name::ObjectManyVersions),
|
||||
30 => Some(Name::ObjectLargeVersions),
|
||||
31 => Some(Name::PrefixManyFolders),
|
||||
32 => Some(Name::IlmDelMarkerExpirationDelete),
|
||||
33 => Some(Name::ObjectAccessedAll),
|
||||
34 => Some(Name::ObjectCreatedAll),
|
||||
35 => Some(Name::ObjectRemovedAll),
|
||||
36 => Some(Name::ObjectReplicationAll),
|
||||
37 => Some(Name::ObjectRestoreAll),
|
||||
38 => Some(Name::ObjectTransitionAll),
|
||||
39 => Some(Name::ObjectScannerAll),
|
||||
40 => Some(Name::Everything),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
mod error;
|
||||
mod event;
|
||||
mod notifier;
|
||||
mod system;
|
||||
@@ -0,0 +1,143 @@
|
||||
use crate::config::EventNotifierConfig;
|
||||
use crate::event::Event;
|
||||
use common::error::{Error, Result};
|
||||
use ecstore::store::ECStore;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
|
||||
/// Event Notifier
|
||||
pub struct EventNotifier {
|
||||
/// The event sending channel
|
||||
sender: mpsc::Sender<Event>,
|
||||
/// Receiver task handle
|
||||
task_handle: Option<tokio::task::JoinHandle<()>>,
|
||||
/// Configuration information
|
||||
config: EventNotifierConfig,
|
||||
/// Turn off tagging
|
||||
shutdown: CancellationToken,
|
||||
/// Close the notification channel
|
||||
shutdown_complete_tx: Option<broadcast::Sender<()>>,
|
||||
}
|
||||
|
||||
impl EventNotifier {
|
||||
/// Create a new event notifier
|
||||
#[instrument(skip_all)]
|
||||
pub async fn new(store: Arc<ECStore>) -> Result<Self> {
|
||||
let manager = crate::store::manager::EventManager::new(store);
|
||||
|
||||
let manager = Arc::new(manager.await);
|
||||
|
||||
// Initialize the configuration
|
||||
let config = manager.clone().init().await?;
|
||||
|
||||
// Create adapters
|
||||
let adapters = manager.clone().create_adapters().await?;
|
||||
info!("Created {} adapters", adapters.len());
|
||||
|
||||
// Create a close marker
|
||||
let shutdown = CancellationToken::new();
|
||||
let (shutdown_complete_tx, _) = broadcast::channel(1);
|
||||
|
||||
// 创建事件通道 - 使用默认容量,因为每个适配器都有自己的队列
|
||||
// 这里使用较小的通道容量,因为事件会被快速分发到适配器
|
||||
let (sender, mut receiver) = mpsc::channel::<Event>(100);
|
||||
|
||||
let shutdown_clone = shutdown.clone();
|
||||
let shutdown_complete_tx_clone = shutdown_complete_tx.clone();
|
||||
let adapters_clone = adapters.clone();
|
||||
|
||||
// Start the event processing task
|
||||
let task_handle = tokio::spawn(async move {
|
||||
debug!("The event processing task starts");
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(event) = receiver.recv() => {
|
||||
debug!("The event is received:{}", event.id);
|
||||
|
||||
// Distribute to all adapters
|
||||
for adapter in &adapters_clone {
|
||||
let adapter_name = adapter.name();
|
||||
match adapter.send(&event).await {
|
||||
Ok(_) => {
|
||||
debug!("Event {} Successfully sent to the adapter {}", event.id, adapter_name);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Event {} send to adapter {} failed:{}", event.id, adapter_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = shutdown_clone.cancelled() => {
|
||||
info!("A shutdown signal is received, and the event processing task is stopped");
|
||||
let _ = shutdown_complete_tx_clone.send(());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("The event processing task has been stopped");
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
sender,
|
||||
task_handle: Some(task_handle),
|
||||
config,
|
||||
shutdown,
|
||||
shutdown_complete_tx: Some(shutdown_complete_tx),
|
||||
})
|
||||
}
|
||||
|
||||
/// Turn off the event notifier
|
||||
pub async fn shutdown(&mut self) -> Result<()> {
|
||||
info!("Turn off the event notifier");
|
||||
self.shutdown.cancel();
|
||||
|
||||
if let Some(shutdown_tx) = self.shutdown_complete_tx.take() {
|
||||
let mut rx = shutdown_tx.subscribe();
|
||||
|
||||
// Wait for the shutdown to complete the signal or time out
|
||||
tokio::select! {
|
||||
_ = rx.recv() => {
|
||||
debug!("A shutdown completion signal is received");
|
||||
}
|
||||
_ = tokio::time::sleep(std::time::Duration::from_secs(10)) => {
|
||||
warn!("Shutdown timeout and forced termination");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(handle) = self.task_handle.take() {
|
||||
handle.abort();
|
||||
match handle.await {
|
||||
Ok(_) => debug!("The event processing task has been terminated gracefully"),
|
||||
Err(e) => {
|
||||
if e.is_cancelled() {
|
||||
debug!("The event processing task has been canceled");
|
||||
} else {
|
||||
error!("An error occurred while waiting for the event processing task to terminate:{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("The event notifier is completely turned off");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send events
|
||||
pub async fn send(&self, event: Event) -> Result<()> {
|
||||
self.sender
|
||||
.send(event)
|
||||
.await
|
||||
.map_err(|e| Error::msg(format!("Failed to send events to channel:{}", e)))
|
||||
}
|
||||
|
||||
/// Get the current configuration
|
||||
pub fn config(&self) -> &EventNotifierConfig {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
use crate::notifier::EventNotifier;
|
||||
use common::error::Result;
|
||||
use ecstore::store::ECStore;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
/// Global event system
|
||||
pub struct EventSystem {
|
||||
/// Event Notifier
|
||||
notifier: Mutex<Option<EventNotifier>>,
|
||||
}
|
||||
|
||||
impl EventSystem {
|
||||
/// Create a new event system
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
notifier: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the event system
|
||||
pub async fn init(&self, store: Arc<ECStore>) -> Result<EventNotifierConfig> {
|
||||
info!("Initialize the event system");
|
||||
let notifier = EventNotifier::new(store).await?;
|
||||
let config = notifier.config().clone();
|
||||
|
||||
let mut guard = self
|
||||
.notifier
|
||||
.lock()
|
||||
.map_err(|e| common::error::Error::msg(format!("Failed to acquire locks:{}", e)))?;
|
||||
|
||||
*guard = Some(notifier);
|
||||
debug!("The event system initialization is complete");
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Send events
|
||||
pub async fn send_event(&self, event: crate::Event) -> Result<()> {
|
||||
let guard = self
|
||||
.notifier
|
||||
.lock()
|
||||
.map_err(|e| common::error::Error::msg(format!("Failed to acquire locks:{}", e)))?;
|
||||
|
||||
if let Some(notifier) = &*guard {
|
||||
notifier.send(event).await
|
||||
} else {
|
||||
error!("The event system is not initialized");
|
||||
Err(common::error::Error::msg("The event system is not initialized"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Shut down the event system
|
||||
pub async fn shutdown(&self) -> Result<()> {
|
||||
info!("Shut down the event system");
|
||||
let mut guard = self
|
||||
.notifier
|
||||
.lock()
|
||||
.map_err(|e| common::error::Error::msg(format!("Failed to acquire locks:{}", e)))?;
|
||||
|
||||
if let Some(ref mut notifier) = *guard {
|
||||
notifier.shutdown().await?;
|
||||
*guard = None;
|
||||
info!("The event system is down");
|
||||
Ok(())
|
||||
} else {
|
||||
debug!("The event system has been shut down");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A global event system instance
|
||||
pub static GLOBAL_EVENT_SYS: OnceCell<EventSystem> = OnceCell::new();
|
||||
|
||||
/// Initialize the global event system
|
||||
pub fn init_global_event_system() -> &'static EventSystem {
|
||||
GLOBAL_EVENT_SYS.get_or_init(EventSystem::new)
|
||||
}
|
||||
Reference in New Issue
Block a user