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