1use kmp_domain::{
2 GraphNeighborhoodReader, KmpBundle, NeighborhoodRequest, NodeDetailReader, SnapshotSaveOptions,
3 SnapshotStore,
4};
5
6use crate::ApplicationError;
7pub use crate::queries::render_graph_bundle::RenderedContext;
8use crate::queries::{
9 ContextRenderOptions, QueryApplicationService, QueryTimingBreakdown, RehydrateSessionUseCase,
10 clamp_native_graph_traversal_depth, render_graph_bundle_with_options,
11};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct GetContextQuery {
15 pub root_node_id: String,
16 pub role: String,
17 pub depth: u32,
18 pub requested_scopes: Vec<String>,
19 pub render_options: ContextRenderOptions,
20}
21
22#[derive(Debug, Clone, PartialEq)]
23pub struct GetContextResult {
24 pub bundle: KmpBundle,
25 pub rendered: RenderedContext,
26 pub requested_scopes: Vec<String>,
27 pub served_at: std::time::SystemTime,
28 pub timing: Option<QueryTimingBreakdown>,
29}
30
31#[derive(Debug)]
32pub struct GetContextUseCase<G, D, S> {
33 rehydrate_session: RehydrateSessionUseCase<G, D, S>,
34}
35
36impl<G, D, S> GetContextUseCase<G, D, S>
37where
38 G: GraphNeighborhoodReader + Send + Sync,
39 D: NodeDetailReader + Send + Sync,
40 S: SnapshotStore + Send + Sync,
41{
42 pub fn new(rehydrate_session: RehydrateSessionUseCase<G, D, S>) -> Self {
43 Self { rehydrate_session }
44 }
45
46 pub async fn execute(
47 &self,
48 root_node_id: &str,
49 role: &str,
50 depth: u32,
51 requested_scopes: &[String],
52 render_options: &ContextRenderOptions,
53 ) -> Result<GetContextResult, ApplicationError> {
54 let (bundle, timing) = self
58 .rehydrate_session
59 .execute_for(
60 &NeighborhoodRequest::new(root_node_id, clamp_native_graph_traversal_depth(depth))
61 .with_scopes(requested_scopes.to_vec()),
62 role,
63 false,
64 SnapshotSaveOptions::default(),
65 )
66 .await?;
67 let rendered = render_graph_bundle_with_options(&bundle, render_options);
68
69 Ok(GetContextResult {
70 bundle,
71 rendered,
72 requested_scopes: requested_scopes.to_vec(),
73 served_at: std::time::SystemTime::now(),
74 timing: Some(timing),
75 })
76 }
77}
78
79impl<G, D, S> QueryApplicationService<G, D, S>
80where
81 G: GraphNeighborhoodReader + Send + Sync,
82 D: NodeDetailReader + Send + Sync,
83 S: SnapshotStore + Send + Sync,
84{
85 pub(crate) async fn read_context_bundle(
88 &self,
89 request: &NeighborhoodRequest,
90 role: &str,
91 ) -> Result<KmpBundle, ApplicationError> {
92 let rehydrate = RehydrateSessionUseCase::new(
93 std::sync::Arc::clone(&self.graph_reader),
94 std::sync::Arc::clone(&self.detail_reader),
95 std::sync::Arc::clone(&self.snapshot_store),
96 self.generator_version,
97 );
98 let (bundle, _) = rehydrate
99 .execute_for(request, role, false, SnapshotSaveOptions::default())
100 .await?;
101 Ok(bundle)
102 }
103
104 pub(crate) async fn read_context_catalogue(
107 &self,
108 request: &NeighborhoodRequest,
109 role: &str,
110 ) -> Result<KmpBundle, ApplicationError> {
111 Ok(self
112 .read_context_catalogue_with_timing(request, role)
113 .await?
114 .0)
115 }
116
117 pub(crate) async fn read_context_catalogue_with_timing(
118 &self,
119 request: &NeighborhoodRequest,
120 role: &str,
121 ) -> Result<(KmpBundle, QueryTimingBreakdown), ApplicationError> {
122 let reader = crate::queries::NodeCentricProjectionReader::new(
123 std::sync::Arc::clone(&self.graph_reader),
124 std::sync::Arc::clone(&self.detail_reader),
125 );
126 let catalogue = NeighborhoodRequest::new(request.root_node_id(), request.depth());
130 let (bundle, timing) = reader
131 .load_catalogue_for(&catalogue, role, self.generator_version)
132 .await?;
133 bundle.map(|bundle| (bundle, timing)).ok_or_else(|| {
134 ApplicationError::NotFound(format!("node '{}' not found", request.root_node_id()))
135 })
136 }
137
138 pub(crate) async fn materialize_selected_details(
139 &self,
140 bundle: KmpBundle,
141 selected: &std::collections::BTreeSet<String>,
142 ) -> Result<KmpBundle, ApplicationError> {
143 let ids = std::iter::once(bundle.root_node())
144 .chain(bundle.neighbor_nodes())
145 .map(|node| node.node_id())
146 .filter(|id| selected.contains(*id))
147 .map(str::to_string)
148 .collect::<Vec<_>>();
149 let details = if ids.is_empty() {
150 Vec::new()
151 } else {
152 self.detail_reader
153 .load_node_details_batch(ids)
154 .await?
155 .into_iter()
156 .flatten()
157 .map(|detail| kmp_domain::BundleNodeDetail::from_projection(&detail))
158 .collect()
159 };
160 Ok(bundle.with_node_details(details)?)
161 }
162
163 pub async fn get_context(
164 &self,
165 query: GetContextQuery,
166 ) -> Result<GetContextResult, ApplicationError> {
167 let rehydrate = RehydrateSessionUseCase::new(
168 std::sync::Arc::clone(&self.graph_reader),
169 std::sync::Arc::clone(&self.detail_reader),
170 std::sync::Arc::clone(&self.snapshot_store),
171 self.generator_version,
172 );
173
174 GetContextUseCase::new(rehydrate)
175 .execute(
176 &query.root_node_id,
177 &query.role,
178 query.depth,
179 &query.requested_scopes,
180 &query.render_options,
181 )
182 .await
183 }
184}