This change simplifies the crate name to better reflect its core functionality as the event handling system for RustFS. The renamed package maintains all existing functionality while improving naming consistency across the project. - Updated all imports and references to use the new crate name - Maintained API compatibility with existing implementations - Updated tests to reflect the name change
617 lines
19 KiB
Rust
617 lines
19 KiB
Rust
use crate::Error;
|
|
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,
|
|
}
|
|
}
|
|
}
|