Skip to main content

winterbaume_codecommit/
handlers.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use serde_json::{Value, json};
6use winterbaume_core::{
7    BackendState, DEFAULT_ACCOUNT_ID, MockRequest, MockResponse, MockService, StateChangeNotifier,
8    json_error_response,
9};
10
11use crate::state::{CodeCommitError, CodeCommitState};
12use crate::views::CodeCommitStateView;
13use crate::wire;
14
15pub struct CodeCommitService {
16    pub(crate) state: Arc<BackendState<CodeCommitState>>,
17    pub(crate) notifier: StateChangeNotifier<CodeCommitStateView>,
18}
19
20impl CodeCommitService {
21    pub fn new() -> Self {
22        Self {
23            state: Arc::new(BackendState::new()),
24            notifier: StateChangeNotifier::new(),
25        }
26    }
27}
28
29impl Default for CodeCommitService {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl MockService for CodeCommitService {
36    fn service_name(&self) -> &str {
37        "codecommit"
38    }
39
40    fn url_patterns(&self) -> Vec<&str> {
41        vec![
42            r"https?://codecommit\..*\.amazonaws\.com",
43            r"https?://codecommit\.amazonaws\.com",
44        ]
45    }
46
47    fn handle(
48        &self,
49        request: MockRequest,
50    ) -> Pin<Box<dyn Future<Output = MockResponse> + Send + '_>> {
51        Box::pin(async move { self.dispatch(request).await })
52    }
53}
54
55impl CodeCommitService {
56    async fn dispatch(&self, request: MockRequest) -> MockResponse {
57        let region = winterbaume_core::auth::extract_region_from_uri(&request.uri);
58        let account_id = DEFAULT_ACCOUNT_ID;
59
60        // Extract action from X-Amz-Target header
61        // Format: "CodeCommit_20150413.CreateRepository"
62        let action = request
63            .headers
64            .get("x-amz-target")
65            .and_then(|v| v.to_str().ok())
66            .and_then(|v| v.split('.').next_back())
67            .map(|s| s.to_string());
68
69        let action = match action {
70            Some(a) => a,
71            None => {
72                return json_error_response(400, "MissingAction", "Missing X-Amz-Target header");
73            }
74        };
75
76        // Validate the body is well-formed JSON up-front; the typed deserialisers in
77        // `wire` re-parse the bytes per operation.
78        if serde_json::from_slice::<Value>(&request.body).is_err() {
79            return json_error_response(400, "SerializationException", "Invalid JSON body");
80        }
81        let body_bytes: &[u8] = &request.body;
82
83        let state = self.state.get(account_id, &region);
84
85        match action.as_str() {
86            "CreateRepository" => {
87                self.handle_create_repository(&state, body_bytes, account_id, &region)
88                    .await
89            }
90            "GetRepository" => self.handle_get_repository(&state, body_bytes).await,
91            "DeleteRepository" => self.handle_delete_repository(&state, body_bytes).await,
92            "ListRepositories" => self.handle_list_repositories(&state, body_bytes).await,
93            "UpdateRepositoryDescription" => {
94                self.handle_update_repository_description(&state, body_bytes)
95                    .await
96            }
97            "UpdateRepositoryName" => {
98                self.handle_update_repository_name(&state, body_bytes, account_id, &region)
99                    .await
100            }
101            // Branches
102            "CreateBranch" => self.handle_create_branch(&state, body_bytes).await,
103            "GetBranch" => self.handle_get_branch(&state, body_bytes).await,
104            "ListBranches" => self.handle_list_branches(&state, body_bytes).await,
105            "DeleteBranch" => self.handle_delete_branch(&state, body_bytes).await,
106            "UpdateDefaultBranch" => self.handle_update_default_branch(&state, body_bytes).await,
107            // Commits & files
108            "CreateCommit" => self.handle_create_commit(&state, body_bytes).await,
109            "GetCommit" => self.handle_get_commit(&state, body_bytes).await,
110            "GetDifferences" => self.handle_get_differences(&state, body_bytes).await,
111            "GetFile" => self.handle_get_file(&state, body_bytes).await,
112            "GetFolder" => self.handle_get_folder(&state, body_bytes).await,
113            "PutFile" => self.handle_put_file(&state, body_bytes).await,
114            "DeleteFile" => self.handle_delete_file(&state, body_bytes).await,
115            // Pull Requests
116            "CreatePullRequest" => self.handle_create_pull_request(&state, body_bytes).await,
117            "GetPullRequest" => self.handle_get_pull_request(&state, body_bytes).await,
118            "ListPullRequests" => self.handle_list_pull_requests(&state, body_bytes).await,
119            "UpdatePullRequestStatus" => {
120                self.handle_update_pull_request_status(&state, body_bytes)
121                    .await
122            }
123            // Tags
124            "TagResource" => self.handle_tag_resource(&state, body_bytes).await,
125            "UntagResource" => self.handle_untag_resource(&state, body_bytes).await,
126            "ListTagsForResource" => self.handle_list_tags_for_resource(&state, body_bytes).await,
127            // --- Unimplemented operations ---
128            "AssociateApprovalRuleTemplateWithRepository" => json_error_response(
129                501,
130                "NotImplementedError",
131                "AssociateApprovalRuleTemplateWithRepository is not yet implemented in winterbaume-codecommit",
132            ),
133            "BatchAssociateApprovalRuleTemplateWithRepositories" => json_error_response(
134                501,
135                "NotImplementedError",
136                "BatchAssociateApprovalRuleTemplateWithRepositories is not yet implemented in winterbaume-codecommit",
137            ),
138            "BatchDescribeMergeConflicts" => json_error_response(
139                501,
140                "NotImplementedError",
141                "BatchDescribeMergeConflicts is not yet implemented in winterbaume-codecommit",
142            ),
143            "BatchDisassociateApprovalRuleTemplateFromRepositories" => json_error_response(
144                501,
145                "NotImplementedError",
146                "BatchDisassociateApprovalRuleTemplateFromRepositories is not yet implemented in winterbaume-codecommit",
147            ),
148            "BatchGetCommits" => json_error_response(
149                501,
150                "NotImplementedError",
151                "BatchGetCommits is not yet implemented in winterbaume-codecommit",
152            ),
153            "BatchGetRepositories" => json_error_response(
154                501,
155                "NotImplementedError",
156                "BatchGetRepositories is not yet implemented in winterbaume-codecommit",
157            ),
158            "CreateApprovalRuleTemplate" => json_error_response(
159                501,
160                "NotImplementedError",
161                "CreateApprovalRuleTemplate is not yet implemented in winterbaume-codecommit",
162            ),
163            "CreatePullRequestApprovalRule" => json_error_response(
164                501,
165                "NotImplementedError",
166                "CreatePullRequestApprovalRule is not yet implemented in winterbaume-codecommit",
167            ),
168            "CreateUnreferencedMergeCommit" => json_error_response(
169                501,
170                "NotImplementedError",
171                "CreateUnreferencedMergeCommit is not yet implemented in winterbaume-codecommit",
172            ),
173            "DeleteApprovalRuleTemplate" => json_error_response(
174                501,
175                "NotImplementedError",
176                "DeleteApprovalRuleTemplate is not yet implemented in winterbaume-codecommit",
177            ),
178            "DeleteCommentContent" => json_error_response(
179                501,
180                "NotImplementedError",
181                "DeleteCommentContent is not yet implemented in winterbaume-codecommit",
182            ),
183            "DeletePullRequestApprovalRule" => json_error_response(
184                501,
185                "NotImplementedError",
186                "DeletePullRequestApprovalRule is not yet implemented in winterbaume-codecommit",
187            ),
188            "DescribeMergeConflicts" => json_error_response(
189                501,
190                "NotImplementedError",
191                "DescribeMergeConflicts is not yet implemented in winterbaume-codecommit",
192            ),
193            "DescribePullRequestEvents" => json_error_response(
194                501,
195                "NotImplementedError",
196                "DescribePullRequestEvents is not yet implemented in winterbaume-codecommit",
197            ),
198            "DisassociateApprovalRuleTemplateFromRepository" => json_error_response(
199                501,
200                "NotImplementedError",
201                "DisassociateApprovalRuleTemplateFromRepository is not yet implemented in winterbaume-codecommit",
202            ),
203            "EvaluatePullRequestApprovalRules" => json_error_response(
204                501,
205                "NotImplementedError",
206                "EvaluatePullRequestApprovalRules is not yet implemented in winterbaume-codecommit",
207            ),
208            "GetApprovalRuleTemplate" => json_error_response(
209                501,
210                "NotImplementedError",
211                "GetApprovalRuleTemplate is not yet implemented in winterbaume-codecommit",
212            ),
213            "GetBlob" => json_error_response(
214                501,
215                "NotImplementedError",
216                "GetBlob is not yet implemented in winterbaume-codecommit",
217            ),
218            "GetComment" => json_error_response(
219                501,
220                "NotImplementedError",
221                "GetComment is not yet implemented in winterbaume-codecommit",
222            ),
223            "GetCommentReactions" => json_error_response(
224                501,
225                "NotImplementedError",
226                "GetCommentReactions is not yet implemented in winterbaume-codecommit",
227            ),
228            "GetCommentsForComparedCommit" => json_error_response(
229                501,
230                "NotImplementedError",
231                "GetCommentsForComparedCommit is not yet implemented in winterbaume-codecommit",
232            ),
233            "GetCommentsForPullRequest" => json_error_response(
234                501,
235                "NotImplementedError",
236                "GetCommentsForPullRequest is not yet implemented in winterbaume-codecommit",
237            ),
238            "GetMergeCommit" => json_error_response(
239                501,
240                "NotImplementedError",
241                "GetMergeCommit is not yet implemented in winterbaume-codecommit",
242            ),
243            "GetMergeConflicts" => json_error_response(
244                501,
245                "NotImplementedError",
246                "GetMergeConflicts is not yet implemented in winterbaume-codecommit",
247            ),
248            "GetMergeOptions" => json_error_response(
249                501,
250                "NotImplementedError",
251                "GetMergeOptions is not yet implemented in winterbaume-codecommit",
252            ),
253            "GetPullRequestApprovalStates" => json_error_response(
254                501,
255                "NotImplementedError",
256                "GetPullRequestApprovalStates is not yet implemented in winterbaume-codecommit",
257            ),
258            "GetPullRequestOverrideState" => json_error_response(
259                501,
260                "NotImplementedError",
261                "GetPullRequestOverrideState is not yet implemented in winterbaume-codecommit",
262            ),
263            "GetRepositoryTriggers" => json_error_response(
264                501,
265                "NotImplementedError",
266                "GetRepositoryTriggers is not yet implemented in winterbaume-codecommit",
267            ),
268            "ListApprovalRuleTemplates" => json_error_response(
269                501,
270                "NotImplementedError",
271                "ListApprovalRuleTemplates is not yet implemented in winterbaume-codecommit",
272            ),
273            "ListAssociatedApprovalRuleTemplatesForRepository" => json_error_response(
274                501,
275                "NotImplementedError",
276                "ListAssociatedApprovalRuleTemplatesForRepository is not yet implemented in winterbaume-codecommit",
277            ),
278            "ListFileCommitHistory" => json_error_response(
279                501,
280                "NotImplementedError",
281                "ListFileCommitHistory is not yet implemented in winterbaume-codecommit",
282            ),
283            "ListRepositoriesForApprovalRuleTemplate" => json_error_response(
284                501,
285                "NotImplementedError",
286                "ListRepositoriesForApprovalRuleTemplate is not yet implemented in winterbaume-codecommit",
287            ),
288            "MergeBranchesByFastForward" => json_error_response(
289                501,
290                "NotImplementedError",
291                "MergeBranchesByFastForward is not yet implemented in winterbaume-codecommit",
292            ),
293            "MergeBranchesBySquash" => json_error_response(
294                501,
295                "NotImplementedError",
296                "MergeBranchesBySquash is not yet implemented in winterbaume-codecommit",
297            ),
298            "MergeBranchesByThreeWay" => json_error_response(
299                501,
300                "NotImplementedError",
301                "MergeBranchesByThreeWay is not yet implemented in winterbaume-codecommit",
302            ),
303            "MergePullRequestByFastForward" => json_error_response(
304                501,
305                "NotImplementedError",
306                "MergePullRequestByFastForward is not yet implemented in winterbaume-codecommit",
307            ),
308            "MergePullRequestBySquash" => json_error_response(
309                501,
310                "NotImplementedError",
311                "MergePullRequestBySquash is not yet implemented in winterbaume-codecommit",
312            ),
313            "MergePullRequestByThreeWay" => json_error_response(
314                501,
315                "NotImplementedError",
316                "MergePullRequestByThreeWay is not yet implemented in winterbaume-codecommit",
317            ),
318            "OverridePullRequestApprovalRules" => json_error_response(
319                501,
320                "NotImplementedError",
321                "OverridePullRequestApprovalRules is not yet implemented in winterbaume-codecommit",
322            ),
323            "PostCommentForComparedCommit" => json_error_response(
324                501,
325                "NotImplementedError",
326                "PostCommentForComparedCommit is not yet implemented in winterbaume-codecommit",
327            ),
328            "PostCommentForPullRequest" => json_error_response(
329                501,
330                "NotImplementedError",
331                "PostCommentForPullRequest is not yet implemented in winterbaume-codecommit",
332            ),
333            "PostCommentReply" => json_error_response(
334                501,
335                "NotImplementedError",
336                "PostCommentReply is not yet implemented in winterbaume-codecommit",
337            ),
338            "PutCommentReaction" => json_error_response(
339                501,
340                "NotImplementedError",
341                "PutCommentReaction is not yet implemented in winterbaume-codecommit",
342            ),
343            "PutRepositoryTriggers" => json_error_response(
344                501,
345                "NotImplementedError",
346                "PutRepositoryTriggers is not yet implemented in winterbaume-codecommit",
347            ),
348            "TestRepositoryTriggers" => json_error_response(
349                501,
350                "NotImplementedError",
351                "TestRepositoryTriggers is not yet implemented in winterbaume-codecommit",
352            ),
353            "UpdateApprovalRuleTemplateContent" => json_error_response(
354                501,
355                "NotImplementedError",
356                "UpdateApprovalRuleTemplateContent is not yet implemented in winterbaume-codecommit",
357            ),
358            "UpdateApprovalRuleTemplateDescription" => json_error_response(
359                501,
360                "NotImplementedError",
361                "UpdateApprovalRuleTemplateDescription is not yet implemented in winterbaume-codecommit",
362            ),
363            "UpdateApprovalRuleTemplateName" => json_error_response(
364                501,
365                "NotImplementedError",
366                "UpdateApprovalRuleTemplateName is not yet implemented in winterbaume-codecommit",
367            ),
368            "UpdateComment" => json_error_response(
369                501,
370                "NotImplementedError",
371                "UpdateComment is not yet implemented in winterbaume-codecommit",
372            ),
373            "UpdatePullRequestApprovalRuleContent" => json_error_response(
374                501,
375                "NotImplementedError",
376                "UpdatePullRequestApprovalRuleContent is not yet implemented in winterbaume-codecommit",
377            ),
378            "UpdatePullRequestApprovalState" => json_error_response(
379                501,
380                "NotImplementedError",
381                "UpdatePullRequestApprovalState is not yet implemented in winterbaume-codecommit",
382            ),
383            "UpdatePullRequestDescription" => json_error_response(
384                501,
385                "NotImplementedError",
386                "UpdatePullRequestDescription is not yet implemented in winterbaume-codecommit",
387            ),
388            "UpdatePullRequestTitle" => json_error_response(
389                501,
390                "NotImplementedError",
391                "UpdatePullRequestTitle is not yet implemented in winterbaume-codecommit",
392            ),
393            "UpdateRepositoryEncryptionKey" => json_error_response(
394                501,
395                "NotImplementedError",
396                "UpdateRepositoryEncryptionKey is not yet implemented in winterbaume-codecommit",
397            ),
398            _ => json_error_response(
399                400,
400                "InvalidAction",
401                &format!("Could not find operation {action} for CodeCommit"),
402            ),
403        }
404    }
405
406    async fn handle_create_repository(
407        &self,
408        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
409        body: &[u8],
410        account_id: &str,
411        region: &str,
412    ) -> MockResponse {
413        let input = match wire::deserialize_create_repository_request(body) {
414            Ok(v) => v,
415            Err(e) => return json_error_response(400, "ValidationException", &e),
416        };
417        if input.repository_name.is_empty() {
418            return json_error_response(
419                400,
420                "RepositoryNameRequiredException",
421                "Repository name is required",
422            );
423        }
424        let description = input.repository_description.as_deref().unwrap_or("");
425
426        let mut state = state.write().await;
427        match state.create_repository(&input.repository_name, description, account_id, region) {
428            Ok(repo) => wire::serialize_create_repository_response(&wire::CreateRepositoryOutput {
429                repository_metadata: Some(repo_to_wire(repo)),
430            }),
431            Err(e) => codecommit_error_response(&e),
432        }
433    }
434
435    async fn handle_get_repository(
436        &self,
437        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
438        body: &[u8],
439    ) -> MockResponse {
440        let input = match wire::deserialize_get_repository_request(body) {
441            Ok(v) => v,
442            Err(e) => return json_error_response(400, "ValidationException", &e),
443        };
444        if input.repository_name.is_empty() {
445            return json_error_response(
446                400,
447                "RepositoryNameRequiredException",
448                "Repository name is required",
449            );
450        }
451
452        let state = state.read().await;
453        match state.get_repository(&input.repository_name) {
454            Ok(repo) => wire::serialize_get_repository_response(&wire::GetRepositoryOutput {
455                repository_metadata: Some(repo_to_wire(repo)),
456            }),
457            Err(e) => codecommit_error_response(&e),
458        }
459    }
460
461    async fn handle_delete_repository(
462        &self,
463        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
464        body: &[u8],
465    ) -> MockResponse {
466        let input = match wire::deserialize_delete_repository_request(body) {
467            Ok(v) => v,
468            Err(e) => return json_error_response(400, "ValidationException", &e),
469        };
470        if input.repository_name.is_empty() {
471            return json_error_response(
472                400,
473                "RepositoryNameRequiredException",
474                "Repository name is required",
475            );
476        }
477
478        let mut state = state.write().await;
479        let repo_id = state.delete_repository(&input.repository_name);
480        let repo_id_opt = if repo_id.is_empty() {
481            None
482        } else {
483            Some(repo_id)
484        };
485        wire::serialize_delete_repository_response(&wire::DeleteRepositoryOutput {
486            repository_id: repo_id_opt,
487        })
488    }
489
490    async fn handle_list_repositories(
491        &self,
492        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
493        body: &[u8],
494    ) -> MockResponse {
495        if let Err(e) = wire::deserialize_list_repositories_request(body) {
496            return json_error_response(400, "ValidationException", &e);
497        }
498        let state = state.read().await;
499        let repos = state.list_repositories();
500        wire::serialize_list_repositories_response(&wire::ListRepositoriesOutput {
501            repositories: Some(
502                repos
503                    .iter()
504                    .map(|r| wire::RepositoryNameIdPair {
505                        repository_id: Some(r.repository_id.clone()),
506                        repository_name: Some(r.repository_name.clone()),
507                    })
508                    .collect(),
509            ),
510            next_token: None,
511        })
512    }
513
514    async fn handle_update_repository_description(
515        &self,
516        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
517        body: &[u8],
518    ) -> MockResponse {
519        let input = match wire::deserialize_update_repository_description_request(body) {
520            Ok(v) => v,
521            Err(e) => return json_error_response(400, "ValidationException", &e),
522        };
523        if input.repository_name.is_empty() {
524            return json_error_response(
525                400,
526                "RepositoryNameRequiredException",
527                "Repository name is required",
528            );
529        }
530        let description = input.repository_description.as_deref();
531
532        let mut state = state.write().await;
533        match state.update_repository_description(&input.repository_name, description) {
534            Ok(()) => wire::serialize_update_repository_description_response(),
535            Err(e) => codecommit_error_response(&e),
536        }
537    }
538
539    async fn handle_update_repository_name(
540        &self,
541        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
542        body: &[u8],
543        account_id: &str,
544        region: &str,
545    ) -> MockResponse {
546        let input = match wire::deserialize_update_repository_name_request(body) {
547            Ok(v) => v,
548            Err(e) => return json_error_response(400, "ValidationException", &e),
549        };
550        if input.old_name.is_empty() {
551            return json_error_response(
552                400,
553                "RepositoryNameRequiredException",
554                "Old repository name is required",
555            );
556        }
557        if input.new_name.is_empty() {
558            return json_error_response(
559                400,
560                "RepositoryNameRequiredException",
561                "New repository name is required",
562            );
563        }
564
565        let mut state = state.write().await;
566        match state.update_repository_name(&input.old_name, &input.new_name, region, account_id) {
567            Ok(()) => wire::serialize_update_repository_name_response(),
568            Err(e) => codecommit_error_response(&e),
569        }
570    }
571
572    // ---- Branches ----
573
574    async fn handle_create_branch(
575        &self,
576        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
577        body: &[u8],
578    ) -> MockResponse {
579        let input = match wire::deserialize_create_branch_request(body) {
580            Ok(v) => v,
581            Err(e) => return json_error_response(400, "ValidationException", &e),
582        };
583        if input.repository_name.is_empty() {
584            return json_error_response(
585                400,
586                "RepositoryNameRequiredException",
587                "Repository name is required",
588            );
589        }
590        if input.branch_name.is_empty() {
591            return json_error_response(
592                400,
593                "BranchNameRequiredException",
594                "Branch name is required",
595            );
596        }
597        if input.commit_id.is_empty() {
598            return json_error_response(400, "CommitIdRequiredException", "Commit ID is required");
599        }
600
601        let mut state = state.write().await;
602        match state.create_branch(&input.repository_name, &input.branch_name, &input.commit_id) {
603            Ok(()) => wire::serialize_create_branch_response(),
604            Err(e) => codecommit_error_response(&e),
605        }
606    }
607
608    async fn handle_get_branch(
609        &self,
610        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
611        body: &[u8],
612    ) -> MockResponse {
613        let input = match wire::deserialize_get_branch_request(body) {
614            Ok(v) => v,
615            Err(e) => return json_error_response(400, "ValidationException", &e),
616        };
617        let repo_name = match input.repository_name.as_deref() {
618            Some(n) if !n.is_empty() => n,
619            _ => {
620                return json_error_response(
621                    400,
622                    "RepositoryNameRequiredException",
623                    "Repository name is required",
624                );
625            }
626        };
627        let branch_name = match input.branch_name.as_deref() {
628            Some(n) if !n.is_empty() => n,
629            _ => {
630                return json_error_response(
631                    400,
632                    "BranchNameRequiredException",
633                    "Branch name is required",
634                );
635            }
636        };
637
638        let state = state.read().await;
639        match state.get_branch(repo_name, branch_name) {
640            Ok(branch) => wire::serialize_get_branch_response(&wire::GetBranchOutput {
641                branch: Some(wire::BranchInfo {
642                    branch_name: Some(branch.branch_name.clone()),
643                    commit_id: Some(branch.commit_id.clone()),
644                }),
645            }),
646            Err(e) => codecommit_error_response(&e),
647        }
648    }
649
650    async fn handle_list_branches(
651        &self,
652        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
653        body: &[u8],
654    ) -> MockResponse {
655        let input = match wire::deserialize_list_branches_request(body) {
656            Ok(v) => v,
657            Err(e) => return json_error_response(400, "ValidationException", &e),
658        };
659        if input.repository_name.is_empty() {
660            return json_error_response(
661                400,
662                "RepositoryNameRequiredException",
663                "Repository name is required",
664            );
665        }
666
667        let state = state.read().await;
668        match state.list_branches(&input.repository_name) {
669            Ok(branches) => wire::serialize_list_branches_response(&wire::ListBranchesOutput {
670                branches: Some(branches),
671                next_token: None,
672            }),
673            Err(e) => codecommit_error_response(&e),
674        }
675    }
676
677    async fn handle_delete_branch(
678        &self,
679        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
680        body: &[u8],
681    ) -> MockResponse {
682        let input = match wire::deserialize_delete_branch_request(body) {
683            Ok(v) => v,
684            Err(e) => return json_error_response(400, "ValidationException", &e),
685        };
686        if input.repository_name.is_empty() {
687            return json_error_response(
688                400,
689                "RepositoryNameRequiredException",
690                "Repository name is required",
691            );
692        }
693        if input.branch_name.is_empty() {
694            return json_error_response(
695                400,
696                "BranchNameRequiredException",
697                "Branch name is required",
698            );
699        }
700
701        let mut state = state.write().await;
702        match state.delete_branch(&input.repository_name, &input.branch_name) {
703            Ok(branch) => wire::serialize_delete_branch_response(&wire::DeleteBranchOutput {
704                deleted_branch: Some(wire::BranchInfo {
705                    branch_name: Some(branch.branch_name.clone()),
706                    commit_id: Some(branch.commit_id.clone()),
707                }),
708            }),
709            Err(e) => codecommit_error_response(&e),
710        }
711    }
712
713    async fn handle_update_default_branch(
714        &self,
715        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
716        body: &[u8],
717    ) -> MockResponse {
718        let input = match wire::deserialize_update_default_branch_request(body) {
719            Ok(v) => v,
720            Err(e) => return json_error_response(400, "ValidationException", &e),
721        };
722        if input.repository_name.is_empty() {
723            return json_error_response(
724                400,
725                "RepositoryNameRequiredException",
726                "Repository name is required",
727            );
728        }
729        if input.default_branch_name.is_empty() {
730            return json_error_response(
731                400,
732                "BranchNameRequiredException",
733                "Default branch name is required",
734            );
735        }
736
737        let mut state = state.write().await;
738        match state.update_default_branch(&input.repository_name, &input.default_branch_name) {
739            Ok(()) => wire::serialize_update_default_branch_response(),
740            Err(e) => codecommit_error_response(&e),
741        }
742    }
743
744    // ---- Commits ----
745
746    async fn handle_create_commit(
747        &self,
748        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
749        body: &[u8],
750    ) -> MockResponse {
751        let input = match wire::deserialize_create_commit_request(body) {
752            Ok(v) => v,
753            Err(e) => return json_error_response(400, "ValidationException", &e),
754        };
755        if input.repository_name.is_empty() {
756            return json_error_response(
757                400,
758                "RepositoryNameRequiredException",
759                "Repository name is required",
760            );
761        }
762        if input.branch_name.is_empty() {
763            return json_error_response(
764                400,
765                "BranchNameRequiredException",
766                "Branch name is required",
767            );
768        }
769        let parent_commit_id = input.parent_commit_id.as_deref();
770        let author_name = input.author_name.as_deref();
771        let author_email = input.email.as_deref();
772        let commit_message = input.commit_message.as_deref();
773
774        // Collect put files: putFiles array of {filePath, fileContent (base64), fileMode}
775        let put_files: Vec<(String, String)> = input
776            .put_files
777            .unwrap_or_default()
778            .into_iter()
779            .map(|item| {
780                let mode = item.file_mode.unwrap_or_else(|| "NORMAL".to_string());
781                (item.file_path, mode)
782            })
783            .collect();
784
785        // Collect delete files: deleteFiles array of {filePath}
786        let delete_files: Vec<String> = input
787            .delete_files
788            .unwrap_or_default()
789            .into_iter()
790            .map(|item| item.file_path)
791            .collect();
792
793        let mut state = state.write().await;
794        match state.create_commit(
795            &input.repository_name,
796            &input.branch_name,
797            parent_commit_id,
798            author_name,
799            author_email,
800            commit_message,
801            put_files,
802            delete_files,
803        ) {
804            Ok(commit) => wire::serialize_create_commit_response(&wire::CreateCommitOutput {
805                commit_id: Some(commit.commit_id.clone()),
806                tree_id: Some(commit.tree_id.clone()),
807                files_added: None,
808                files_updated: None,
809                files_deleted: None,
810            }),
811            Err(e) => codecommit_error_response(&e),
812        }
813    }
814
815    async fn handle_get_commit(
816        &self,
817        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
818        body: &[u8],
819    ) -> MockResponse {
820        let input = match wire::deserialize_get_commit_request(body) {
821            Ok(v) => v,
822            Err(e) => return json_error_response(400, "ValidationException", &e),
823        };
824        if input.repository_name.is_empty() {
825            return json_error_response(
826                400,
827                "RepositoryNameRequiredException",
828                "Repository name is required",
829            );
830        }
831        if input.commit_id.is_empty() {
832            return json_error_response(400, "CommitIdRequiredException", "Commit ID is required");
833        }
834
835        let state = state.read().await;
836        match state.get_commit(&input.repository_name, &input.commit_id) {
837            Ok(commit) => wire::serialize_get_commit_response(&wire::GetCommitOutput {
838                commit: Some(commit_to_wire(commit)),
839            }),
840            Err(e) => codecommit_error_response(&e),
841        }
842    }
843
844    async fn handle_get_differences(
845        &self,
846        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
847        body: &[u8],
848    ) -> MockResponse {
849        let input = match wire::deserialize_get_differences_request(body) {
850            Ok(v) => v,
851            Err(e) => return json_error_response(400, "ValidationException", &e),
852        };
853        if input.repository_name.is_empty() {
854            return json_error_response(
855                400,
856                "RepositoryNameRequiredException",
857                "Repository name is required",
858            );
859        }
860        if input.after_commit_specifier.is_empty() {
861            return json_error_response(
862                400,
863                "CommitRequiredException",
864                "afterCommitSpecifier is required",
865            );
866        }
867        let before_commit = input.before_commit_specifier.as_deref();
868
869        let state = state.read().await;
870        match state.get_differences(
871            &input.repository_name,
872            &input.after_commit_specifier,
873            before_commit,
874        ) {
875            Ok(diffs) => wire::serialize_get_differences_response(&wire::GetDifferencesOutput {
876                differences: Some(
877                    diffs
878                        .iter()
879                        .map(|(before, after, change_type)| wire::Difference {
880                            before_blob: before.as_ref().map(|fe| wire::BlobMetadata {
881                                blob_id: Some(fe.blob_id.clone()),
882                                path: Some(fe.file_path.clone()),
883                                mode: Some(fe.file_mode.clone()),
884                            }),
885                            after_blob: after.as_ref().map(|fe| wire::BlobMetadata {
886                                blob_id: Some(fe.blob_id.clone()),
887                                path: Some(fe.file_path.clone()),
888                                mode: Some(fe.file_mode.clone()),
889                            }),
890                            change_type: Some(change_type.clone()),
891                        })
892                        .collect(),
893                ),
894                next_token: None,
895            }),
896            Err(e) => codecommit_error_response(&e),
897        }
898    }
899
900    async fn handle_get_file(
901        &self,
902        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
903        body: &[u8],
904    ) -> MockResponse {
905        let input = match wire::deserialize_get_file_request(body) {
906            Ok(v) => v,
907            Err(e) => return json_error_response(400, "ValidationException", &e),
908        };
909        if input.repository_name.is_empty() {
910            return json_error_response(
911                400,
912                "RepositoryNameRequiredException",
913                "Repository name is required",
914            );
915        }
916        if input.file_path.is_empty() {
917            return json_error_response(400, "FilePathRequiredException", "File path is required");
918        }
919        let commit_specifier = input.commit_specifier.as_deref();
920
921        let state = state.read().await;
922        match state.get_file(&input.repository_name, commit_specifier, &input.file_path) {
923            Ok((commit, file)) => wire::serialize_get_file_response(&wire::GetFileOutput {
924                commit_id: Some(commit.commit_id.clone()),
925                blob_id: Some(file.blob_id.clone()),
926                file_path: Some(file.file_path.clone()),
927                file_mode: Some(file.file_mode.clone()),
928                file_size: Some(0i64),
929                file_content: Some(String::new()),
930            }),
931            Err(e) => codecommit_error_response(&e),
932        }
933    }
934
935    async fn handle_get_folder(
936        &self,
937        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
938        body: &[u8],
939    ) -> MockResponse {
940        let input = match wire::deserialize_get_folder_request(body) {
941            Ok(v) => v,
942            Err(e) => return json_error_response(400, "ValidationException", &e),
943        };
944        if input.repository_name.is_empty() {
945            return json_error_response(
946                400,
947                "RepositoryNameRequiredException",
948                "Repository name is required",
949            );
950        }
951        let folder_path = if input.folder_path.is_empty() {
952            "/"
953        } else {
954            input.folder_path.as_str()
955        };
956        let commit_specifier = input.commit_specifier.as_deref();
957
958        let state = state.read().await;
959        match state.get_folder(&input.repository_name, commit_specifier, folder_path) {
960            Ok((commit_id, files, sub_folders)) => {
961                wire::serialize_get_folder_response(&wire::GetFolderOutput {
962                    commit_id: Some(commit_id),
963                    folder_path: Some(folder_path.to_string()),
964                    tree_id: None,
965                    sub_folders: Some(
966                        sub_folders
967                            .iter()
968                            .map(|sf| wire::Folder {
969                                absolute_path: Some(format!("{folder_path}/{sf}")),
970                                relative_path: Some(sf.clone()),
971                                tree_id: None,
972                            })
973                            .collect(),
974                    ),
975                    files: Some(
976                        files
977                            .iter()
978                            .map(|fe| wire::File {
979                                absolute_path: Some(fe.file_path.clone()),
980                                blob_id: Some(fe.blob_id.clone()),
981                                file_mode: Some(fe.file_mode.clone()),
982                                relative_path: Some(
983                                    fe.file_path
984                                        .strip_prefix(folder_path.trim_start_matches('/'))
985                                        .unwrap_or(&fe.file_path)
986                                        .to_string(),
987                                ),
988                            })
989                            .collect(),
990                    ),
991                    sub_modules: None,
992                    symbolic_links: None,
993                })
994            }
995            Err(e) => codecommit_error_response(&e),
996        }
997    }
998
999    async fn handle_put_file(
1000        &self,
1001        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
1002        body: &[u8],
1003    ) -> MockResponse {
1004        let input = match wire::deserialize_put_file_request(body) {
1005            Ok(v) => v,
1006            Err(e) => return json_error_response(400, "ValidationException", &e),
1007        };
1008        if input.repository_name.is_empty() {
1009            return json_error_response(
1010                400,
1011                "RepositoryNameRequiredException",
1012                "Repository name is required",
1013            );
1014        }
1015        if input.branch_name.is_empty() {
1016            return json_error_response(
1017                400,
1018                "BranchNameRequiredException",
1019                "Branch name is required",
1020            );
1021        }
1022        if input.file_path.is_empty() {
1023            return json_error_response(400, "FilePathRequiredException", "File path is required");
1024        }
1025        if input.file_content.is_empty() {
1026            return json_error_response(
1027                400,
1028                "FileContentRequiredException",
1029                "File content is required",
1030            );
1031        }
1032        let parent_commit_id = match input.parent_commit_id.as_deref() {
1033            Some(c) if !c.is_empty() => c,
1034            _ => {
1035                return json_error_response(
1036                    400,
1037                    "ParentCommitIdRequiredException",
1038                    "Parent commit ID is required",
1039                );
1040            }
1041        };
1042        let file_mode = input.file_mode.as_deref();
1043        let author_name = input.name.as_deref();
1044        let author_email = input.email.as_deref();
1045        let commit_message = input.commit_message.as_deref();
1046
1047        let mut state = state.write().await;
1048        match state.put_file(
1049            &input.repository_name,
1050            &input.branch_name,
1051            parent_commit_id,
1052            &input.file_path,
1053            file_mode,
1054            author_name,
1055            author_email,
1056            commit_message,
1057        ) {
1058            Ok(commit) => wire::serialize_put_file_response(&wire::PutFileOutput {
1059                commit_id: Some(commit.commit_id.clone()),
1060                tree_id: Some(commit.tree_id.clone()),
1061                blob_id: None,
1062            }),
1063            Err(e) => codecommit_error_response(&e),
1064        }
1065    }
1066
1067    async fn handle_delete_file(
1068        &self,
1069        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
1070        body: &[u8],
1071    ) -> MockResponse {
1072        let input = match wire::deserialize_delete_file_request(body) {
1073            Ok(v) => v,
1074            Err(e) => return json_error_response(400, "ValidationException", &e),
1075        };
1076        if input.repository_name.is_empty() {
1077            return json_error_response(
1078                400,
1079                "RepositoryNameRequiredException",
1080                "Repository name is required",
1081            );
1082        }
1083        if input.branch_name.is_empty() {
1084            return json_error_response(
1085                400,
1086                "BranchNameRequiredException",
1087                "Branch name is required",
1088            );
1089        }
1090        if input.file_path.is_empty() {
1091            return json_error_response(400, "FilePathRequiredException", "File path is required");
1092        }
1093        if input.parent_commit_id.is_empty() {
1094            return json_error_response(
1095                400,
1096                "ParentCommitIdRequiredException",
1097                "Parent commit ID is required",
1098            );
1099        }
1100        let author_name = input.name.as_deref();
1101        let author_email = input.email.as_deref();
1102        let commit_message = input.commit_message.as_deref();
1103
1104        let mut state = state.write().await;
1105        match state.delete_file(
1106            &input.repository_name,
1107            &input.branch_name,
1108            &input.parent_commit_id,
1109            &input.file_path,
1110            author_name,
1111            author_email,
1112            commit_message,
1113        ) {
1114            Ok(commit) => wire::serialize_delete_file_response(&wire::DeleteFileOutput {
1115                commit_id: Some(commit.commit_id.clone()),
1116                tree_id: Some(commit.tree_id.clone()),
1117                blob_id: None,
1118                file_path: Some(input.file_path.clone()),
1119            }),
1120            Err(e) => codecommit_error_response(&e),
1121        }
1122    }
1123
1124    // ---- Pull Requests ----
1125
1126    async fn handle_create_pull_request(
1127        &self,
1128        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
1129        body: &[u8],
1130    ) -> MockResponse {
1131        let input = match wire::deserialize_create_pull_request_request(body) {
1132            Ok(v) => v,
1133            Err(e) => return json_error_response(400, "ValidationException", &e),
1134        };
1135        if input.title.is_empty() {
1136            return json_error_response(
1137                400,
1138                "TitleRequiredException",
1139                "Pull request title is required",
1140            );
1141        }
1142        let description = input.description.as_deref().unwrap_or("");
1143
1144        if input.targets.is_empty() {
1145            return json_error_response(
1146                400,
1147                "TargetsRequiredException",
1148                "At least one target is required",
1149            );
1150        }
1151
1152        let target = &input.targets[0];
1153        if target.repository_name.is_empty() {
1154            return json_error_response(
1155                400,
1156                "RepositoryNameRequiredException",
1157                "Repository name is required in target",
1158            );
1159        }
1160        if target.source_reference.is_empty() {
1161            return json_error_response(
1162                400,
1163                "SourceBranchRequiredException",
1164                "Source reference is required",
1165            );
1166        }
1167        let dest_ref = target
1168            .destination_reference
1169            .as_deref()
1170            .unwrap_or("refs/heads/main");
1171
1172        let mut state = state.write().await;
1173        match state.create_pull_request(
1174            &input.title,
1175            description,
1176            &target.repository_name,
1177            &target.source_reference,
1178            dest_ref,
1179        ) {
1180            Ok(pr) => {
1181                wire::serialize_create_pull_request_response(&wire::CreatePullRequestOutput {
1182                    pull_request: Some(pr_to_wire(&pr)),
1183                })
1184            }
1185            Err(e) => codecommit_error_response(&e),
1186        }
1187    }
1188
1189    async fn handle_get_pull_request(
1190        &self,
1191        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
1192        body: &[u8],
1193    ) -> MockResponse {
1194        let input = match wire::deserialize_get_pull_request_request(body) {
1195            Ok(v) => v,
1196            Err(e) => return json_error_response(400, "ValidationException", &e),
1197        };
1198        if input.pull_request_id.is_empty() {
1199            return json_error_response(
1200                400,
1201                "PullRequestIdRequiredException",
1202                "Pull request ID is required",
1203            );
1204        }
1205
1206        let state = state.read().await;
1207        match state.get_pull_request(&input.pull_request_id) {
1208            Ok(pr) => wire::serialize_get_pull_request_response(&wire::GetPullRequestOutput {
1209                pull_request: Some(pr_to_wire(pr)),
1210            }),
1211            Err(e) => codecommit_error_response(&e),
1212        }
1213    }
1214
1215    async fn handle_list_pull_requests(
1216        &self,
1217        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
1218        body: &[u8],
1219    ) -> MockResponse {
1220        let input = match wire::deserialize_list_pull_requests_request(body) {
1221            Ok(v) => v,
1222            Err(e) => return json_error_response(400, "ValidationException", &e),
1223        };
1224        if input.repository_name.is_empty() {
1225            return json_error_response(
1226                400,
1227                "RepositoryNameRequiredException",
1228                "Repository name is required",
1229            );
1230        }
1231        let status = input.pull_request_status.as_deref();
1232
1233        let state = state.read().await;
1234        let pr_ids = state.list_pull_requests(&input.repository_name, status);
1235        wire::serialize_list_pull_requests_response(&wire::ListPullRequestsOutput {
1236            pull_request_ids: Some(pr_ids),
1237            next_token: None,
1238        })
1239    }
1240
1241    async fn handle_update_pull_request_status(
1242        &self,
1243        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
1244        body: &[u8],
1245    ) -> MockResponse {
1246        let input = match wire::deserialize_update_pull_request_status_request(body) {
1247            Ok(v) => v,
1248            Err(e) => return json_error_response(400, "ValidationException", &e),
1249        };
1250        if input.pull_request_id.is_empty() {
1251            return json_error_response(
1252                400,
1253                "PullRequestIdRequiredException",
1254                "Pull request ID is required",
1255            );
1256        }
1257        if input.pull_request_status.is_empty() {
1258            return json_error_response(
1259                400,
1260                "PullRequestStatusRequiredException",
1261                "Pull request status is required",
1262            );
1263        }
1264
1265        let mut state = state.write().await;
1266        match state.update_pull_request_status(&input.pull_request_id, &input.pull_request_status) {
1267            Ok(pr) => wire::serialize_update_pull_request_status_response(
1268                &wire::UpdatePullRequestStatusOutput {
1269                    pull_request: Some(pr_to_wire(&pr)),
1270                },
1271            ),
1272            Err(e) => codecommit_error_response(&e),
1273        }
1274    }
1275
1276    // ---- Tags ----
1277
1278    async fn handle_tag_resource(
1279        &self,
1280        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
1281        body: &[u8],
1282    ) -> MockResponse {
1283        let input = match wire::deserialize_tag_resource_request(body) {
1284            Ok(v) => v,
1285            Err(e) => return json_error_response(400, "ValidationException", &e),
1286        };
1287        if input.resource_arn.is_empty() {
1288            return json_error_response(
1289                400,
1290                "ResourceArnRequiredException",
1291                "Resource ARN is required",
1292            );
1293        }
1294
1295        // Find repo name from ARN (last segment)
1296        let repo_name = input.resource_arn.split(':').next_back().unwrap_or("");
1297
1298        let mut state = state.write().await;
1299        match state.tag_resource(repo_name, input.tags) {
1300            Ok(()) => wire::serialize_tag_resource_response(),
1301            Err(e) => codecommit_error_response(&e),
1302        }
1303    }
1304
1305    async fn handle_untag_resource(
1306        &self,
1307        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
1308        body: &[u8],
1309    ) -> MockResponse {
1310        let input = match wire::deserialize_untag_resource_request(body) {
1311            Ok(v) => v,
1312            Err(e) => return json_error_response(400, "ValidationException", &e),
1313        };
1314        if input.resource_arn.is_empty() {
1315            return json_error_response(
1316                400,
1317                "ResourceArnRequiredException",
1318                "Resource ARN is required",
1319            );
1320        }
1321
1322        let repo_name = input.resource_arn.split(':').next_back().unwrap_or("");
1323
1324        let mut state = state.write().await;
1325        match state.untag_resource(repo_name, &input.tag_keys) {
1326            Ok(()) => wire::serialize_untag_resource_response(),
1327            Err(e) => codecommit_error_response(&e),
1328        }
1329    }
1330
1331    async fn handle_list_tags_for_resource(
1332        &self,
1333        state: &Arc<tokio::sync::RwLock<CodeCommitState>>,
1334        body: &[u8],
1335    ) -> MockResponse {
1336        let input = match wire::deserialize_list_tags_for_resource_request(body) {
1337            Ok(v) => v,
1338            Err(e) => return json_error_response(400, "ValidationException", &e),
1339        };
1340        if input.resource_arn.is_empty() {
1341            return json_error_response(
1342                400,
1343                "ResourceArnRequiredException",
1344                "Resource ARN is required",
1345            );
1346        }
1347
1348        let state = state.read().await;
1349        match state.list_tags_for_resource(&input.resource_arn) {
1350            Ok(tags) => {
1351                wire::serialize_list_tags_for_resource_response(&wire::ListTagsForResourceOutput {
1352                    tags: Some(tags),
1353                    next_token: None,
1354                })
1355            }
1356            Err(e) => codecommit_error_response(&e),
1357        }
1358    }
1359}
1360
1361fn repo_to_wire(repo: &crate::types::Repository) -> wire::RepositoryMetadata {
1362    wire::RepositoryMetadata {
1363        account_id: Some(repo.account_id.clone()),
1364        repository_id: Some(repo.repository_id.clone()),
1365        repository_name: Some(repo.repository_name.clone()),
1366        repository_description: Some(repo.description.clone()),
1367        arn: Some(repo.arn.clone()),
1368        clone_url_http: Some(repo.clone_url_http.clone()),
1369        clone_url_ssh: Some(repo.clone_url_ssh.clone()),
1370        creation_date: Some(repo.creation_date.timestamp() as f64),
1371        last_modified_date: Some(repo.last_modified_date.timestamp() as f64),
1372        default_branch: repo.default_branch.clone(),
1373        ..Default::default()
1374    }
1375}
1376
1377fn commit_to_wire(commit: &crate::types::CommitRecord) -> wire::Commit {
1378    wire::Commit {
1379        commit_id: Some(commit.commit_id.clone()),
1380        tree_id: Some(commit.tree_id.clone()),
1381        parents: Some(commit.parent_ids.clone()),
1382        message: Some(commit.message.clone()),
1383        author: Some(wire::UserInfo {
1384            name: Some(commit.author_name.clone()),
1385            email: Some(commit.author_email.clone()),
1386            date: Some(commit.date.to_rfc3339()),
1387        }),
1388        committer: Some(wire::UserInfo {
1389            name: Some(commit.author_name.clone()),
1390            email: Some(commit.author_email.clone()),
1391            date: Some(commit.date.to_rfc3339()),
1392        }),
1393        additional_data: None,
1394    }
1395}
1396
1397fn pr_to_wire(pr: &crate::types::PullRequestRecord) -> wire::PullRequest {
1398    wire::PullRequest {
1399        pull_request_id: Some(pr.pull_request_id.clone()),
1400        title: Some(pr.title.clone()),
1401        description: Some(pr.description.clone()),
1402        pull_request_status: Some(pr.status.clone()),
1403        creation_date: Some(pr.creation_date.timestamp() as f64),
1404        last_activity_date: Some(pr.last_activity_date.timestamp() as f64),
1405        author_arn: Some(pr.author_arn.clone()),
1406        revision_id: None,
1407        client_request_token: None,
1408        approval_rules: None,
1409        pull_request_targets: Some(vec![wire::PullRequestTarget {
1410            repository_name: Some(pr.repository_name.clone()),
1411            source_reference: Some(pr.source_reference.clone()),
1412            destination_reference: Some(pr.destination_reference.clone()),
1413            source_commit: Some(pr.source_commit.clone()),
1414            destination_commit: Some(pr.destination_commit.clone()),
1415            merge_base: None,
1416            merge_metadata: Some(wire::MergeMetadata {
1417                is_merged: Some(pr.status == "CLOSED"),
1418                merge_commit_id: None,
1419                merge_option: None,
1420                merged_by: None,
1421            }),
1422        }]),
1423    }
1424}
1425
1426fn codecommit_error_response(err: &CodeCommitError) -> MockResponse {
1427    let (status, error_type) = match err {
1428        CodeCommitError::RepositoryAlreadyExists { .. } => {
1429            (400u16, "RepositoryNameExistsException")
1430        }
1431        CodeCommitError::RepositoryNameTaken { .. } => (400, "RepositoryNameExistsException"),
1432        CodeCommitError::RepositoryDoesNotExist { .. } => (400, "RepositoryDoesNotExistException"),
1433        CodeCommitError::RepositoryDoesNotExistByArn { .. } => {
1434            (400, "RepositoryDoesNotExistException")
1435        }
1436        CodeCommitError::BranchAlreadyExists { .. } => (400, "BranchNameExistsException"),
1437        CodeCommitError::BranchDoesNotExist { .. } => (400, "BranchDoesNotExistException"),
1438        CodeCommitError::BranchNotFound { .. } => (400, "BranchDoesNotExistException"),
1439        CodeCommitError::DefaultBranchCannotBeDeleted => {
1440            (400, "DefaultBranchCannotBeDeletedException")
1441        }
1442        CodeCommitError::CommitDoesNotExist { .. } => (400, "CommitDoesNotExistException"),
1443        CodeCommitError::SpecifierDoesNotResolve { .. } => (400, "CommitDoesNotExistException"),
1444        CodeCommitError::FileDoesNotExist { .. } => (400, "FileDoesNotExistException"),
1445        CodeCommitError::PullRequestDoesNotExist { .. } => {
1446            (400, "PullRequestDoesNotExistException")
1447        }
1448        CodeCommitError::RepositoryEmpty => (400, "RepositoryEmptyException"),
1449    };
1450    let body = json!({
1451        "__type": error_type,
1452        "message": err.to_string(),
1453    });
1454    MockResponse::json(status, body.to_string())
1455}