Skip to main content

relay_knowledge/application/code_repository/
software_projection.rs

1use 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};
14
15impl RelayKnowledgeService {
16    /// Reads the repository-scoped software global dependency and SDK projection.
17    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 = indexed_commit_for_selector(
163        status,
164        &request.repository,
165        request.repository.ref_selector.clone(),
166    )
167    .await?;
168
169    Ok(request)
170}