1use async_trait::async_trait;
12use http::{Method, StatusCode};
13use std::sync::Arc;
14use tokio::sync::Mutex as AsyncMutex;
15
16use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
17use fakecloud_persistence::SnapshotStore;
18
19use crate::state::SharedCodeArtifactState;
20
21pub const CODEARTIFACT_ACTIONS: &[&str] = &[
22 "AssociateExternalConnection",
23 "CopyPackageVersions",
24 "CreateDomain",
25 "CreatePackageGroup",
26 "CreateRepository",
27 "DeleteDomain",
28 "DeleteDomainPermissionsPolicy",
29 "DeletePackage",
30 "DeletePackageGroup",
31 "DeletePackageVersions",
32 "DeleteRepository",
33 "DeleteRepositoryPermissionsPolicy",
34 "DescribeDomain",
35 "DescribePackage",
36 "DescribePackageGroup",
37 "DescribePackageVersion",
38 "DescribeRepository",
39 "DisassociateExternalConnection",
40 "DisposePackageVersions",
41 "GetAssociatedPackageGroup",
42 "GetAuthorizationToken",
43 "GetDomainPermissionsPolicy",
44 "GetPackageVersionAsset",
45 "GetPackageVersionReadme",
46 "GetRepositoryEndpoint",
47 "GetRepositoryPermissionsPolicy",
48 "ListAllowedRepositoriesForGroup",
49 "ListAssociatedPackages",
50 "ListDomains",
51 "ListPackageGroups",
52 "ListPackageVersionAssets",
53 "ListPackageVersionDependencies",
54 "ListPackageVersions",
55 "ListPackages",
56 "ListRepositories",
57 "ListRepositoriesInDomain",
58 "ListSubPackageGroups",
59 "ListTagsForResource",
60 "PublishPackageVersion",
61 "PutDomainPermissionsPolicy",
62 "PutPackageOriginConfiguration",
63 "PutRepositoryPermissionsPolicy",
64 "TagResource",
65 "UntagResource",
66 "UpdatePackageGroup",
67 "UpdatePackageGroupOriginConfiguration",
68 "UpdatePackageVersionsStatus",
69 "UpdateRepository",
70];
71
72const MUTATING: &[&str] = &[
74 "AssociateExternalConnection",
75 "CopyPackageVersions",
76 "CreateDomain",
77 "CreatePackageGroup",
78 "CreateRepository",
79 "DeleteDomain",
80 "DeleteDomainPermissionsPolicy",
81 "DeletePackage",
82 "DeletePackageGroup",
83 "DeletePackageVersions",
84 "DeleteRepository",
85 "DeleteRepositoryPermissionsPolicy",
86 "DisassociateExternalConnection",
87 "DisposePackageVersions",
88 "PublishPackageVersion",
89 "PutDomainPermissionsPolicy",
90 "PutPackageOriginConfiguration",
91 "PutRepositoryPermissionsPolicy",
92 "TagResource",
93 "UntagResource",
94 "UpdatePackageGroup",
95 "UpdatePackageGroupOriginConfiguration",
96 "UpdatePackageVersionsStatus",
97 "UpdateRepository",
98];
99
100pub struct CodeArtifactService {
101 state: SharedCodeArtifactState,
102 snapshot_store: Option<Arc<dyn SnapshotStore>>,
103 snapshot_lock: Arc<AsyncMutex<()>>,
104}
105
106impl CodeArtifactService {
107 pub fn new(state: SharedCodeArtifactState) -> Self {
108 Self {
109 state,
110 snapshot_store: None,
111 snapshot_lock: Arc::new(AsyncMutex::new(())),
112 }
113 }
114
115 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
116 self.snapshot_store = Some(store);
117 self
118 }
119
120 async fn save(&self) {
121 crate::persistence::save_snapshot(
122 &self.state,
123 self.snapshot_store.clone(),
124 &self.snapshot_lock,
125 )
126 .await;
127 }
128
129 pub fn snapshot_hook(&self) -> Option<fakecloud_persistence::SnapshotHook> {
131 let store = self.snapshot_store.clone()?;
132 let state = self.state.clone();
133 let lock = self.snapshot_lock.clone();
134 Some(Arc::new(move || {
135 let state = state.clone();
136 let store = store.clone();
137 let lock = lock.clone();
138 Box::pin(async move {
139 crate::persistence::save_snapshot(&state, Some(store), &lock).await;
140 })
141 }))
142 }
143
144 fn resolve_action(req: &AwsRequest) -> Option<&'static str> {
147 let raw = req.raw_path.split('?').next().unwrap_or(&req.raw_path);
148 let trimmed = raw.strip_prefix('/').unwrap_or(raw);
149 let trimmed = trimmed.strip_suffix('/').unwrap_or(trimmed);
154 let segs: Vec<&str> = if trimmed.is_empty() {
155 Vec::new()
156 } else {
157 trimmed.split('/').collect()
158 };
159 let m = &req.method;
160 let post = m == Method::POST;
161 let get = m == Method::GET;
162 let del = m == Method::DELETE;
163 let put = m == Method::PUT;
164 let action = match segs.as_slice() {
165 ["v1", "domain"] if post => "CreateDomain",
166 ["v1", "domain"] if get => "DescribeDomain",
167 ["v1", "domain"] if del => "DeleteDomain",
168 ["v1", "domains"] if post => "ListDomains",
169 ["v1", "domain", "permissions", "policy"] if put => "PutDomainPermissionsPolicy",
170 ["v1", "domain", "permissions", "policy"] if get => "GetDomainPermissionsPolicy",
171 ["v1", "domain", "permissions", "policy"] if del => "DeleteDomainPermissionsPolicy",
172 ["v1", "domain", "repositories"] if post => "ListRepositoriesInDomain",
173 ["v1", "repository"] if post => "CreateRepository",
174 ["v1", "repository"] if get => "DescribeRepository",
175 ["v1", "repository"] if del => "DeleteRepository",
176 ["v1", "repository"] if put => "UpdateRepository",
177 ["v1", "repositories"] if post => "ListRepositories",
178 ["v1", "repository", "external-connection"] if post => "AssociateExternalConnection",
179 ["v1", "repository", "external-connection"] if del => "DisassociateExternalConnection",
180 ["v1", "repository", "permissions", "policy"] if get => {
181 "GetRepositoryPermissionsPolicy"
182 }
183 ["v1", "repository", "permissions", "policy"] if put => {
184 "PutRepositoryPermissionsPolicy"
185 }
186 ["v1", "repository", "permissions", "policies"] if del => {
188 "DeleteRepositoryPermissionsPolicy"
189 }
190 ["v1", "repository", "endpoint"] if get => "GetRepositoryEndpoint",
191 ["v1", "package"] if get => "DescribePackage",
192 ["v1", "package"] if del => "DeletePackage",
193 ["v1", "package"] if post => "PutPackageOriginConfiguration",
194 ["v1", "package", "version"] if get => "DescribePackageVersion",
195 ["v1", "package", "version", "asset"] if get => "GetPackageVersionAsset",
196 ["v1", "package", "version", "readme"] if get => "GetPackageVersionReadme",
197 ["v1", "package", "version", "publish"] if post => "PublishPackageVersion",
198 ["v1", "package", "version", "assets"] if post => "ListPackageVersionAssets",
199 ["v1", "package", "version", "dependencies"] if post => {
200 "ListPackageVersionDependencies"
201 }
202 ["v1", "package", "versions"] if post => "ListPackageVersions",
203 ["v1", "package", "versions", "copy"] if post => "CopyPackageVersions",
204 ["v1", "package", "versions", "delete"] if post => "DeletePackageVersions",
205 ["v1", "package", "versions", "dispose"] if post => "DisposePackageVersions",
206 ["v1", "package", "versions", "update_status"] if post => "UpdatePackageVersionsStatus",
207 ["v1", "packages"] if post => "ListPackages",
208 ["v1", "package-group"] if post => "CreatePackageGroup",
209 ["v1", "package-group"] if get => "DescribePackageGroup",
210 ["v1", "package-group"] if del => "DeletePackageGroup",
211 ["v1", "package-group"] if put => "UpdatePackageGroup",
212 ["v1", "package-groups"] if post => "ListPackageGroups",
213 ["v1", "package-groups", "sub-groups"] if post => "ListSubPackageGroups",
214 ["v1", "package-group-allowed-repositories"] if get => {
215 "ListAllowedRepositoriesForGroup"
216 }
217 ["v1", "package-group-origin-configuration"] if put => {
218 "UpdatePackageGroupOriginConfiguration"
219 }
220 ["v1", "get-associated-package-group"] if get => "GetAssociatedPackageGroup",
221 ["v1", "list-associated-packages"] if get => "ListAssociatedPackages",
222 ["v1", "authorization-token"] if post => "GetAuthorizationToken",
223 ["v1", "tags"] if post => "ListTagsForResource",
224 ["v1", "tag"] if post => "TagResource",
225 ["v1", "untag"] if post => "UntagResource",
226 _ => return None,
227 };
228 Some(action)
229 }
230}
231
232#[async_trait]
233impl AwsService for CodeArtifactService {
234 fn service_name(&self) -> &str {
235 "codeartifact"
236 }
237
238 async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
239 let Some(action) = Self::resolve_action(&req) else {
240 return Err(AwsServiceError::aws_error(
241 StatusCode::NOT_FOUND,
242 "UnknownOperationException",
243 format!("Unknown operation: {} {}", req.method, req.raw_path),
244 ));
245 };
246 let result = self.dispatch(action, &req);
247 if MUTATING.contains(&action)
248 && matches!(result.as_ref(), Ok(resp) if resp.status.is_success())
249 {
250 self.save().await;
251 }
252 result
253 }
254
255 fn supported_actions(&self) -> &[&str] {
256 CODEARTIFACT_ACTIONS
257 }
258}
259
260impl CodeArtifactService {
261 #[allow(clippy::too_many_lines)]
262 fn dispatch(&self, action: &str, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
263 match action {
264 "CreateDomain" => self.create_domain(req),
266 "DescribeDomain" => self.describe_domain(req),
267 "DeleteDomain" => self.delete_domain(req),
268 "ListDomains" => self.list_domains(req),
269 "PutDomainPermissionsPolicy" => self.put_domain_policy(req),
270 "GetDomainPermissionsPolicy" => self.get_domain_policy(req),
271 "DeleteDomainPermissionsPolicy" => self.delete_domain_policy(req),
272 "CreateRepository" => self.create_repository(req),
274 "DescribeRepository" => self.describe_repository(req),
275 "UpdateRepository" => self.update_repository(req),
276 "DeleteRepository" => self.delete_repository(req),
277 "ListRepositories" => self.list_repositories(req),
278 "ListRepositoriesInDomain" => self.list_repositories_in_domain(req),
279 "GetRepositoryEndpoint" => self.get_repository_endpoint(req),
280 "AssociateExternalConnection" => self.associate_external_connection(req),
281 "DisassociateExternalConnection" => self.disassociate_external_connection(req),
282 "GetRepositoryPermissionsPolicy" => self.get_repository_policy(req),
283 "PutRepositoryPermissionsPolicy" => self.put_repository_policy(req),
284 "DeleteRepositoryPermissionsPolicy" => self.delete_repository_policy(req),
285 "ListPackages" => self.list_packages(req),
287 "DescribePackage" => self.describe_package(req),
288 "DeletePackage" => self.delete_package(req),
289 "PutPackageOriginConfiguration" => self.put_package_origin(req),
290 "ListPackageVersions" => self.list_package_versions(req),
292 "DescribePackageVersion" => self.describe_package_version(req),
293 "DeletePackageVersions" => self.delete_package_versions(req),
294 "DisposePackageVersions" => self.dispose_package_versions(req),
295 "UpdatePackageVersionsStatus" => self.update_package_versions_status(req),
296 "CopyPackageVersions" => self.copy_package_versions(req),
297 "PublishPackageVersion" => self.publish_package_version(req),
298 "GetPackageVersionReadme" => self.get_package_version_readme(req),
299 "GetPackageVersionAsset" => self.get_package_version_asset(req),
300 "ListPackageVersionAssets" => self.list_package_version_assets(req),
301 "ListPackageVersionDependencies" => self.list_package_version_dependencies(req),
302 "CreatePackageGroup" => self.create_package_group(req),
304 "DescribePackageGroup" => self.describe_package_group(req),
305 "UpdatePackageGroup" => self.update_package_group(req),
306 "DeletePackageGroup" => self.delete_package_group(req),
307 "ListPackageGroups" => self.list_package_groups(req),
308 "ListSubPackageGroups" => self.list_sub_package_groups(req),
309 "ListAllowedRepositoriesForGroup" => self.list_allowed_repositories_for_group(req),
310 "GetAssociatedPackageGroup" => self.get_associated_package_group(req),
311 "ListAssociatedPackages" => self.list_associated_packages(req),
312 "UpdatePackageGroupOriginConfiguration" => self.update_package_group_origin(req),
313 "GetAuthorizationToken" => self.get_authorization_token(req),
315 "TagResource" => self.tag_resource(req),
316 "UntagResource" => self.untag_resource(req),
317 "ListTagsForResource" => self.list_tags_for_resource(req),
318 _ => Err(AwsServiceError::action_not_implemented(
319 self.service_name(),
320 action,
321 )),
322 }
323 }
324}
325
326include!("handlers.rs");