use crate::{
config::snapshot::ConfigurationSnapshot,
operation::OperationContext,
provenance::ProvenanceContext,
runtime::{CancellationToken, Deadline, EngineContext},
security::SecurityContext,
};
#[derive(Clone, Debug)]
pub struct StreamContext {
engine: EngineContext,
}
impl StreamContext {
pub fn from_engine_context(context: &EngineContext) -> Self {
Self {
engine: context.child(),
}
}
pub fn engine_context(&self) -> &EngineContext {
&self.engine
}
pub fn operation(&self) -> &OperationContext {
self.engine.operation()
}
pub fn cancellation(&self) -> &CancellationToken {
self.engine.cancellation()
}
pub fn deadline(&self) -> Option<Deadline> {
self.engine.deadline()
}
pub fn security(&self) -> Option<&SecurityContext> {
self.engine.security()
}
pub fn provenance(&self) -> &ProvenanceContext {
self.engine.provenance()
}
pub fn configuration(&self) -> Option<&ConfigurationSnapshot> {
self.engine.configuration()
}
pub fn is_cancelled(&self) -> bool {
self.engine.cancellation().is_cancelled()
}
pub fn is_expired(&self) -> bool {
self.engine.is_expired()
}
}
#[cfg(test)]
mod tests {
use std::{sync::Arc, time::Duration};
use crate::{
config::{
resolution::ConfigurationResolver,
snapshot::{ConfigurationSnapshot, ConfigurationSnapshotId},
validation::{ConfigurationValidator, ConfigurationValue, ParsedConfiguration},
},
identity::{CorrelationId, OperationId},
operation::{Deadline, Operation, OperationContext},
provenance::ProvenanceContext,
runtime::EngineContext,
security::{PrincipalId, PrincipalIdentity, PrincipalType, SecurityContext},
};
use super::StreamContext;
fn sample_operation() -> Operation {
Operation::new(
OperationId::new("stream-op-1").unwrap(),
CorrelationId::new("stream-corr-1").unwrap(),
)
}
fn sample_context() -> EngineContext {
EngineContext::new(OperationContext::new(sample_operation()))
}
fn sample_security() -> SecurityContext {
let principal = PrincipalIdentity::new(
PrincipalType::User,
PrincipalId::new("stream-user-1").unwrap(),
);
SecurityContext::new(principal, None)
}
fn sample_configuration() -> Arc<ConfigurationSnapshot> {
let mut parsed = ParsedConfiguration::empty();
parsed.insert("stream.test", ConfigurationValue::Boolean(true));
let validated = ConfigurationValidator::new().validate(parsed).unwrap();
let resolved = ConfigurationResolver::new().resolve(&validated).unwrap();
Arc::new(ConfigurationSnapshot::new(
ConfigurationSnapshotId::new(1),
resolved,
))
}
fn sample_provenance() -> ProvenanceContext {
ProvenanceContext::new().with_attribute("source", "stream-test")
}
#[test]
fn derives_from_engine_context_without_changing_operation_identity() {
let engine = sample_context();
let stream = StreamContext::from_engine_context(&engine);
assert_eq!(stream.operation(), engine.operation());
}
#[test]
fn creates_child_cancellation_scope() {
let engine = sample_context();
let stream = StreamContext::from_engine_context(&engine);
assert!(!engine.cancellation().is_cancelled());
assert!(!stream.is_cancelled());
stream.cancellation().cancel();
assert!(stream.is_cancelled());
assert!(!engine.cancellation().is_cancelled());
}
#[test]
fn parent_cancellation_reaches_stream_context() {
let engine = sample_context();
let stream = StreamContext::from_engine_context(&engine);
engine.cancellation().cancel();
assert!(stream.is_cancelled());
}
#[test]
fn stream_cancellation_does_not_affect_sibling_contexts() {
let engine = sample_context();
let first = StreamContext::from_engine_context(&engine);
let second = StreamContext::from_engine_context(&engine);
first.cancellation().cancel();
assert!(first.is_cancelled());
assert!(!second.is_cancelled());
assert!(!engine.cancellation().is_cancelled());
}
#[test]
fn preserves_deadline() {
let deadline = Deadline::from_now(Duration::from_secs(30)).unwrap();
let engine = sample_context().with_deadline(deadline);
let stream = StreamContext::from_engine_context(&engine);
assert_eq!(stream.deadline(), Some(deadline));
assert!(!stream.is_expired());
}
#[test]
fn preserves_security_context() {
let security = sample_security();
let engine = sample_context().with_security(security.clone());
let stream = StreamContext::from_engine_context(&engine);
assert_eq!(stream.security(), Some(&security));
}
#[test]
fn preserves_provenance_context() {
let provenance = sample_provenance();
let engine = sample_context().with_provenance(provenance.clone());
let stream = StreamContext::from_engine_context(&engine);
assert_eq!(stream.provenance(), &provenance);
}
#[test]
fn preserves_configuration_snapshot() {
let configuration = sample_configuration();
let engine = sample_context().with_configuration(configuration.clone());
let stream = StreamContext::from_engine_context(&engine);
assert_eq!(stream.configuration(), Some(configuration.as_ref()));
}
#[test]
fn exposes_the_established_engine_context() {
let engine = sample_context();
let stream = StreamContext::from_engine_context(&engine);
assert_eq!(stream.engine_context().operation(), engine.operation());
}
#[test]
fn clone_shares_the_same_stream_cancellation_scope() {
let stream = StreamContext::from_engine_context(&sample_context());
let clone = stream.clone();
stream.cancellation().cancel();
assert!(clone.is_cancelled());
}
#[test]
fn missing_optional_contexts_remain_absent() {
let stream = StreamContext::from_engine_context(&sample_context());
assert!(stream.deadline().is_none());
assert!(stream.security().is_none());
assert!(stream.configuration().is_none());
}
#[test]
fn correlation_identity_is_preserved_through_operation_context() {
let stream = StreamContext::from_engine_context(&sample_context());
assert_eq!(
stream.operation().operation.correlation_id,
CorrelationId::new("stream-corr-1").unwrap()
);
}
#[test]
fn stream_context_does_not_create_a_new_operation_identity() {
let operation_id = sample_operation().id;
let stream = StreamContext::from_engine_context(&sample_context());
assert_eq!(stream.operation().operation.id, operation_id);
}
}