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