Skip to main content

saddle_core/request_context/unified/
select.rs

1//! Borrowed selectors over the same event projection; no intermediate JSON tree.
2use super::*;
3use crate::{context_json::RedactedSubtree, json_pointer::{JsonPointer, PointerSegment}};
4pub struct SelectedContextValue<'a>(Value<'a>);
5enum Value<'a> {
6    Full(UnifiedContext<'a>), Json(JsonSubtree<'a>), Protected(RedactedSubtree<'a>), Text(&'a str),
7    Id(u64), Integer(i64), Attempt(u32), Identity(ContextIdentity), Label(ContextLabel), Phase(RequestViewPhase), Span(SpanProjection), Operation(RegisteredContextOperation),
8    Application(Application), Trace(Trace<'a>), Request(Request<'a>), Interface(Interface<'a>), Input(Input<'a>),
9    Call(Call<'a>), Execution(Execution<'a>), Auth(Auth<'a>), Meta(Meta<'a>), States(FieldStates<'a>), Redactions(Redactions<'a>), Empty,
10}
11impl Serialize for SelectedContextValue<'_> {
12    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
13        macro_rules! serialize { ($($variant:ident),*) => { match &self.0 {
14            $(Value::$variant(v) => v.serialize(s),)*
15            Value::Id(v) => StringId(*v).serialize(s), Value::Empty => Empty.serialize(s),
16        } }; }
17        serialize!(Full, Json, Protected, Text, Integer, Attempt, Identity, Label, Phase, Span, Operation,
18            Application, Trace, Request, Interface, Input, Call, Execution, Auth, Meta, States, Redactions)
19    }
20}
21fn split(path: &str) -> Option<(PointerSegment<'_>, &str)> {
22    let first = JsonPointer::parse(path).ok()?.segments().next()?;
23    let raw = &path[1..]; Some((first, raw.find('/').map_or("", |at| &raw[at..])))
24}
25fn fact<'a, T>(value: ContextFact<T>, map: impl FnOnce(T) -> Value<'a>) -> Option<Value<'a>> {
26    match value { ContextFact::Present(value) => Some(map(value)), _ => None }
27}
28impl UnifiedContext<'_> {
29    /// The pointer is relative to context. Missing differs from a present null.
30    /// The callback cannot keep any value borrowed from a managed source.
31    ///
32    /// ```compile_fail
33    /// use saddle_core::request_context::{UnifiedContext, SelectedContextValue};
34    /// fn escape<'a>(context: &UnifiedContext<'a>) -> Option<SelectedContextValue<'a>> {
35    ///     context.with_selected("/business", |value| value).unwrap()
36    /// }
37    /// ```
38    pub fn with_selected<R>(&self, path: &str, read: impl FnOnce(Option<SelectedContextValue<'_>>) -> R)
39        -> Result<R, crate::json_pointer::InvalidJsonPointer> {
40        JsonPointer::parse(path)?;
41        if path.is_empty() { return Ok(read(Some(SelectedContextValue(Value::Full(UnifiedContext(self.0)))))); }
42        let mut read = Some(read); let mut result = None;
43        if let Some((group, rest)) = split(path) {
44            if group.matches("business") {
45                if let Some(source) = &self.0.inner.business {
46                    source.with_tree_and_secrets(&mut |root, secrets| {
47                        let selected = RedactedSubtree::new(root, secrets, "/business").pointer(rest).ok().flatten();
48                        result = Some(read.take().unwrap()(selected.map(|v| SelectedContextValue(Value::Protected(v)))));
49                    });
50                } else { result = Some(read.take().unwrap()(rest.is_empty().then_some(SelectedContextValue(Value::Empty)))); }
51                return Ok(result.expect("business source provides exactly one tree"));
52            }
53        }
54        if let Some(ingress) = &self.0.inner.ingress {
55            ingress.source.with_tree(&mut |tree| {
56                result = Some(read.take().unwrap()(self.selected(path, Some(tree)).map(SelectedContextValue)));
57            });
58        } else { result = Some(read.take().unwrap()(self.selected(path, None).map(SelectedContextValue))); }
59        Ok(result.expect("source provides exactly one immutable tree"))
60    }
61    fn selected<'a>(&'a self, path: &str, tree: Option<JsonSubtree<'a>>) -> Option<Value<'a>> {
62        if path.is_empty() { return Some(Value::Full(UnifiedContext(self.0))); }
63        let (group, rest) = split(path)?;
64        let raw = |path| tree.and_then(|tree| tree.pointer(path).ok().flatten());
65        let subtree = |source: Option<JsonSubtree<'a>>| source.and_then(|source| source.pointer(rest).ok().flatten()).map(Value::Json);
66        let leaf = |name: &str| split(rest).is_some_and(|(field, tail)| tail.is_empty() && field.matches(name));
67        let view = self.0; let snap = view.diagnostic_snapshot();
68        if group.matches("schemaVersion") { return rest.is_empty().then_some(Value::Text("1.0")); }
69        if group.matches("applicationInfo") {
70            if rest.is_empty() { return Some(Value::Application(Application { application: view.inner.root.application })); }
71            if leaf("application") { return Some(Value::Label(view.inner.root.application)); }
72        }
73        if group.matches("traceInfo") {
74            let source = raw("/profuseGwContext/traceInfo");
75            if rest.is_empty() { return Some(Value::Trace(Trace { view, source })); }
76            if leaf("traceId") { return fact(snap.trace_id, Value::Identity); }
77            if leaf("rpcId") { return fact(snap.rpc_id, Value::Identity); }
78            if split(rest).is_some_and(|(field, _)| field.matches("traceId") || field.matches("rpcId")) { return None; }
79            return subtree(source);
80        }
81        if group.matches("ldcInfo") { return subtree(raw("/profuseGwContext/ldcInfo")); }
82        if group.matches("profuseContext") { return subtree(raw("/profuseContext")); }
83        if group.matches("inputInfo") {
84            let source = raw("/requestData");
85            if rest.is_empty() { return Some(Value::Input(Input { request_data: source })); }
86            let (field, tail) = split(rest)?;
87            if field.matches("requestData") { return source.and_then(|s| s.pointer(tail).ok().flatten()).map(Value::Json); }
88        }
89        if group.matches("requestInfo") {
90            if rest.is_empty() { return Some(Value::Request(Request(view))); }
91            if leaf("localRequestId") { return Some(Value::Id(view.inner.root.local)); }
92            if leaf("requestId") { return fact(snap.request, Value::Identity); }
93            if leaf("callId") { return view.inner.ingress.as_ref().map(|i| Value::Identity(i.call_id)); }
94            if leaf("deadlineUnixMs") { return view.inner.ingress.as_ref().map(|i| Value::Integer(i.deadline_unix_ms)); }
95        }
96        if group.matches("interfaceInfo") {
97            if rest.is_empty() { return Some(Value::Interface(Interface(view))); }
98            if leaf("entry") { return Some(Value::Text("profusegw")); }
99            if leaf("interfaceId") { return view.identity().map(|i| Value::Label(i.route)); }
100        }
101        if group.matches("executionInfo") {
102            if rest.is_empty() { return Some(Value::Execution(Execution(view))); }
103            if leaf("taskId") { return fact(snap.task, Value::Id); }
104            if leaf("scopeId") { return fact(snap.scope, |scope| Value::Id(scope.as_u64())); }
105            if leaf("dbOperation") { return fact(snap.db_operation, Value::Operation); }
106            if leaf("phase") { return Some(Value::Phase(view.inner.facts.phase)); }
107        }
108        if group.matches("callInfo") {
109            if rest.is_empty() { return Some(Value::Call(Call(view))); }
110            let child = view.inner.child.as_ref()?;
111            if leaf("spanId") { return Some(Value::Span(SpanProjection(child.span))); }
112            if leaf("attempt") { return Some(Value::Attempt(child.attempt)); }
113            if leaf("service") { return Some(Value::Label(child.service)); }
114            if leaf("method") { return Some(Value::Label(child.operation)); }
115        }
116        if group.matches("authInfo") {
117            let subject = raw("/profuseGwContext/userInfo/userId");
118            if rest.is_empty() { return Some(Value::Auth(Auth { state: "unavailable", subject })); }
119            if leaf("state") { return Some(Value::Text("unavailable")); }
120            if leaf("subjectId") { return subject.map(Value::Json); }
121        }
122        if group.matches("_meta") {
123            if rest.is_empty() { return Some(Value::Meta(Meta { view, tree })); }
124            if leaf("revision") { return Some(Value::Id(view.inner.revision)); }
125            if leaf("scopeId") { return fact(snap.scope, |scope| Value::Id(scope.as_u64())); }
126            let (field, tail) = split(rest)?;
127            if field.matches("redactions") {
128                if tail.is_empty() { return Some(Value::Redactions(Redactions(view))); }
129                let (key, remaining) = split(tail)?;
130                let mut marked = false;
131                if remaining.is_empty() {
132                    if let Some(source) = &view.inner.business {
133                        source.with_secrets(&mut |paths| marked = paths.iter().any(|path| key.matches(path)));
134                    }
135                }
136                if marked { return Some(Value::Text("secret")); }
137            }
138            if field.matches("fieldStates") {
139                if tail.is_empty() { return Some(Value::States(FieldStates { view, tree })); }
140                let (key, remaining) = split(tail)?;
141                if !remaining.is_empty() { return None; }
142                let mut buffer = [0; 1024];
143                return state_at(view, tree, key.decode_into(&mut buffer).ok()?).map(Value::Text);
144            }
145        }
146        None
147    }
148}
149pub(super) fn state_at(view: &RequestExecutionView, tree: Option<JsonSubtree<'_>>, path: &str) -> Option<&'static str> {
150    let snap = view.diagnostic_snapshot();
151    match path {
152        "/traceInfo/traceId" => state(snap.trace_id), "/traceInfo/rpcId" => state(snap.rpc_id), "/requestInfo/requestId" => state(snap.request),
153        "/executionInfo/taskId" => state(snap.task), "/executionInfo/scopeId" => state(snap.scope), "/executionInfo/dbOperation" => state(snap.db_operation),
154        "/interfaceInfo/module" | "/interfaceInfo/service" | "/interfaceInfo/operation" => Some("not_applicable"),
155        "/interfaceInfo/interfaceId" => view.identity().is_none().then_some("not_established"),
156        "/requestInfo/callId" | "/requestInfo/deadlineUnixMs" => view.inner.ingress.is_none().then_some(if view.identity().is_some() { "unavailable" } else { "not_established" }),
157        "/inputInfo/requestData" | "/ldcInfo" | "/authInfo/subjectId" => view.inner.ingress.is_none().then_some("unavailable"),
158        "/profuseContext" => tree.and_then(|tree| tree.pointer("/profuseContext").ok().flatten()).is_none().then_some(if tree.is_some() { "not_established" } else { "unavailable" }),
159        "/callInfo/spanId" | "/callInfo/attempt" | "/callInfo/service" | "/callInfo/method" => view.inner.child.is_none().then_some("not_applicable"),
160        "/callInfo/callId" | "/callInfo/parentCallId" | "/callInfo/kind" | "/callInfo/dependencyAlias" | "/callInfo/contractSha256" => Some(if view.inner.child.is_some() { "unavailable" } else { "not_applicable" }),
161        _ => None,
162    }
163}