relay_knowledge/application/code_repository/software_projection/
mod.rs1use crate::{
4 api::{ApiError, ApiMetadata, RequestContext, SoftwareGlobalResponse},
5 application::service::RelayKnowledgeService,
6 domain::{
7 CodeRepositoryStatus, FreshnessPolicy, GraphVersion, SoftwareGlobalRequest,
8 SoftwareGlobalStatus,
9 },
10};
11
12use super::{
13 errors::storage_api_error,
14 repository::{ensure_worktree_overlay_matches_current_worktree, required_code_repository},
15 scope::{
16 active_index_matches_request, indexed_commit_for_selector,
17 latest_compatible_code_scope_status, resolved_code_scope_status,
18 },
19};
20
21impl RelayKnowledgeService {
22 pub async fn software_global_projection(
24 &self,
25 request: SoftwareGlobalRequest,
26 context: RequestContext,
27 ) -> Result<SoftwareGlobalResponse, ApiError> {
28 let store = self.store().await.map_err(storage_api_error)?;
29 let status =
30 required_code_repository(store.as_ref(), &request.repository.repository).await?;
31 if request.freshness_policy == FreshnessPolicy::GraphOnly {
32 let graph_version = store
33 .current_graph_version()
34 .await
35 .map_err(storage_api_error)?;
36 return Ok(SoftwareGlobalResponse {
37 metadata: ApiMetadata::graph_only(&context, graph_version),
38 scope: crate::api::CodeRepositoryScopeMetadata::from_status(
39 &status,
40 &request.repository,
41 request.repository.ref_selector.clone(),
42 ),
43 request,
44 status: SoftwareGlobalStatus {
45 repository_id: status.repository_id.clone(),
46 source_scope: status
47 .last_indexed_scope_id
48 .clone()
49 .unwrap_or_else(|| "unscoped".to_owned()),
50 projected_graph_version: GraphVersion::ZERO,
51 stale: true,
52 component_count: 0,
53 sdk_usage_count: 0,
54 file_count: 0,
55 topic_count: 0,
56 relationship_count: 0,
57 build_target_count: 0,
58 iac_resource_count: 0,
59 design_element_count: 0,
60 last_error: Some("graph_only freshness policy selected".to_owned()),
61 },
62 components: Vec::new(),
63 dependency_usages: Vec::new(),
64 sdk_usages: Vec::new(),
65 files: Vec::new(),
66 topics: Vec::new(),
67 relationships: Vec::new(),
68 build_targets: Vec::new(),
69 iac_resources: Vec::new(),
70 design_elements: Vec::new(),
71 });
72 }
73
74 let requested_ref = request.repository.ref_selector.clone();
75 let mut request = software_request_at_indexed_ref(request, &status).await?;
76 if requested_ref == "worktree" {
77 ensure_worktree_overlay_matches_current_worktree(&store, &status, &request.repository)
78 .await?;
79 }
80 let mut served_stale_scope = false;
81 let scoped_status =
82 match resolved_code_scope_status(&store, &status, &request.repository).await {
83 Ok(scoped_status) => scoped_status,
84 Err(error) if request.freshness_policy == FreshnessPolicy::AllowStale => {
85 if !active_index_matches_request(&store, &status, &request.repository).await? {
86 return Err(error);
87 }
88 let Some(stale_status) =
89 latest_compatible_code_scope_status(&store, &request.repository).await?
90 else {
91 return Err(error);
92 };
93 let Some(last_indexed_commit) = stale_status.last_indexed_commit.clone() else {
94 return Err(error);
95 };
96 request.repository.ref_selector = last_indexed_commit;
97 served_stale_scope = true;
98 stale_status
99 }
100 Err(error) => return Err(error),
101 };
102 if let Some(last_indexed_commit) = scoped_status.last_indexed_commit.clone() {
103 request.repository.ref_selector = last_indexed_commit;
104 }
105 request.repository.repository = status.repository_id.clone();
106
107 let source_scope = scoped_status.last_indexed_scope_id.clone().ok_or_else(|| {
108 ApiError::invalid_argument(format!(
109 "code repository '{}' does not have an indexed source scope",
110 scoped_status.alias
111 ))
112 })?;
113 let projection = store
114 .software_global_projection_for_scope(source_scope, request.clone())
115 .await
116 .map_err(storage_api_error)?;
117 if request.freshness_policy == FreshnessPolicy::WaitUntilFresh
118 && (projection.status.stale || scoped_status.stale)
119 {
120 return Err(ApiError::invalid_argument(format!(
121 "software global projection for repository '{}' scope '{}' is stale; run repo index before querying with wait_until_fresh",
122 status.alias, projection.status.source_scope
123 )));
124 }
125 let graph_version = store
126 .current_graph_version()
127 .await
128 .map_err(storage_api_error)?;
129 let mut metadata = ApiMetadata::graph_only(&context, graph_version);
130 if projection.status.stale || scoped_status.stale || served_stale_scope {
131 metadata.stale = true;
132 }
133
134 let mut scope_selector = request.repository.clone();
135 scope_selector.path_filters = scoped_status.path_filters.clone();
136 scope_selector.language_filters = scoped_status.language_filters.clone();
137 let mut scope = crate::api::CodeRepositoryScopeMetadata::from_status(
138 &scoped_status,
139 &scope_selector,
140 requested_ref,
141 );
142 if served_stale_scope {
143 scope.stale = true;
144 }
145
146 let mut status = projection.status;
147 if scoped_status.stale || served_stale_scope {
148 status.stale = true;
149 }
150
151 Ok(SoftwareGlobalResponse {
152 metadata,
153 scope,
154 request,
155 status,
156 components: projection.components,
157 dependency_usages: projection.dependency_usages,
158 sdk_usages: projection.sdk_usages,
159 files: projection.files,
160 topics: projection.topics,
161 relationships: projection.relationships,
162 build_targets: projection.build_targets,
163 iac_resources: projection.iac_resources,
164 design_elements: projection.design_elements,
165 })
166 }
167}
168
169async fn software_request_at_indexed_ref(
170 mut request: SoftwareGlobalRequest,
171 status: &CodeRepositoryStatus,
172) -> Result<SoftwareGlobalRequest, ApiError> {
173 request.repository.ref_selector = indexed_commit_for_selector(
174 status,
175 &request.repository,
176 request.repository.ref_selector.clone(),
177 )
178 .await?;
179
180 Ok(request)
181}