relay_knowledge/application/code_repository/repository_set/membership/
mod.rs1use 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 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 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;