1use std::io::{IsTerminal, stderr, stdout};
8
9use clap::{Parser, Subcommand, ValueEnum};
10
11use crate::exit_code::ExitCode;
12use crate::output::OutputConfig;
13
14mod admin;
15mod alias;
16mod anonymous;
17mod bucket;
18mod cat;
19mod completions;
20mod cors;
21pub mod cp;
22pub mod diff;
23mod event;
24mod find;
25mod head;
26mod ilm;
27mod ls;
28mod mb;
29mod mirror;
30mod mv;
31mod object;
32mod pipe;
33mod quota;
34mod rb;
35mod replicate;
36mod rm;
37mod share;
38mod stat;
39mod tag;
40mod tree;
41mod version;
42
43#[derive(Parser, Debug)]
48#[command(name = "rc")]
49#[command(author, version, about, long_about = None)]
50#[command(propagate_version = true)]
51pub struct Cli {
52 #[arg(long, global = true, value_enum)]
54 pub format: Option<OutputFormat>,
55
56 #[arg(long, global = true, default_value = "false")]
58 pub json: bool,
59
60 #[arg(long, global = true, default_value = "false")]
62 pub no_color: bool,
63
64 #[arg(long, global = true, default_value = "false")]
66 pub no_progress: bool,
67
68 #[arg(short, long, global = true, default_value = "false")]
70 pub quiet: bool,
71
72 #[arg(long, global = true, default_value = "false")]
74 pub debug: bool,
75
76 #[command(subcommand)]
77 pub command: Commands,
78}
79
80#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
81pub enum OutputFormat {
82 Auto,
83 Human,
84 Json,
85}
86
87#[derive(Copy, Clone, Debug, Eq, PartialEq)]
88enum OutputBehavior {
89 HumanDefault,
90 StructuredDefault,
91}
92
93#[derive(Copy, Clone, Debug)]
94struct GlobalOutputOptions {
95 format: Option<OutputFormat>,
96 json: bool,
97 no_color: bool,
98 no_progress: bool,
99 quiet: bool,
100}
101
102impl GlobalOutputOptions {
103 fn from_cli(cli: &Cli) -> Self {
104 Self {
105 format: cli.format,
106 json: cli.json,
107 no_color: cli.no_color,
108 no_progress: cli.no_progress,
109 quiet: cli.quiet,
110 }
111 }
112
113 fn resolve(self, behavior: OutputBehavior) -> OutputConfig {
114 let stdout_is_tty = stdout().is_terminal();
115 let stderr_is_tty = stderr().is_terminal();
116
117 let selected_format = if self.json {
118 OutputFormat::Json
119 } else {
120 self.format.unwrap_or(match behavior {
121 OutputBehavior::HumanDefault => OutputFormat::Human,
122 OutputBehavior::StructuredDefault => OutputFormat::Auto,
123 })
124 };
125
126 let json = match selected_format {
127 OutputFormat::Json => true,
128 OutputFormat::Human => false,
129 OutputFormat::Auto => !stdout_is_tty,
130 };
131
132 OutputConfig {
133 json,
134 no_color: self.no_color || !stdout_is_tty || json,
135 no_progress: self.no_progress || !stderr_is_tty || json,
136 quiet: self.quiet,
137 }
138 }
139}
140
141#[derive(Subcommand, Debug)]
142pub enum Commands {
143 #[command(subcommand)]
145 Alias(alias::AliasCommands),
146
147 #[command(subcommand)]
149 Admin(admin::AdminCommands),
150
151 Bucket(bucket::BucketArgs),
153
154 Object(object::ObjectArgs),
156
157 Ls(ls::LsArgs),
160
161 Mb(mb::MbArgs),
163
164 Rb(rb::RbArgs),
166
167 Cat(cat::CatArgs),
169
170 Head(head::HeadArgs),
172
173 Stat(stat::StatArgs),
175
176 Cp(cp::CpArgs),
179
180 Mv(mv::MvArgs),
182
183 Rm(rm::RmArgs),
185
186 Pipe(pipe::PipeArgs),
188
189 Find(find::FindArgs),
192
193 Event(event::EventArgs),
195
196 #[command(subcommand)]
198 Cors(cors::CorsCommands),
199
200 Diff(diff::DiffArgs),
202
203 Mirror(mirror::MirrorArgs),
205
206 Tree(tree::TreeArgs),
208
209 Share(share::ShareArgs),
211
212 #[command(subcommand)]
215 Version(version::VersionCommands),
216
217 #[command(subcommand)]
219 Tag(tag::TagCommands),
220
221 #[command(subcommand)]
223 Anonymous(anonymous::AnonymousCommands),
224
225 #[command(subcommand)]
227 Quota(quota::QuotaCommands),
228
229 Ilm(ilm::IlmArgs),
231
232 Replicate(replicate::ReplicateArgs),
234
235 Completions(completions::CompletionsArgs),
238 }
245
246pub async fn execute(cli: Cli) -> ExitCode {
248 let output_options = GlobalOutputOptions::from_cli(&cli);
249
250 match cli.command {
251 Commands::Alias(cmd) => {
252 alias::execute(cmd, output_options.resolve(OutputBehavior::HumanDefault)).await
253 }
254 Commands::Admin(cmd) => {
255 admin::execute(cmd, output_options.resolve(OutputBehavior::HumanDefault)).await
256 }
257 Commands::Bucket(args) => {
258 bucket::execute(
259 args,
260 output_options.resolve(OutputBehavior::StructuredDefault),
261 )
262 .await
263 }
264 Commands::Object(args) => {
265 let behavior = match &args.command {
266 object::ObjectCommands::Show(_) | object::ObjectCommands::Head(_) => {
267 OutputBehavior::HumanDefault
268 }
269 _ => OutputBehavior::StructuredDefault,
270 };
271 object::execute(args, output_options.resolve(behavior)).await
272 }
273 Commands::Ls(args) => {
274 ls::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
275 }
276 Commands::Mb(args) => {
277 mb::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
278 }
279 Commands::Rb(args) => {
280 rb::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
281 }
282 Commands::Cat(args) => {
283 cat::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
284 }
285 Commands::Head(args) => {
286 head::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
287 }
288 Commands::Stat(args) => {
289 stat::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
290 }
291 Commands::Cp(args) => {
292 cp::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
293 }
294 Commands::Mv(args) => {
295 mv::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
296 }
297 Commands::Rm(args) => {
298 rm::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
299 }
300 Commands::Pipe(args) => {
301 pipe::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
302 }
303 Commands::Find(args) => {
304 find::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
305 }
306 Commands::Event(args) => {
307 event::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
308 }
309 Commands::Cors(cmd) => {
310 cors::execute(
311 cors::CorsArgs { command: cmd },
312 output_options.resolve(OutputBehavior::HumanDefault),
313 )
314 .await
315 }
316 Commands::Diff(args) => {
317 diff::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
318 }
319 Commands::Mirror(args) => {
320 mirror::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
321 }
322 Commands::Tree(args) => {
323 tree::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
324 }
325 Commands::Share(args) => {
326 share::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
327 }
328 Commands::Version(cmd) => {
329 version::execute(
330 version::VersionArgs { command: cmd },
331 output_options.resolve(OutputBehavior::HumanDefault),
332 )
333 .await
334 }
335 Commands::Tag(cmd) => {
336 tag::execute(
337 tag::TagArgs { command: cmd },
338 output_options.resolve(OutputBehavior::HumanDefault),
339 )
340 .await
341 }
342 Commands::Anonymous(cmd) => {
343 anonymous::execute(
344 anonymous::AnonymousArgs { command: cmd },
345 output_options.resolve(OutputBehavior::HumanDefault),
346 )
347 .await
348 }
349 Commands::Quota(cmd) => {
350 quota::execute(
351 quota::QuotaArgs { command: cmd },
352 output_options.resolve(OutputBehavior::HumanDefault),
353 )
354 .await
355 }
356 Commands::Ilm(args) => {
357 ilm::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
358 }
359 Commands::Replicate(args) => {
360 replicate::execute(args, output_options.resolve(OutputBehavior::HumanDefault)).await
361 }
362 Commands::Completions(args) => completions::execute(args),
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use clap::Parser;
370
371 #[test]
372 fn structured_default_uses_auto_format_when_not_explicit() {
373 let options = GlobalOutputOptions {
374 format: None,
375 json: false,
376 no_color: false,
377 no_progress: false,
378 quiet: false,
379 };
380
381 let resolved = options.resolve(OutputBehavior::StructuredDefault);
382 assert_eq!(resolved.json, !std::io::stdout().is_terminal());
383 }
384
385 #[test]
386 fn human_default_keeps_human_format_when_not_explicit() {
387 let options = GlobalOutputOptions {
388 format: None,
389 json: false,
390 no_color: false,
391 no_progress: false,
392 quiet: false,
393 };
394
395 let resolved = options.resolve(OutputBehavior::HumanDefault);
396 assert!(!resolved.json);
397 }
398
399 #[test]
400 fn explicit_json_overrides_behavior_defaults() {
401 let options = GlobalOutputOptions {
402 format: Some(OutputFormat::Human),
403 json: true,
404 no_color: false,
405 no_progress: false,
406 quiet: false,
407 };
408
409 let resolved = options.resolve(OutputBehavior::HumanDefault);
410 assert!(resolved.json);
411 }
412
413 #[test]
414 fn explicit_human_overrides_structured_default() {
415 let options = GlobalOutputOptions {
416 format: Some(OutputFormat::Human),
417 json: false,
418 no_color: false,
419 no_progress: false,
420 quiet: false,
421 };
422
423 let resolved = options.resolve(OutputBehavior::StructuredDefault);
424 assert!(!resolved.json);
425 }
426
427 #[test]
428 fn explicit_auto_overrides_human_default() {
429 let options = GlobalOutputOptions {
430 format: Some(OutputFormat::Auto),
431 json: false,
432 no_color: false,
433 no_progress: false,
434 quiet: false,
435 };
436
437 let resolved = options.resolve(OutputBehavior::HumanDefault);
438 assert_eq!(resolved.json, !std::io::stdout().is_terminal());
439 }
440
441 #[test]
442 fn cli_accepts_bucket_cors_subcommand() {
443 let cli = Cli::try_parse_from(["rc", "bucket", "cors", "list", "local/my-bucket"])
444 .expect("parse bucket cors");
445
446 match cli.command {
447 Commands::Bucket(args) => match args.command {
448 bucket::BucketCommands::Cors(cors::CorsCommands::List(arg)) => {
449 assert_eq!(arg.path, "local/my-bucket");
450 }
451 other => panic!("expected bucket cors list command, got {:?}", other),
452 },
453 other => panic!("expected bucket command, got {:?}", other),
454 }
455 }
456
457 #[test]
458 fn cli_accepts_bucket_list_alias() {
459 let cli =
460 Cli::try_parse_from(["rc", "bucket", "ls", "local/"]).expect("parse bucket ls alias");
461
462 match cli.command {
463 Commands::Bucket(args) => match args.command {
464 bucket::BucketCommands::List(arg) => {
465 assert_eq!(arg.path, "local/");
466 }
467 other => panic!("expected bucket list alias, got {:?}", other),
468 },
469 other => panic!("expected bucket command, got {:?}", other),
470 }
471 }
472
473 #[test]
474 fn cli_accepts_top_level_cors_subcommand() {
475 let cli = Cli::try_parse_from(["rc", "cors", "remove", "local/my-bucket"])
476 .expect("parse top-level cors");
477
478 match cli.command {
479 Commands::Cors(cors::CorsCommands::Remove(arg)) => {
480 assert_eq!(arg.path, "local/my-bucket");
481 }
482 other => panic!("expected top-level cors remove command, got {:?}", other),
483 }
484 }
485
486 #[test]
487 fn cli_accepts_top_level_cors_get_alias() {
488 let cli =
489 Cli::try_parse_from(["rc", "cors", "get", "local/my-bucket"]).expect("parse cors get");
490
491 match cli.command {
492 Commands::Cors(cors::CorsCommands::List(arg)) => {
493 assert_eq!(arg.path, "local/my-bucket");
494 }
495 other => panic!("expected top-level cors get alias, got {:?}", other),
496 }
497 }
498
499 #[test]
500 fn cli_accepts_top_level_event_subcommand() {
501 let cli = Cli::try_parse_from(["rc", "event", "list", "local/my-bucket"])
502 .expect("parse top-level event");
503
504 match cli.command {
505 Commands::Event(event::EventArgs {
506 command: event::EventCommands::List(arg),
507 }) => {
508 assert_eq!(arg.path, "local/my-bucket");
509 }
510 other => panic!("expected top-level event list command, got {:?}", other),
511 }
512 }
513
514 #[test]
515 fn cli_accepts_top_level_event_add_subcommand() {
516 let cli = Cli::try_parse_from([
517 "rc",
518 "event",
519 "add",
520 "local/my-bucket",
521 "arn:aws:sqs:us-east-1:123456789012:jobs",
522 "--event",
523 "put,delete",
524 "--force",
525 ])
526 .expect("parse top-level event add");
527
528 match cli.command {
529 Commands::Event(event::EventArgs {
530 command: event::EventCommands::Add(arg),
531 }) => {
532 assert_eq!(arg.path, "local/my-bucket");
533 assert_eq!(arg.arn, "arn:aws:sqs:us-east-1:123456789012:jobs");
534 assert_eq!(arg.events, vec!["put,delete".to_string()]);
535 assert!(arg.force);
536 }
537 other => panic!("expected top-level event add command, got {:?}", other),
538 }
539 }
540
541 #[test]
542 fn cli_accepts_object_list_alias() {
543 let cli = Cli::try_parse_from(["rc", "object", "ls", "local/my-bucket/logs/"])
544 .expect("parse object ls alias");
545
546 match cli.command {
547 Commands::Object(args) => match args.command {
548 object::ObjectCommands::List(arg) => {
549 assert_eq!(arg.path, "local/my-bucket/logs/");
550 }
551 other => panic!("expected object list alias, got {:?}", other),
552 },
553 other => panic!("expected object command, got {:?}", other),
554 }
555 }
556
557 #[test]
558 fn cli_accepts_top_level_event_remove_subcommand() {
559 let cli = Cli::try_parse_from([
560 "rc",
561 "event",
562 "remove",
563 "local/my-bucket",
564 "arn:aws:sns:us-east-1:123456789012:alerts",
565 "--force",
566 ])
567 .expect("parse top-level event remove");
568
569 match cli.command {
570 Commands::Event(event::EventArgs {
571 command: event::EventCommands::Remove(arg),
572 }) => {
573 assert_eq!(arg.path, "local/my-bucket");
574 assert_eq!(arg.arn, "arn:aws:sns:us-east-1:123456789012:alerts");
575 assert!(arg.force);
576 }
577 other => panic!("expected top-level event remove command, got {:?}", other),
578 }
579 }
580
581 #[test]
582 fn cli_accepts_bucket_cors_get_alias() {
583 let cli = Cli::try_parse_from(["rc", "bucket", "cors", "get", "local/my-bucket"])
584 .expect("parse bucket cors get");
585
586 match cli.command {
587 Commands::Bucket(args) => match args.command {
588 bucket::BucketCommands::Cors(cors::CorsCommands::List(arg)) => {
589 assert_eq!(arg.path, "local/my-bucket");
590 }
591 other => panic!("expected bucket cors get alias, got {:?}", other),
592 },
593 other => panic!("expected bucket command, got {:?}", other),
594 }
595 }
596
597 #[test]
598 fn cli_accepts_bucket_cors_set_with_positional_source() {
599 let cli =
600 Cli::try_parse_from(["rc", "bucket", "cors", "set", "local/my-bucket", "cors.xml"])
601 .expect("parse bucket cors set with positional source");
602
603 match cli.command {
604 Commands::Bucket(args) => match args.command {
605 bucket::BucketCommands::Cors(cors::CorsCommands::Set(arg)) => {
606 assert_eq!(arg.path, "local/my-bucket");
607 assert_eq!(arg.source.as_deref(), Some("cors.xml"));
608 }
609 other => panic!("expected bucket cors set command, got {:?}", other),
610 },
611 other => panic!("expected bucket command, got {:?}", other),
612 }
613 }
614
615 #[test]
616 fn cli_accepts_top_level_cors_set_with_positional_source() {
617 let cli = Cli::try_parse_from(["rc", "cors", "set", "local/my-bucket", "cors.xml"])
618 .expect("parse top-level cors set with positional source");
619
620 match cli.command {
621 Commands::Cors(cors::CorsCommands::Set(arg)) => {
622 assert_eq!(arg.path, "local/my-bucket");
623 assert_eq!(arg.source.as_deref(), Some("cors.xml"));
624 assert_eq!(arg.file, None);
625 assert!(!arg.force);
626 }
627 other => panic!("expected top-level cors set command, got {:?}", other),
628 }
629 }
630
631 #[test]
632 fn cli_accepts_top_level_cors_set_with_legacy_file_flag() {
633 let cli = Cli::try_parse_from([
634 "rc",
635 "cors",
636 "set",
637 "local/my-bucket",
638 "--file",
639 "cors.json",
640 "--force",
641 ])
642 .expect("parse top-level cors set with --file");
643
644 match cli.command {
645 Commands::Cors(cors::CorsCommands::Set(arg)) => {
646 assert_eq!(arg.path, "local/my-bucket");
647 assert_eq!(arg.source, None);
648 assert_eq!(arg.file.as_deref(), Some("cors.json"));
649 assert!(arg.force);
650 }
651 other => panic!("expected top-level cors set command, got {:?}", other),
652 }
653 }
654
655 #[test]
656 fn cli_accepts_bucket_cors_list_force_flag() {
657 let cli =
658 Cli::try_parse_from(["rc", "bucket", "cors", "list", "local/my-bucket", "--force"])
659 .expect("parse bucket cors list with force");
660
661 match cli.command {
662 Commands::Bucket(args) => match args.command {
663 bucket::BucketCommands::Cors(cors::CorsCommands::List(arg)) => {
664 assert_eq!(arg.path, "local/my-bucket");
665 assert!(arg.force);
666 }
667 other => panic!("expected bucket cors list command, got {:?}", other),
668 },
669 other => panic!("expected bucket command, got {:?}", other),
670 }
671 }
672
673 #[test]
674 fn cli_accepts_bucket_lifecycle_subcommand() {
675 let cli = Cli::try_parse_from([
676 "rc",
677 "bucket",
678 "lifecycle",
679 "rule",
680 "list",
681 "local/my-bucket",
682 ])
683 .expect("parse bucket lifecycle rule list");
684
685 match cli.command {
686 Commands::Bucket(args) => match args.command {
687 bucket::BucketCommands::Lifecycle(ilm::IlmArgs {
688 command: ilm::IlmCommands::Rule(ilm::rule::RuleCommands::List(arg)),
689 }) => {
690 assert_eq!(arg.path, "local/my-bucket");
691 assert!(!arg.force);
692 }
693 other => panic!(
694 "expected bucket lifecycle rule list command, got {:?}",
695 other
696 ),
697 },
698 other => panic!("expected bucket command, got {:?}", other),
699 }
700 }
701
702 #[test]
703 fn cli_accepts_bucket_replication_subcommand() {
704 let cli = Cli::try_parse_from(["rc", "bucket", "replication", "status", "local/my-bucket"])
705 .expect("parse bucket replication status");
706
707 match cli.command {
708 Commands::Bucket(args) => match args.command {
709 bucket::BucketCommands::Replication(replicate::ReplicateArgs {
710 command: replicate::ReplicateCommands::Status(arg),
711 }) => {
712 assert_eq!(arg.path, "local/my-bucket");
713 assert!(!arg.force);
714 }
715 other => panic!(
716 "expected bucket replication status command, got {:?}",
717 other
718 ),
719 },
720 other => panic!("expected bucket command, got {:?}", other),
721 }
722 }
723
724 #[test]
725 fn cli_accepts_bucket_remove_subcommand() {
726 let cli = Cli::try_parse_from(["rc", "bucket", "remove", "local/my-bucket"])
727 .expect("parse bucket remove");
728
729 match cli.command {
730 Commands::Bucket(args) => match args.command {
731 bucket::BucketCommands::Remove(arg) => {
732 assert_eq!(arg.target, "local/my-bucket");
733 }
734 other => panic!("expected bucket remove command, got {:?}", other),
735 },
736 other => panic!("expected bucket command, got {:?}", other),
737 }
738 }
739
740 #[test]
741 fn cli_accepts_object_remove_subcommand() {
742 let cli = Cli::try_parse_from([
743 "rc",
744 "object",
745 "remove",
746 "local/my-bucket/report.csv",
747 "--dry-run",
748 ])
749 .expect("parse object remove");
750
751 match cli.command {
752 Commands::Object(args) => match args.command {
753 object::ObjectCommands::Remove(arg) => {
754 assert_eq!(arg.paths, vec!["local/my-bucket/report.csv".to_string()]);
755 assert!(arg.dry_run);
756 }
757 other => panic!("expected object remove command, got {:?}", other),
758 },
759 other => panic!("expected object command, got {:?}", other),
760 }
761 }
762
763 #[test]
764 fn cli_accepts_bucket_event_remove_subcommand() {
765 let cli = Cli::try_parse_from([
766 "rc",
767 "bucket",
768 "event",
769 "remove",
770 "local/my-bucket",
771 "arn:aws:sns:us-east-1:123456789012:alerts",
772 ])
773 .expect("parse bucket event remove");
774
775 match cli.command {
776 Commands::Bucket(args) => match args.command {
777 bucket::BucketCommands::Event(event::EventCommands::Remove(arg)) => {
778 assert_eq!(arg.path, "local/my-bucket");
779 assert_eq!(arg.arn, "arn:aws:sns:us-east-1:123456789012:alerts");
780 }
781 other => panic!("expected bucket event remove command, got {:?}", other),
782 },
783 other => panic!("expected bucket command, got {:?}", other),
784 }
785 }
786
787 #[test]
788 fn cli_accepts_rm_purge_flag() {
789 let cli = Cli::try_parse_from(["rc", "rm", "local/my-bucket/object.txt", "--purge"])
790 .expect("parse rm purge");
791
792 match cli.command {
793 Commands::Rm(arg) => {
794 assert_eq!(arg.paths, vec!["local/my-bucket/object.txt".to_string()]);
795 assert!(arg.purge);
796 }
797 other => panic!("expected rm command, got {:?}", other),
798 }
799 }
800
801 #[test]
802 fn cli_accepts_object_remove_purge_flag() {
803 let cli = Cli::try_parse_from([
804 "rc",
805 "object",
806 "remove",
807 "local/my-bucket/object.txt",
808 "--purge",
809 ])
810 .expect("parse object remove purge");
811
812 match cli.command {
813 Commands::Object(args) => match args.command {
814 object::ObjectCommands::Remove(arg) => {
815 assert_eq!(arg.paths, vec!["local/my-bucket/object.txt".to_string()]);
816 assert!(arg.purge);
817 }
818 other => panic!("expected object remove command, got {:?}", other),
819 },
820 other => panic!("expected object command, got {:?}", other),
821 }
822 }
823
824 #[test]
825 fn cli_accepts_object_stat_subcommand() {
826 let cli = Cli::try_parse_from(["rc", "object", "stat", "local/my-bucket/report.json"])
827 .expect("parse object stat");
828
829 match cli.command {
830 Commands::Object(args) => match args.command {
831 object::ObjectCommands::Stat(arg) => {
832 assert_eq!(arg.path, "local/my-bucket/report.json");
833 }
834 other => panic!("expected object stat command, got {:?}", other),
835 },
836 other => panic!("expected object command, got {:?}", other),
837 }
838 }
839
840 #[test]
841 fn cli_accepts_object_copy_with_transfer_options() {
842 let cli = Cli::try_parse_from([
843 "rc",
844 "object",
845 "copy",
846 "./report.json",
847 "local/my-bucket/reports/",
848 "--content-type",
849 "application/json",
850 "--storage-class",
851 "STANDARD_IA",
852 "--dry-run",
853 ])
854 .expect("parse object copy with transfer options");
855
856 match cli.command {
857 Commands::Object(args) => match args.command {
858 object::ObjectCommands::Copy(arg) => {
859 assert_eq!(arg.source, "./report.json");
860 assert_eq!(arg.target, "local/my-bucket/reports/");
861 assert_eq!(arg.content_type.as_deref(), Some("application/json"));
862 assert_eq!(arg.storage_class.as_deref(), Some("STANDARD_IA"));
863 assert!(arg.dry_run);
864 }
865 other => panic!("expected object copy command, got {:?}", other),
866 },
867 other => panic!("expected object command, got {:?}", other),
868 }
869 }
870
871 #[test]
872 fn cli_accepts_object_move_with_recursive_dry_run() {
873 let cli = Cli::try_parse_from([
874 "rc",
875 "object",
876 "move",
877 "local/source-bucket/logs/",
878 "local/archive-bucket/logs/",
879 "--recursive",
880 "--dry-run",
881 "--continue-on-error",
882 ])
883 .expect("parse object move with recursive dry-run");
884
885 match cli.command {
886 Commands::Object(args) => match args.command {
887 object::ObjectCommands::Move(arg) => {
888 assert_eq!(arg.source, "local/source-bucket/logs/");
889 assert_eq!(arg.target, "local/archive-bucket/logs/");
890 assert!(arg.recursive);
891 assert!(arg.dry_run);
892 assert!(arg.continue_on_error);
893 }
894 other => panic!("expected object move command, got {:?}", other),
895 },
896 other => panic!("expected object command, got {:?}", other),
897 }
898 }
899
900 #[test]
901 fn cli_accepts_object_show_and_head_options() {
902 let show_cli = Cli::try_parse_from([
903 "rc",
904 "object",
905 "show",
906 "local/my-bucket/report.json",
907 "--version-id",
908 "v1",
909 "--rewind",
910 "1h",
911 ])
912 .expect("parse object show options");
913
914 match show_cli.command {
915 Commands::Object(args) => match args.command {
916 object::ObjectCommands::Show(arg) => {
917 assert_eq!(arg.path, "local/my-bucket/report.json");
918 assert_eq!(arg.version_id.as_deref(), Some("v1"));
919 assert_eq!(arg.rewind.as_deref(), Some("1h"));
920 }
921 other => panic!("expected object show command, got {:?}", other),
922 },
923 other => panic!("expected object command, got {:?}", other),
924 }
925
926 let head_cli = Cli::try_parse_from([
927 "rc",
928 "object",
929 "head",
930 "local/my-bucket/report.json",
931 "--bytes",
932 "128",
933 "--version-id",
934 "v2",
935 ])
936 .expect("parse object head options");
937
938 match head_cli.command {
939 Commands::Object(args) => match args.command {
940 object::ObjectCommands::Head(arg) => {
941 assert_eq!(arg.path, "local/my-bucket/report.json");
942 assert_eq!(arg.bytes, Some(128));
943 assert_eq!(arg.version_id.as_deref(), Some("v2"));
944 }
945 other => panic!("expected object head command, got {:?}", other),
946 },
947 other => panic!("expected object command, got {:?}", other),
948 }
949 }
950
951 #[test]
952 fn cli_accepts_object_find_and_tree_options() {
953 let find_cli = Cli::try_parse_from([
954 "rc",
955 "object",
956 "find",
957 "local/my-bucket/logs/",
958 "--name",
959 "*.json",
960 "--maxdepth",
961 "2",
962 "--count",
963 "--print",
964 ])
965 .expect("parse object find options");
966
967 match find_cli.command {
968 Commands::Object(args) => match args.command {
969 object::ObjectCommands::Find(arg) => {
970 assert_eq!(arg.path, "local/my-bucket/logs/");
971 assert_eq!(arg.name.as_deref(), Some("*.json"));
972 assert_eq!(arg.maxdepth, 2);
973 assert!(arg.count);
974 assert!(arg.print);
975 }
976 other => panic!("expected object find command, got {:?}", other),
977 },
978 other => panic!("expected object command, got {:?}", other),
979 }
980
981 let tree_cli = Cli::try_parse_from([
982 "rc",
983 "object",
984 "tree",
985 "local/my-bucket/logs/",
986 "--level",
987 "4",
988 "--size",
989 "--pattern",
990 "*.json",
991 "--full-path",
992 ])
993 .expect("parse object tree options");
994
995 match tree_cli.command {
996 Commands::Object(args) => match args.command {
997 object::ObjectCommands::Tree(arg) => {
998 assert_eq!(arg.path, "local/my-bucket/logs/");
999 assert_eq!(arg.level, 4);
1000 assert!(arg.size);
1001 assert_eq!(arg.pattern.as_deref(), Some("*.json"));
1002 assert!(arg.full_path);
1003 }
1004 other => panic!("expected object tree command, got {:?}", other),
1005 },
1006 other => panic!("expected object command, got {:?}", other),
1007 }
1008 }
1009}