re-uploading work

This commit is contained in:
2026-02-04 17:46:30 -06:00
commit 3b14c65998
1388 changed files with 381262 additions and 0 deletions

View File

@@ -0,0 +1,545 @@
//! Message Type Definitions
//!
//! This module defines the core message types and traits for inter-service
//! communication in Attune. All messages follow a standard envelope format
//! with headers and payload.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use uuid::Uuid;
use crate::models::Id;
/// Message trait that all messages must implement
pub trait Message: Serialize + for<'de> Deserialize<'de> + Send + Sync {
/// Get the message type identifier
fn message_type(&self) -> MessageType;
/// Get the routing key for this message
fn routing_key(&self) -> String {
self.message_type().routing_key()
}
/// Get the exchange name for this message
fn exchange(&self) -> String {
self.message_type().exchange()
}
/// Serialize message to JSON
fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
/// Deserialize message from JSON
fn from_json(json: &str) -> Result<Self, serde_json::Error>
where
Self: Sized,
{
serde_json::from_str(json)
}
}
/// Message type identifier
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MessageType {
/// Event created by sensor
EventCreated,
/// Enforcement created (rule triggered)
EnforcementCreated,
/// Execution requested
ExecutionRequested,
/// Execution status changed
ExecutionStatusChanged,
/// Execution completed
ExecutionCompleted,
/// Inquiry created (human input needed)
InquiryCreated,
/// Inquiry responded
InquiryResponded,
/// Notification created
NotificationCreated,
/// Rule created
RuleCreated,
/// Rule enabled
RuleEnabled,
/// Rule disabled
RuleDisabled,
}
impl MessageType {
/// Get the routing key for this message type
pub fn routing_key(&self) -> String {
match self {
Self::EventCreated => "event.created".to_string(),
Self::EnforcementCreated => "enforcement.created".to_string(),
Self::ExecutionRequested => "execution.requested".to_string(),
Self::ExecutionStatusChanged => "execution.status.changed".to_string(),
Self::ExecutionCompleted => "execution.completed".to_string(),
Self::InquiryCreated => "inquiry.created".to_string(),
Self::InquiryResponded => "inquiry.responded".to_string(),
Self::NotificationCreated => "notification.created".to_string(),
Self::RuleCreated => "rule.created".to_string(),
Self::RuleEnabled => "rule.enabled".to_string(),
Self::RuleDisabled => "rule.disabled".to_string(),
}
}
/// Get the exchange name for this message type
pub fn exchange(&self) -> String {
match self {
Self::EventCreated => "attune.events".to_string(),
Self::EnforcementCreated => "attune.executions".to_string(),
Self::ExecutionRequested | Self::ExecutionStatusChanged | Self::ExecutionCompleted => {
"attune.executions".to_string()
}
Self::InquiryCreated | Self::InquiryResponded => "attune.executions".to_string(),
Self::NotificationCreated => "attune.notifications".to_string(),
Self::RuleCreated | Self::RuleEnabled | Self::RuleDisabled => {
"attune.events".to_string()
}
}
}
/// Get the message type as a string
pub fn as_str(&self) -> &'static str {
match self {
Self::EventCreated => "EventCreated",
Self::EnforcementCreated => "EnforcementCreated",
Self::ExecutionRequested => "ExecutionRequested",
Self::ExecutionStatusChanged => "ExecutionStatusChanged",
Self::ExecutionCompleted => "ExecutionCompleted",
Self::InquiryCreated => "InquiryCreated",
Self::InquiryResponded => "InquiryResponded",
Self::NotificationCreated => "NotificationCreated",
Self::RuleCreated => "RuleCreated",
Self::RuleEnabled => "RuleEnabled",
Self::RuleDisabled => "RuleDisabled",
}
}
}
/// Message envelope that wraps all messages with metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageEnvelope<T>
where
T: Clone,
{
/// Unique message identifier
pub message_id: Uuid,
/// Correlation ID for tracing related messages
pub correlation_id: Uuid,
/// Message type
pub message_type: MessageType,
/// Message version (for backwards compatibility)
#[serde(default = "default_version")]
pub version: String,
/// Timestamp when message was created
pub timestamp: DateTime<Utc>,
/// Message headers
#[serde(default)]
pub headers: MessageHeaders,
/// Message payload
pub payload: T,
}
impl<T> MessageEnvelope<T>
where
T: Clone + Serialize + for<'de> Deserialize<'de>,
{
/// Create a new message envelope
pub fn new(message_type: MessageType, payload: T) -> Self {
let message_id = Uuid::new_v4();
Self {
message_id,
correlation_id: message_id, // Default to message_id, can be overridden
message_type,
version: "1.0".to_string(),
timestamp: Utc::now(),
headers: MessageHeaders::default(),
payload,
}
}
/// Set correlation ID for message tracing
pub fn with_correlation_id(mut self, correlation_id: Uuid) -> Self {
self.correlation_id = correlation_id;
self
}
/// Set source service
pub fn with_source(mut self, source: impl Into<String>) -> Self {
self.headers.source_service = Some(source.into());
self
}
/// Set trace ID
pub fn with_trace_id(mut self, trace_id: Uuid) -> Self {
self.headers.trace_id = Some(trace_id);
self
}
/// Increment retry count
pub fn increment_retry(&mut self) {
self.headers.retry_count += 1;
}
/// Serialize to JSON string
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
/// Deserialize from JSON string
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
/// Serialize to JSON bytes
pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
serde_json::to_vec(self)
}
/// Deserialize from JSON bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
serde_json::from_slice(bytes)
}
}
/// Message headers for metadata and tracing
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MessageHeaders {
/// Number of times this message has been retried
#[serde(default)]
pub retry_count: u32,
/// Source service that generated this message
#[serde(skip_serializing_if = "Option::is_none")]
pub source_service: Option<String>,
/// Trace ID for distributed tracing
#[serde(skip_serializing_if = "Option::is_none")]
pub trace_id: Option<Uuid>,
/// Additional custom headers
#[serde(flatten)]
pub custom: JsonValue,
}
impl MessageHeaders {
/// Create new headers
pub fn new() -> Self {
Self::default()
}
/// Create headers with source service
pub fn with_source(source: impl Into<String>) -> Self {
Self {
source_service: Some(source.into()),
..Default::default()
}
}
}
fn default_version() -> String {
"1.0".to_string()
}
// ============================================================================
// Message Payload Definitions
// ============================================================================
/// Payload for EventCreated message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventCreatedPayload {
/// Event ID
pub event_id: Id,
/// Trigger ID (may be None if trigger was deleted)
pub trigger_id: Option<Id>,
/// Trigger reference
pub trigger_ref: String,
/// Sensor ID that generated the event (None for system events)
pub sensor_id: Option<Id>,
/// Sensor reference (None for system events)
pub sensor_ref: Option<String>,
/// Event payload data
pub payload: JsonValue,
/// Configuration snapshot
pub config: Option<JsonValue>,
}
/// Payload for EnforcementCreated message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnforcementCreatedPayload {
/// Enforcement ID
pub enforcement_id: Id,
/// Rule ID (may be None if rule was deleted)
pub rule_id: Option<Id>,
/// Rule reference
pub rule_ref: String,
/// Event ID that triggered this enforcement
pub event_id: Option<Id>,
/// Trigger reference
pub trigger_ref: String,
/// Event payload for rule evaluation
pub payload: JsonValue,
}
/// Payload for ExecutionRequested message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionRequestedPayload {
/// Execution ID
pub execution_id: Id,
/// Action ID (may be None if action was deleted)
pub action_id: Option<Id>,
/// Action reference
pub action_ref: String,
/// Parent execution ID (for workflows)
pub parent_id: Option<Id>,
/// Enforcement ID that created this execution
pub enforcement_id: Option<Id>,
/// Execution configuration/parameters
pub config: Option<JsonValue>,
}
/// Payload for ExecutionStatusChanged message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionStatusChangedPayload {
/// Execution ID
pub execution_id: Id,
/// Action reference
pub action_ref: String,
/// Previous status
pub previous_status: String,
/// New status
pub new_status: String,
/// Status change timestamp
pub changed_at: DateTime<Utc>,
}
/// Payload for ExecutionCompleted message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionCompletedPayload {
/// Execution ID
pub execution_id: Id,
/// Action ID (needed for queue notification)
pub action_id: Id,
/// Action reference
pub action_ref: String,
/// Execution status (completed, failed, timeout, etc.)
pub status: String,
/// Execution result data
pub result: Option<JsonValue>,
/// Completion timestamp
pub completed_at: DateTime<Utc>,
}
/// Payload for InquiryCreated message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InquiryCreatedPayload {
/// Inquiry ID
pub inquiry_id: Id,
/// Execution ID that created this inquiry
pub execution_id: Id,
/// Prompt text for the user
pub prompt: String,
/// Response schema (optional)
pub response_schema: Option<JsonValue>,
/// User/identity assigned to respond (optional)
pub assigned_to: Option<Id>,
/// Timeout timestamp (optional)
pub timeout_at: Option<DateTime<Utc>>,
}
/// Payload for InquiryResponded message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InquiryRespondedPayload {
/// Inquiry ID
pub inquiry_id: Id,
/// Execution ID
pub execution_id: Id,
/// Response data
pub response: JsonValue,
/// User/identity that responded
pub responded_by: Option<Id>,
/// Response timestamp
pub responded_at: DateTime<Utc>,
}
/// Payload for NotificationCreated message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationCreatedPayload {
/// Notification ID
pub notification_id: Id,
/// Notification channel
pub channel: String,
/// Entity type (execution, inquiry, etc.)
pub entity_type: String,
/// Entity identifier
pub entity: String,
/// Activity description
pub activity: String,
/// Notification content
pub content: Option<JsonValue>,
}
/// Payload for RuleCreated message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleCreatedPayload {
/// Rule ID
pub rule_id: Id,
/// Rule reference
pub rule_ref: String,
/// Trigger ID
pub trigger_id: Option<Id>,
/// Trigger reference
pub trigger_ref: String,
/// Action ID
pub action_id: Option<Id>,
/// Action reference
pub action_ref: String,
/// Trigger parameters (from rule.trigger_params)
pub trigger_params: Option<JsonValue>,
/// Whether rule is enabled
pub enabled: bool,
}
/// Payload for RuleEnabled message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleEnabledPayload {
/// Rule ID
pub rule_id: Id,
/// Rule reference
pub rule_ref: String,
/// Trigger reference
pub trigger_ref: String,
/// Trigger parameters (from rule.trigger_params)
pub trigger_params: Option<JsonValue>,
}
/// Payload for RuleDisabled message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleDisabledPayload {
/// Rule ID
pub rule_id: Id,
/// Rule reference
pub rule_ref: String,
/// Trigger reference
pub trigger_ref: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct TestPayload {
data: String,
}
#[test]
fn test_message_envelope_creation() {
let payload = TestPayload {
data: "test".to_string(),
};
let envelope = MessageEnvelope::new(MessageType::EventCreated, payload.clone());
assert_eq!(envelope.message_type, MessageType::EventCreated);
assert_eq!(envelope.payload.data, "test");
assert_eq!(envelope.version, "1.0");
assert_eq!(envelope.message_id, envelope.correlation_id);
}
#[test]
fn test_message_envelope_with_correlation_id() {
let payload = TestPayload {
data: "test".to_string(),
};
let correlation_id = Uuid::new_v4();
let envelope = MessageEnvelope::new(MessageType::EventCreated, payload)
.with_correlation_id(correlation_id);
assert_eq!(envelope.correlation_id, correlation_id);
assert_ne!(envelope.message_id, envelope.correlation_id);
}
#[test]
fn test_message_envelope_serialization() {
let payload = TestPayload {
data: "test".to_string(),
};
let envelope = MessageEnvelope::new(MessageType::EventCreated, payload);
let json = envelope.to_json().unwrap();
assert!(json.contains("EventCreated"));
assert!(json.contains("test"));
let deserialized: MessageEnvelope<TestPayload> = MessageEnvelope::from_json(&json).unwrap();
assert_eq!(deserialized.message_id, envelope.message_id);
assert_eq!(deserialized.payload.data, "test");
}
#[test]
fn test_message_type_routing_key() {
assert_eq!(MessageType::EventCreated.routing_key(), "event.created");
assert_eq!(
MessageType::ExecutionRequested.routing_key(),
"execution.requested"
);
}
#[test]
fn test_message_type_exchange() {
assert_eq!(MessageType::EventCreated.exchange(), "attune.events");
assert_eq!(
MessageType::ExecutionRequested.exchange(),
"attune.executions"
);
assert_eq!(
MessageType::NotificationCreated.exchange(),
"attune.notifications"
);
}
#[test]
fn test_retry_increment() {
let payload = TestPayload {
data: "test".to_string(),
};
let mut envelope = MessageEnvelope::new(MessageType::EventCreated, payload);
assert_eq!(envelope.headers.retry_count, 0);
envelope.increment_retry();
assert_eq!(envelope.headers.retry_count, 1);
envelope.increment_retry();
assert_eq!(envelope.headers.retry_count, 2);
}
#[test]
fn test_message_headers_with_source() {
let headers = MessageHeaders::with_source("api-service");
assert_eq!(headers.source_service, Some("api-service".to_string()));
}
#[test]
fn test_envelope_with_source_and_trace() {
let payload = TestPayload {
data: "test".to_string(),
};
let trace_id = Uuid::new_v4();
let envelope = MessageEnvelope::new(MessageType::EventCreated, payload)
.with_source("api-service")
.with_trace_id(trace_id);
assert_eq!(
envelope.headers.source_service,
Some("api-service".to_string())
);
assert_eq!(envelope.headers.trace_id, Some(trace_id));
}
}