dcontext_dactor/propagation.rs
1use dactor::ActorContext;
2use dcontext::ContextFutureExt;
3use dcontext::ContextSnapshot;
4
5use crate::header::{ContextHeader, ContextSnapshotHeader};
6
7/// Extract the propagated context snapshot from actor message headers.
8///
9/// Checks for a [`ContextSnapshotHeader`] (local hop) first, then falls
10/// back to [`ContextHeader`] (remote wire bytes). Returns `None` if no
11/// propagated context is present.
12///
13/// Useful for advanced scenarios such as spawning sub-tasks that need the
14/// propagated context, or inspecting context values without relying on
15/// the task-local scope.
16pub fn extract_context(ctx: &ActorContext) -> Option<ContextSnapshot> {
17 // Prefer local snapshot (preserves local-only values).
18 if let Some(h) = ctx.headers.get::<ContextSnapshotHeader>() {
19 return Some(h.snapshot.clone());
20 }
21
22 // Fall back to wire bytes.
23 if let Some(h) = ctx.headers.get::<ContextHeader>() {
24 return bytes_to_snapshot(&h.bytes);
25 }
26
27 None
28}
29
30/// Run an async handler body with the propagated dcontext from actor headers.
31///
32/// **Deprecated since dactor 0.3**: The [`ContextInboundInterceptor`](crate::ContextInboundInterceptor)
33/// now implements `wrap_handler` which automatically restores context into the
34/// handler's async task-local scope. This function is no longer needed when
35/// using the interceptor pipeline.
36///
37/// Retained for backward compatibility and for use cases outside the
38/// interceptor pipeline (e.g., manual context restoration in tests or
39/// one-off async blocks).
40///
41/// If no propagated context is found in the headers, the future runs without
42/// any dcontext scope (a no-op passthrough).
43#[deprecated(
44 since = "0.2.0",
45 note = "ContextInboundInterceptor now restores context automatically via wrap_handler. \
46 Use this only for manual context restoration outside the interceptor pipeline."
47)]
48pub async fn with_propagated_context<F, R>(ctx: &ActorContext, f: F) -> R
49where
50 F: std::future::Future<Output = R>,
51{
52 match extract_context(ctx) {
53 Some(snap) => f.attach(snap).await,
54 None => f.await,
55 }
56}
57
58/// Convert serialized wire bytes into a `ContextSnapshot`.
59///
60/// Temporarily deserializes into sync (thread-local) storage, captures a
61/// snapshot, then reverts. This avoids interfering with any active task-local
62/// context.
63pub(crate) fn bytes_to_snapshot(bytes: &[u8]) -> Option<ContextSnapshot> {
64 dcontext::ContextSnapshot::deserialize(bytes).ok()
65}