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