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_sql_select_options() {
548 let cli = Cli::try_parse_from([
549 "rc",
550 "sql",
551 "local/reports/data.jsonl",
552 "--query",
553 "SELECT * FROM S3Object",
554 "--input-format",
555 "json",
556 "--output-format",
557 "json",
558 "--compression",
559 "gzip",
560 ])
561 .expect("parse sql command");
562
563 match cli.command {
564 Commands::Sql(arg) => {
565 assert_eq!(arg.path, "local/reports/data.jsonl");
566 assert_eq!(arg.query, "SELECT * FROM S3Object");
567 assert!(matches!(arg.input_format, sql::InputFormatArg::Json));
568 assert!(matches!(arg.output_format, sql::OutputFormatArg::Json));
569 assert!(matches!(arg.compression, sql::CompressionArg::Gzip));
570 }
571 other => panic!("expected sql command, got {:?}", other),
572 }
573 }
574
575 #[test]
576 fn cli_accepts_sql_defaults() {
577 let cli = Cli::try_parse_from([
578 "rc",
579 "sql",
580 "local/reports/data.csv",
581 "--query",
582 "SELECT s._1 FROM S3Object s",
583 ])
584 .expect("parse sql command defaults");
585
586 match cli.command {
587 Commands::Sql(arg) => {
588 assert_eq!(arg.path, "local/reports/data.csv");
589 assert_eq!(arg.query, "SELECT s._1 FROM S3Object s");
590 assert!(matches!(arg.input_format, sql::InputFormatArg::Csv));
591 assert!(matches!(arg.output_format, sql::OutputFormatArg::Csv));
592 assert!(matches!(arg.compression, sql::CompressionArg::None));
593 }
594 other => panic!("expected sql command, got {:?}", other),
595 }
596 }
597
598 #[test]
599 fn cli_accepts_object_list_alias() {
600 let cli = Cli::try_parse_from(["rc", "object", "ls", "local/my-bucket/logs/"])
601 .expect("parse object ls alias");
602
603 match cli.command {
604 Commands::Object(args) => match args.command {
605 object::ObjectCommands::List(arg) => {
606 assert_eq!(arg.path, "local/my-bucket/logs/");
607 }
608 other => panic!("expected object list alias, got {:?}", other),
609 },
610 other => panic!("expected object command, got {:?}", other),
611 }
612 }
613
614 #[test]
615 fn cli_accepts_top_level_event_remove_subcommand() {
616 let cli = Cli::try_parse_from([
617 "rc",
618 "event",
619 "remove",
620 "local/my-bucket",
621 "arn:aws:sns:us-east-1:123456789012:alerts",
622 "--force",
623 ])
624 .expect("parse top-level event remove");
625
626 match cli.command {
627 Commands::Event(event::EventArgs {
628 command: event::EventCommands::Remove(arg),
629 }) => {
630 assert_eq!(arg.path, "local/my-bucket");
631 assert_eq!(arg.arn, "arn:aws:sns:us-east-1:123456789012:alerts");
632 assert!(arg.force);
633 }
634 other => panic!("expected top-level event remove command, got {:?}", other),
635 }
636 }
637
638 #[test]
639 fn cli_accepts_bucket_cors_get_alias() {
640 let cli = Cli::try_parse_from(["rc", "bucket", "cors", "get", "local/my-bucket"])
641 .expect("parse bucket cors get");
642
643 match cli.command {
644 Commands::Bucket(args) => match args.command {
645 bucket::BucketCommands::Cors(cors::CorsCommands::List(arg)) => {
646 assert_eq!(arg.path, "local/my-bucket");
647 }
648 other => panic!("expected bucket cors get alias, got {:?}", other),
649 },
650 other => panic!("expected bucket command, got {:?}", other),
651 }
652 }
653
654 #[test]
655 fn cli_accepts_bucket_cors_set_with_positional_source() {
656 let cli =
657 Cli::try_parse_from(["rc", "bucket", "cors", "set", "local/my-bucket", "cors.xml"])
658 .expect("parse bucket cors set with positional source");
659
660 match cli.command {
661 Commands::Bucket(args) => match args.command {
662 bucket::BucketCommands::Cors(cors::CorsCommands::Set(arg)) => {
663 assert_eq!(arg.path, "local/my-bucket");
664 assert_eq!(arg.source.as_deref(), Some("cors.xml"));
665 }
666 other => panic!("expected bucket cors set command, got {:?}", other),
667 },
668 other => panic!("expected bucket command, got {:?}", other),
669 }
670 }
671
672 #[test]
673 fn cli_accepts_top_level_cors_set_with_positional_source() {
674 let cli = Cli::try_parse_from(["rc", "cors", "set", "local/my-bucket", "cors.xml"])
675 .expect("parse top-level cors set with positional source");
676
677 match cli.command {
678 Commands::Cors(cors::CorsCommands::Set(arg)) => {
679 assert_eq!(arg.path, "local/my-bucket");
680 assert_eq!(arg.source.as_deref(), Some("cors.xml"));
681 assert_eq!(arg.file, None);
682 assert!(!arg.force);
683 }
684 other => panic!("expected top-level cors set command, got {:?}", other),
685 }
686 }
687
688 #[test]
689 fn cli_accepts_top_level_cors_set_with_legacy_file_flag() {
690 let cli = Cli::try_parse_from([
691 "rc",
692 "cors",
693 "set",
694 "local/my-bucket",
695 "--file",
696 "cors.json",
697 "--force",
698 ])
699 .expect("parse top-level cors set with --file");
700
701 match cli.command {
702 Commands::Cors(cors::CorsCommands::Set(arg)) => {
703 assert_eq!(arg.path, "local/my-bucket");
704 assert_eq!(arg.source, None);
705 assert_eq!(arg.file.as_deref(), Some("cors.json"));
706 assert!(arg.force);
707 }
708 other => panic!("expected top-level cors set command, got {:?}", other),
709 }
710 }
711
712 #[test]
713 fn cli_accepts_bucket_cors_list_force_flag() {
714 let cli =
715 Cli::try_parse_from(["rc", "bucket", "cors", "list", "local/my-bucket", "--force"])
716 .expect("parse bucket cors list with force");
717
718 match cli.command {
719 Commands::Bucket(args) => match args.command {
720 bucket::BucketCommands::Cors(cors::CorsCommands::List(arg)) => {
721 assert_eq!(arg.path, "local/my-bucket");
722 assert!(arg.force);
723 }
724 other => panic!("expected bucket cors list command, got {:?}", other),
725 },
726 other => panic!("expected bucket command, got {:?}", other),
727 }
728 }
729
730 #[test]
731 fn cli_accepts_bucket_lifecycle_subcommand() {
732 let cli = Cli::try_parse_from([
733 "rc",
734 "bucket",
735 "lifecycle",
736 "rule",
737 "list",
738 "local/my-bucket",
739 ])
740 .expect("parse bucket lifecycle rule list");
741
742 match cli.command {
743 Commands::Bucket(args) => match args.command {
744 bucket::BucketCommands::Lifecycle(ilm::IlmArgs {
745 command: ilm::IlmCommands::Rule(ilm::rule::RuleCommands::List(arg)),
746 }) => {
747 assert_eq!(arg.path, "local/my-bucket");
748 assert!(!arg.force);
749 }
750 other => panic!(
751 "expected bucket lifecycle rule list command, got {:?}",
752 other
753 ),
754 },
755 other => panic!("expected bucket command, got {:?}", other),
756 }
757 }
758
759 #[test]
760 fn cli_accepts_bucket_replication_subcommand() {
761 let cli = Cli::try_parse_from(["rc", "bucket", "replication", "status", "local/my-bucket"])
762 .expect("parse bucket replication status");
763
764 match cli.command {
765 Commands::Bucket(args) => match args.command {
766 bucket::BucketCommands::Replication(replicate::ReplicateArgs {
767 command: replicate::ReplicateCommands::Status(arg),
768 }) => {
769 assert_eq!(arg.path, "local/my-bucket");
770 assert!(!arg.force);
771 }
772 other => panic!(
773 "expected bucket replication status command, got {:?}",
774 other
775 ),
776 },
777 other => panic!("expected bucket command, got {:?}", other),
778 }
779 }
780
781 #[test]
782 fn cli_accepts_bucket_remove_subcommand() {
783 let cli = Cli::try_parse_from(["rc", "bucket", "remove", "local/my-bucket"])
784 .expect("parse bucket remove");
785
786 match cli.command {
787 Commands::Bucket(args) => match args.command {
788 bucket::BucketCommands::Remove(arg) => {
789 assert_eq!(arg.target, "local/my-bucket");
790 }
791 other => panic!("expected bucket remove command, got {:?}", other),
792 },
793 other => panic!("expected bucket command, got {:?}", other),
794 }
795 }
796
797 #[test]
798 fn cli_accepts_object_remove_subcommand() {
799 let cli = Cli::try_parse_from([
800 "rc",
801 "object",
802 "remove",
803 "local/my-bucket/report.csv",
804 "--dry-run",
805 ])
806 .expect("parse object remove");
807
808 match cli.command {
809 Commands::Object(args) => match args.command {
810 object::ObjectCommands::Remove(arg) => {
811 assert_eq!(arg.paths, vec!["local/my-bucket/report.csv".to_string()]);
812 assert!(arg.dry_run);
813 }
814 other => panic!("expected object remove command, got {:?}", other),
815 },
816 other => panic!("expected object command, got {:?}", other),
817 }
818 }
819
820 #[test]
821 fn cli_accepts_bucket_event_remove_subcommand() {
822 let cli = Cli::try_parse_from([
823 "rc",
824 "bucket",
825 "event",
826 "remove",
827 "local/my-bucket",
828 "arn:aws:sns:us-east-1:123456789012:alerts",
829 ])
830 .expect("parse bucket event remove");
831
832 match cli.command {
833 Commands::Bucket(args) => match args.command {
834 bucket::BucketCommands::Event(event::EventCommands::Remove(arg)) => {
835 assert_eq!(arg.path, "local/my-bucket");
836 assert_eq!(arg.arn, "arn:aws:sns:us-east-1:123456789012:alerts");
837 }
838 other => panic!("expected bucket event remove command, got {:?}", other),
839 },
840 other => panic!("expected bucket command, got {:?}", other),
841 }
842 }
843
844 #[test]
845 fn cli_accepts_rm_purge_flag() {
846 let cli = Cli::try_parse_from(["rc", "rm", "local/my-bucket/object.txt", "--purge"])
847 .expect("parse rm purge");
848
849 match cli.command {
850 Commands::Rm(arg) => {
851 assert_eq!(arg.paths, vec!["local/my-bucket/object.txt".to_string()]);
852 assert!(arg.purge);
853 }
854 other => panic!("expected rm command, got {:?}", other),
855 }
856 }
857
858 #[test]
859 fn cli_accepts_object_remove_purge_flag() {
860 let cli = Cli::try_parse_from([
861 "rc",
862 "object",
863 "remove",
864 "local/my-bucket/object.txt",
865 "--purge",
866 ])
867 .expect("parse object remove purge");
868
869 match cli.command {
870 Commands::Object(args) => match args.command {
871 object::ObjectCommands::Remove(arg) => {
872 assert_eq!(arg.paths, vec!["local/my-bucket/object.txt".to_string()]);
873 assert!(arg.purge);
874 }
875 other => panic!("expected object remove command, got {:?}", other),
876 },
877 other => panic!("expected object command, got {:?}", other),
878 }
879 }
880
881 #[test]
882 fn cli_accepts_object_stat_subcommand() {
883 let cli = Cli::try_parse_from(["rc", "object", "stat", "local/my-bucket/report.json"])
884 .expect("parse object stat");
885
886 match cli.command {
887 Commands::Object(args) => match args.command {
888 object::ObjectCommands::Stat(arg) => {
889 assert_eq!(arg.path, "local/my-bucket/report.json");
890 }
891 other => panic!("expected object stat command, got {:?}", other),
892 },
893 other => panic!("expected object command, got {:?}", other),
894 }
895 }
896
897 #[test]
898 fn cli_accepts_object_copy_with_transfer_options() {
899 let cli = Cli::try_parse_from([
900 "rc",
901 "object",
902 "copy",
903 "./report.json",
904 "local/my-bucket/reports/",
905 "--content-type",
906 "application/json",
907 "--storage-class",
908 "STANDARD_IA",
909 "--dry-run",
910 ])
911 .expect("parse object copy with transfer options");
912
913 match cli.command {
914 Commands::Object(args) => match args.command {
915 object::ObjectCommands::Copy(arg) => {
916 assert_eq!(arg.source, "./report.json");
917 assert_eq!(arg.target, "local/my-bucket/reports/");
918 assert_eq!(arg.content_type.as_deref(), Some("application/json"));
919 assert_eq!(arg.storage_class.as_deref(), Some("STANDARD_IA"));
920 assert!(arg.dry_run);
921 }
922 other => panic!("expected object copy command, got {:?}", other),
923 },
924 other => panic!("expected object command, got {:?}", other),
925 }
926 }
927
928 #[test]
929 fn cli_accepts_object_move_with_recursive_dry_run() {
930 let cli = Cli::try_parse_from([
931 "rc",
932 "object",
933 "move",
934 "local/source-bucket/logs/",
935 "local/archive-bucket/logs/",
936 "--recursive",
937 "--dry-run",
938 "--continue-on-error",
939 ])
940 .expect("parse object move with recursive dry-run");
941
942 match cli.command {
943 Commands::Object(args) => match args.command {
944 object::ObjectCommands::Move(arg) => {
945 assert_eq!(arg.source, "local/source-bucket/logs/");
946 assert_eq!(arg.target, "local/archive-bucket/logs/");
947 assert!(arg.recursive);
948 assert!(arg.dry_run);
949 assert!(arg.continue_on_error);
950 }
951 other => panic!("expected object move command, got {:?}", other),
952 },
953 other => panic!("expected object command, got {:?}", other),
954 }
955 }
956
957 #[test]
958 fn cli_accepts_object_show_and_head_options() {
959 let show_cli = Cli::try_parse_from([
960 "rc",
961 "object",
962 "show",
963 "local/my-bucket/report.json",
964 "--version-id",
965 "v1",
966 "--rewind",
967 "1h",
968 ])
969 .expect("parse object show options");
970
971 match show_cli.command {
972 Commands::Object(args) => match args.command {
973 object::ObjectCommands::Show(arg) => {
974 assert_eq!(arg.path, "local/my-bucket/report.json");
975 assert_eq!(arg.version_id.as_deref(), Some("v1"));
976 assert_eq!(arg.rewind.as_deref(), Some("1h"));
977 }
978 other => panic!("expected object show command, got {:?}", other),
979 },
980 other => panic!("expected object command, got {:?}", other),
981 }
982
983 let head_cli = Cli::try_parse_from([
984 "rc",
985 "object",
986 "head",
987 "local/my-bucket/report.json",
988 "--bytes",
989 "128",
990 "--version-id",
991 "v2",
992 ])
993 .expect("parse object head options");
994
995 match head_cli.command {
996 Commands::Object(args) => match args.command {
997 object::ObjectCommands::Head(arg) => {
998 assert_eq!(arg.path, "local/my-bucket/report.json");
999 assert_eq!(arg.bytes, Some(128));
1000 assert_eq!(arg.version_id.as_deref(), Some("v2"));
1001 }
1002 other => panic!("expected object head command, got {:?}", other),
1003 },
1004 other => panic!("expected object command, got {:?}", other),
1005 }
1006 }
1007
1008 #[test]
1009 fn cli_accepts_object_find_and_tree_options() {
1010 let find_cli = Cli::try_parse_from([
1011 "rc",
1012 "object",
1013 "find",
1014 "local/my-bucket/logs/",
1015 "--name",
1016 "*.json",
1017 "--maxdepth",
1018 "2",
1019 "--count",
1020 "--print",
1021 ])
1022 .expect("parse object find options");
1023
1024 match find_cli.command {
1025 Commands::Object(args) => match args.command {
1026 object::ObjectCommands::Find(arg) => {
1027 assert_eq!(arg.path, "local/my-bucket/logs/");
1028 assert_eq!(arg.name.as_deref(), Some("*.json"));
1029 assert_eq!(arg.maxdepth, 2);
1030 assert!(arg.count);
1031 assert!(arg.print);
1032 }
1033 other => panic!("expected object find command, got {:?}", other),
1034 },
1035 other => panic!("expected object command, got {:?}", other),
1036 }
1037
1038 let tree_cli = Cli::try_parse_from([
1039 "rc",
1040 "object",
1041 "tree",
1042 "local/my-bucket/logs/",
1043 "--level",
1044 "4",
1045 "--size",
1046 "--pattern",
1047 "*.json",
1048 "--full-path",
1049 ])
1050 .expect("parse object tree options");
1051
1052 match tree_cli.command {
1053 Commands::Object(args) => match args.command {
1054 object::ObjectCommands::Tree(arg) => {
1055 assert_eq!(arg.path, "local/my-bucket/logs/");
1056 assert_eq!(arg.level, 4);
1057 assert!(arg.size);
1058 assert_eq!(arg.pattern.as_deref(), Some("*.json"));
1059 assert!(arg.full_path);
1060 }
1061 other => panic!("expected object tree command, got {:?}", other),
1062 },
1063 other => panic!("expected object command, got {:?}", other),
1064 }
1065 }
1066}