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