use crate::identity::{
AttemptId, CapabilityId, CorrelationId, EngineId, EngineInstanceId, MessageId, OperationId,
};
use crate::operation::{Operation, OperationContext};
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CorrelationContext {
correlation_id: CorrelationId,
operation_id: Option<OperationId>,
attempt_id: Option<AttemptId>,
message_id: Option<MessageId>,
engine_id: Option<EngineId>,
engine_instance_id: Option<EngineInstanceId>,
capability_id: Option<CapabilityId>,
}
impl CorrelationContext {
pub fn new(correlation_id: CorrelationId) -> Self {
Self {
correlation_id,
operation_id: None,
attempt_id: None,
message_id: None,
engine_id: None,
engine_instance_id: None,
capability_id: None,
}
}
pub fn from_operation(operation: &Operation) -> Self {
Self::new(operation.correlation_id.clone()).with_operation_id(operation.id.clone())
}
pub fn from_operation_context(context: &OperationContext) -> Self {
let correlation = Self::from_operation(&context.operation);
match &context.attempt_id {
Some(attempt_id) => correlation.with_attempt_id(attempt_id.clone()),
None => correlation,
}
}
pub fn correlation_id(&self) -> &CorrelationId {
&self.correlation_id
}
pub fn operation_id(&self) -> Option<&OperationId> {
self.operation_id.as_ref()
}
pub fn attempt_id(&self) -> Option<&AttemptId> {
self.attempt_id.as_ref()
}
pub fn message_id(&self) -> Option<&MessageId> {
self.message_id.as_ref()
}
pub fn engine_id(&self) -> Option<&EngineId> {
self.engine_id.as_ref()
}
pub fn engine_instance_id(&self) -> Option<&EngineInstanceId> {
self.engine_instance_id.as_ref()
}
pub fn capability_id(&self) -> Option<&CapabilityId> {
self.capability_id.as_ref()
}
pub fn with_operation_id(mut self, operation_id: OperationId) -> Self {
self.operation_id = Some(operation_id);
self
}
pub fn with_attempt_id(mut self, attempt_id: AttemptId) -> Self {
self.attempt_id = Some(attempt_id);
self
}
pub fn with_message_id(mut self, message_id: MessageId) -> Self {
self.message_id = Some(message_id);
self
}
pub fn with_engine_id(mut self, engine_id: EngineId) -> Self {
self.engine_id = Some(engine_id);
self
}
pub fn with_engine_instance_id(mut self, engine_instance_id: EngineInstanceId) -> Self {
self.engine_instance_id = Some(engine_instance_id);
self
}
pub fn with_capability_id(mut self, capability_id: CapabilityId) -> Self {
self.capability_id = Some(capability_id);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
fn correlation_id(value: &str) -> CorrelationId {
CorrelationId::new(value).unwrap()
}
#[test]
fn new_context_preserves_correlation_id() {
let context = CorrelationContext::new(correlation_id("corr-1"));
assert_eq!(context.correlation_id().as_str(), "corr-1");
assert!(context.operation_id().is_none());
assert!(context.attempt_id().is_none());
assert!(context.message_id().is_none());
assert!(context.engine_id().is_none());
assert!(context.engine_instance_id().is_none());
assert!(context.capability_id().is_none());
}
#[test]
fn enrichment_preserves_correlation_id() {
let context = CorrelationContext::new(correlation_id("corr-2"))
.with_operation_id(OperationId::new("op-2").unwrap())
.with_attempt_id(AttemptId::new("attempt-2").unwrap())
.with_message_id(MessageId::new("msg-2").unwrap())
.with_engine_id(EngineId::new("engine-2").unwrap())
.with_engine_instance_id(EngineInstanceId::new("instance-2").unwrap())
.with_capability_id(CapabilityId::new("cap-2").unwrap());
assert_eq!(context.correlation_id().as_str(), "corr-2");
assert_eq!(context.operation_id().unwrap().as_str(), "op-2");
assert_eq!(context.attempt_id().unwrap().as_str(), "attempt-2");
assert_eq!(context.message_id().unwrap().as_str(), "msg-2");
assert_eq!(context.engine_id().unwrap().as_str(), "engine-2");
assert_eq!(context.engine_instance_id().unwrap().as_str(), "instance-2");
assert_eq!(context.capability_id().unwrap().as_str(), "cap-2");
}
#[test]
fn derivation_does_not_mutate_original_context() {
let original = CorrelationContext::new(correlation_id("corr-3"));
let derived = original
.clone()
.with_operation_id(OperationId::new("op-3").unwrap())
.with_attempt_id(AttemptId::new("attempt-3").unwrap());
assert!(original.operation_id().is_none());
assert!(original.attempt_id().is_none());
assert_eq!(derived.operation_id().unwrap().as_str(), "op-3");
assert_eq!(derived.attempt_id().unwrap().as_str(), "attempt-3");
assert_eq!(original.correlation_id().as_str(), "corr-3");
}
#[test]
fn from_operation_preserves_existing_core_identities() {
let operation = Operation::new(
OperationId::new("op-4").unwrap(),
CorrelationId::new("corr-4").unwrap(),
);
let context = CorrelationContext::from_operation(&operation);
assert_eq!(context.correlation_id().as_str(), "corr-4");
assert_eq!(context.operation_id().unwrap().as_str(), "op-4");
assert!(context.attempt_id().is_none());
}
#[test]
fn from_operation_context_uses_trusted_operation_context() {
let operation = Operation::new(
OperationId::new("op-5").unwrap(),
CorrelationId::new("corr-5").unwrap(),
);
let operation_context = OperationContext::new(operation);
let correlation = CorrelationContext::from_operation_context(&operation_context);
assert_eq!(correlation.correlation_id().as_str(), "corr-5");
assert_eq!(correlation.operation_id().unwrap().as_str(), "op-5");
assert!(correlation.attempt_id().is_none());
}
#[test]
fn from_operation_context_preserves_attempt_identity() {
let operation = Operation::new(
OperationId::new("op-attempt-1").unwrap(),
CorrelationId::new("corr-attempt-1").unwrap(),
);
let operation_context = OperationContext::new(operation).for_attempt(
crate::identity::NodeId::new("node-1").unwrap(),
AttemptId::new("attempt-1").unwrap(),
);
let correlation = CorrelationContext::from_operation_context(&operation_context);
assert_eq!(correlation.correlation_id().as_str(), "corr-attempt-1");
assert_eq!(correlation.operation_id().unwrap().as_str(), "op-attempt-1");
assert_eq!(correlation.attempt_id().unwrap().as_str(), "attempt-1");
}
#[test]
fn serialization_round_trip_preserves_context() {
let original = CorrelationContext::new(correlation_id("corr-6"))
.with_operation_id(OperationId::new("op-6").unwrap())
.with_attempt_id(AttemptId::new("attempt-6").unwrap())
.with_message_id(MessageId::new("msg-6").unwrap())
.with_engine_id(EngineId::new("engine-6").unwrap())
.with_engine_instance_id(EngineInstanceId::new("instance-6").unwrap())
.with_capability_id(CapabilityId::new("cap-6").unwrap());
let encoded = serde_json::to_string(&original).unwrap();
let decoded: CorrelationContext = serde_json::from_str(&encoded).unwrap();
assert_eq!(decoded, original);
}
#[test]
fn independent_contexts_remain_isolated() {
let first = CorrelationContext::new(correlation_id("corr-a"))
.with_operation_id(OperationId::new("op-a").unwrap())
.with_attempt_id(AttemptId::new("attempt-a").unwrap());
let second = CorrelationContext::new(correlation_id("corr-b"))
.with_operation_id(OperationId::new("op-b").unwrap())
.with_attempt_id(AttemptId::new("attempt-b").unwrap());
assert_eq!(first.correlation_id().as_str(), "corr-a");
assert_eq!(first.operation_id().unwrap().as_str(), "op-a");
assert_eq!(first.attempt_id().unwrap().as_str(), "attempt-a");
assert_eq!(second.correlation_id().as_str(), "corr-b");
assert_eq!(second.operation_id().unwrap().as_str(), "op-b");
assert_eq!(second.attempt_id().unwrap().as_str(), "attempt-b");
}
}