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};
14use super::worktree_freshness::ensure_worktree_overlay_matches_current_worktree;
15
16impl RelayKnowledgeService {
17    /// Reads the repository-scoped software global dependency and SDK projection.
18    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}