Skip to main content

relay_knowledge/application/code_repository/repository_set/membership/
mod.rs

1//! Owns repository-set creation, membership, and member filter composition.
2
3use crate::{
4    api::{
5        ApiError, ApiMetadata, CodeRepositorySetAddResponse, CodeRepositorySetCreateResponse,
6        RequestContext,
7    },
8    domain::{
9        CodeRepositorySelector, CodeRepositorySetAddMemberRequest, CodeRepositorySetCreateRequest,
10    },
11    storage::{CodeRepositorySetMemberSeed, CodeRepositorySetSeed},
12};
13
14use crate::application::service::RelayKnowledgeService;
15
16use super::super::{clock::now_millis, scope::resolve_code_ref_for_selector};
17use super::{errors::storage_api_error, status::required_set_status};
18
19impl RelayKnowledgeService {
20    /// Creates or updates a thin repository set.
21    pub async fn create_code_repository_set(
22        &self,
23        request: CodeRepositorySetCreateRequest,
24        context: RequestContext,
25    ) -> Result<CodeRepositorySetCreateResponse, ApiError> {
26        let store = self.store().await.map_err(storage_api_error)?;
27        let repository_set = store
28            .create_code_repository_set(CodeRepositorySetSeed {
29                alias: request.alias.clone(),
30                description: request.description.clone(),
31                default_ref_policy_json: request.default_ref_policy_json.clone(),
32                now_ms: now_millis(),
33            })
34            .await
35            .map_err(storage_api_error)?;
36        let graph_version = store
37            .current_graph_version()
38            .await
39            .map_err(storage_api_error)?;
40
41        Ok(CodeRepositorySetCreateResponse {
42            metadata: ApiMetadata::graph_only(&context, graph_version),
43            request,
44            repository_set,
45        })
46    }
47
48    /// Adds one already-indexed repository snapshot to a repository set.
49    pub async fn add_code_repository_set_member(
50        &self,
51        request: CodeRepositorySetAddMemberRequest,
52        context: RequestContext,
53    ) -> Result<CodeRepositorySetAddResponse, ApiError> {
54        let store = self.store().await.map_err(storage_api_error)?;
55        let repository = store
56            .code_repository_status(request.repository_alias.clone())
57            .await
58            .map_err(storage_api_error)?
59            .ok_or_else(|| {
60                ApiError::invalid_argument(format!(
61                    "code repository '{}' is not registered",
62                    request.repository_alias
63                ))
64            })?;
65        let path_filters = merged_filters(&repository.path_filters, &request.path_filters);
66        let language_filters =
67            merged_filters(&repository.language_filters, &request.language_filters);
68        let selector = CodeRepositorySelector {
69            repository: request.repository_alias.clone(),
70            ref_selector: request.ref_selector.clone(),
71            path_filters: request.path_filters.clone(),
72            language_filters: request.language_filters.clone(),
73        };
74        let resolved_commit_sha =
75            resolve_code_ref_for_selector(&repository, &selector, request.ref_selector.clone())
76                .await?;
77        let scope = store
78            .code_repository_scope_status(
79                request.repository_alias.clone(),
80                resolved_commit_sha.clone(),
81                path_filters.clone(),
82                language_filters.clone(),
83            )
84            .await
85            .map_err(storage_api_error)?
86            .ok_or_else(|| {
87                ApiError::invalid_argument(format!(
88                    "code repository '{}' has no indexed scope for ref {} and requested filters",
89                    request.repository_alias, request.ref_selector
90                ))
91            })?;
92        let source_scope = scope.last_indexed_scope_id.clone().ok_or_else(|| {
93            ApiError::invalid_argument(format!(
94                "code repository '{}' matching scope has no source scope",
95                request.repository_alias
96            ))
97        })?;
98        let scope_path_filters = scope.path_filters.clone();
99        let scope_language_filters = scope.language_filters.clone();
100        let member = store
101            .add_code_repository_set_member(CodeRepositorySetMemberSeed {
102                set_alias: request.set_alias.clone(),
103                repository_id: repository.repository_id,
104                repository_alias: request.repository_alias.clone(),
105                ref_selector: request.ref_selector.clone(),
106                resolved_commit_sha,
107                source_scope,
108                path_filters: scope_path_filters,
109                language_filters: scope_language_filters,
110                priority: request.priority,
111            })
112            .await
113            .map_err(storage_api_error)?;
114        let status = required_set_status(&store, &request.set_alias).await?;
115        let graph_version = store
116            .current_graph_version()
117            .await
118            .map_err(storage_api_error)?;
119
120        Ok(CodeRepositorySetAddResponse {
121            metadata: ApiMetadata::graph_only(&context, graph_version),
122            request,
123            member,
124            status,
125        })
126    }
127
128    pub(crate) async fn code_repository_set_member_scopes(
129        &self,
130        set_alias: String,
131    ) -> Result<Option<Vec<(String, String)>>, ApiError> {
132        let store = self.store().await.map_err(storage_api_error)?;
133        store
134            .code_repository_set_status(set_alias)
135            .await
136            .map(|status| {
137                status.map(|status| {
138                    status
139                        .members
140                        .into_iter()
141                        .map(|member| (member.member.repository_alias, member.member.source_scope))
142                        .collect()
143                })
144            })
145            .map_err(storage_api_error)
146    }
147}
148
149fn merged_filters(left: &[String], right: &[String]) -> Vec<String> {
150    let mut merged = Vec::new();
151    for value in left.iter().chain(right.iter()) {
152        if !merged.contains(value) {
153            merged.push(value.clone());
154        }
155    }
156
157    merged
158}
159
160#[cfg(test)]
161#[path = "mod_tests.rs"]
162mod tests;