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