saddle_core/request_context/unified/
select.rs1use 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 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}