s3sync/config/args/
mod.rs

1use crate::Config;
2use crate::callback::event_manager::EventManager;
3use crate::callback::filter_manager::FilterManager;
4use crate::callback::preprocess_manager::PreprocessManager;
5#[allow(unused_imports)]
6use crate::config::args::value_parser::{
7    canned_acl, checksum_algorithm, file_exist, human_bytes, metadata, sse, storage_class,
8    storage_path, tagging, url,
9};
10use crate::config::{
11    CLITimeoutConfig, ClientConfig, FilterConfig, ForceRetryConfig, RetryConfig, TracingConfig,
12    TransferConfig,
13};
14#[allow(unused_imports)]
15use crate::types::event_callback::EventType;
16use crate::types::{
17    AccessKeys, ClientConfigLocation, S3Credentials, SseCustomerKey, SseKmsKeyId, StoragePath,
18};
19use aws_sdk_s3::types::{
20    ChecksumAlgorithm, ChecksumMode, ObjectCannedAcl, RequestPayer, ServerSideEncryption,
21    StorageClass,
22};
23use aws_smithy_types::checksum_config::RequestChecksumCalculation;
24use cfg_if::cfg_if;
25use chrono::{DateTime, Utc};
26use clap::Parser;
27use clap::builder::{ArgPredicate, NonEmptyStringValueParser};
28use clap_verbosity_flag::{Verbosity, WarnLevel};
29use fancy_regex::Regex;
30#[cfg(feature = "version")]
31use shadow_rs::shadow;
32use std::ffi::OsString;
33use std::path::PathBuf;
34use std::str::FromStr;
35use std::sync::Arc;
36use tokio::sync::Semaphore;
37
38mod tests;
39mod value_parser;
40
41const EXPRESS_ONEZONE_STORAGE_SUFFIX: &str = "--x-s3";
42
43const DEFAULT_WORKER_SIZE: u16 = 16;
44const DEFAULT_AWS_MAX_ATTEMPTS: u32 = 10;
45const DEFAULT_FORCE_RETRY_COUNT: u32 = 5;
46const DEFAULT_FORCE_RETRY_INTERVAL_MILLISECONDS: u64 = 1000;
47const DEFAULT_INITIAL_BACKOFF_MILLISECONDS: u64 = 100;
48const DEFAULT_JSON_TRACING: bool = false;
49const DEFAULT_AWS_SDK_TRACING: bool = false;
50const DEFAULT_SPAN_EVENTS_TRACING: bool = false;
51const DEFAULT_DISABLE_COLOR_TRACING: bool = false;
52const DEFAULT_MULTIPART_THRESHOLD: &str = "8MiB";
53const DEFAULT_MULTIPART_CHUNKSIZE: &str = "8MiB";
54const DEFAULT_AUTO_CHUNKSIZE: bool = false;
55const DEFAULT_NO_SYNC_SYSTEM_METADATA: bool = false;
56const DEFAULT_NO_SYNC_USER_DEFINED_METADATA: bool = false;
57const DEFAULT_WARN_AS_ERROR: bool = false;
58const DEFAULT_IGNORE_SYMLINKS: bool = false;
59const DEFAULT_FORCE_PATH_STYLE: bool = false;
60const DEFAULT_HEAD_EACH_TARGET: bool = false;
61const DEFAULT_ENABLE_VERSIONING: bool = false;
62const DEFAULT_REMOVE_MODIFIED_FILTER: bool = false;
63const DEFAULT_CHECK_SIZE: bool = false;
64const DEFAULT_CHECK_ETAG: bool = false;
65const DEFAULT_CHECK_MTIME_AND_ETAG: bool = false;
66const DEFAULT_SYNC_WITH_DELETE: bool = false;
67const DEFAULT_DISABLE_TAGGING: bool = false;
68const DEFAULT_SYNC_LATEST_TAGGING: bool = false;
69const DEFAULT_NO_GUESS_MIME_TYPE: bool = false;
70const DEFAULT_SERVER_SIDE_COPY: bool = false;
71const DEFAULT_DISABLE_MULTIPART_VERIFY: bool = false;
72const DEFAULT_DISABLE_ETAG_VERIFY: bool = false;
73const DEFAULT_DISABLE_ADDITIONAL_CHECKSUM_VERIFY: bool = false;
74const DEFAULT_ENABLE_ADDITIONAL_CHECKSUM: bool = false;
75const DEFAULT_DRY_RUN: bool = false;
76const DEFAULT_MAX_KEYS: i32 = 1000;
77const DEFAULT_PUT_LAST_MODIFIED_METADATA: bool = false;
78const DEFAULT_DISABLE_STALLED_STREAM_PROTECTION: bool = false;
79const DEFAULT_DISABLE_PAYLOAD_SIGNING: bool = false;
80const DEFAULT_DISABLE_CONTENT_MD5_HEADER: bool = false;
81const DEFAULT_DELETE_EXCLUDED: bool = false;
82const DEFAULT_FULL_OBJECT_CHECKSUM: bool = false;
83const DEFAULT_DISABLE_EXPRESS_ONE_ZONE_ADDITIONAL_CHECKSUM: bool = false;
84const DEFAULT_MAX_PARALLEL_MULTIPART_UPLOADS: u16 = 16;
85const DEFAULT_MAX_PARALLEL_LISTINGS: u16 = 16;
86const DEFAULT_OBJECT_LISTING_QUEUE_SIZE: u32 = 200000;
87const DEFAULT_PARALLEL_LISTING_MAX_DEPTH: u16 = 2;
88const DEFAULT_ALLOW_PARALLEL_LISTINGS_IN_EXPRESS_ONE_ZONE: bool = false;
89const DEFAULT_ACCELERATE: bool = false;
90const DEFAULT_REQUEST_PAYER: bool = false;
91const DEFAULT_REPORT_SYNC_STATUS: bool = false;
92const DEFAULT_REPORT_METADATA_SYNC_STATUS: bool = false;
93const DEFAULT_REPORT_TAGGING_SYNC_STATUS: bool = false;
94#[allow(dead_code)]
95const DEFAULT_ALLOW_LUA_OS_LIBRARY: bool = false;
96#[allow(dead_code)]
97const DEFAULT_ALLOW_LUA_UNSAFE_VM: bool = false;
98#[allow(dead_code)]
99const DEFAULT_LUA_VM_MEMORY_LIMIT: &str = "64MiB";
100const DEFAULT_SHOW_NO_PROGRESS: bool = false;
101const DEFAULT_IF_MATCH: bool = false;
102const DEFAULT_COPY_SOURCE_IF_MATCH: bool = false;
103const DEFAULT_IGNORE_GLACIER_WARNINGS: bool = false;
104
105const NO_S3_STORAGE_SPECIFIED: &str = "either SOURCE or TARGET must be s3://\n";
106const LOCAL_STORAGE_SPECIFIED: &str = "with --enable-versioning/--sync-latest-tagging/--copy-source-if-match, both storage must be s3://\n";
107const VERSIONING_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
108    "--enable-versioning is not supported with express onezone storage class\n";
109const LOCAL_STORAGE_SPECIFIED_WITH_STORAGE_CLASS: &str =
110    "with --storage-class, target storage must be s3://\n";
111const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SSE: &str =
112    "with --sse/--sse-kms-key-id, target storage must be s3://\n";
113const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACL: &str = "with --acl, target storage must be s3://\n";
114const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENABLE_ADDITIONAL_CHECKSUM: &str =
115    "with --enable-additional-checksum, source storage must be s3://\n";
116const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ADDITIONAL_CHECKSUM_ALGORITHM: &str =
117    "with --additional-checksum-algorithm, target storage must be s3://\n";
118const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_AUTO_CHUNKSIZE: &str =
119    "with --auto-chunksize, source storage must be s3://\n";
120const SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_IGNORE_SYMLINKS: &str =
121    "with --ignore-symlinks, source storage must be local storage\n";
122const SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_NO_GUESS_MIME_TYPE: &str =
123    "with --no-guess-mime-type, source storage must be local storage\n";
124const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_METADATA_OPTION: &str =
125    "with metadata related option, target storage must be s3://\n";
126const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL: &str =
127    "with --source-endpoint-url, source storage must be s3://\n";
128const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL: &str =
129    "with --target-endpoint-url, target storage must be s3://\n";
130const CHECK_SIZE_CONFLICT: &str =
131    "--head-each-target is required for --check-size, or remove --remove-modified-filter\n";
132
133const CHECK_ETAG_CONFLICT: &str =
134    "--head-each-target is required for --check-etag, or remove --remove-modified-filter\n";
135const CHECK_ETAG_CONFLICT_SSE_KMS: &str =
136    "--check-etag is not supported with --sse aws:kms | aws:kms:dsse \n";
137const CHECK_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
138    "--check-etag is not supported with express onezone storage class\n";
139
140const CHECK_MTIME_AND_ETAG_CONFLICT_SSE_KMS: &str =
141    "--check-mtime-and-etag is not supported with --sse aws:kms | aws:kms:dsse \n";
142const CHECK_MTIME_AND_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
143    "--check-mtime-and-etag is not supported with express onezone storage class\n";
144const SOURCE_LOCAL_STORAGE_FILE_WITH_DELETE_OPTION: &str =
145    "with --delete, source storage must be directory\n";
146const SOURCE_LOCAL_STORAGE_PATH_NOT_FOUND: &str = "source file/directory not found\n";
147const TARGET_LOCAL_STORAGE_INVALID: &str = "invalid target path\n";
148const SSE_KMS_KEY_ID_ARGUMENTS_CONFLICT: &str =
149    "--sse-kms-key-id must be used with --sse aws:kms | aws:kms:dsse\n";
150const LOCAL_STORAGE_SPECIFIED_WITH_SSE_C: &str =
151    "with --source-sse-c/--target-sse-c, remote storage must be s3://\n";
152const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_PAYLOAD_SIGNING: &str =
153    "with --disable-payload-signing, target storage must be s3://\n";
154const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_CONTENT_MD5_HEADER: &str =
155    "with --disable-content-md5-header, target storage must be s3://\n";
156const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_FULL_OBJECT_CHECKSUM: &str =
157    "with --full-object-checksum, target storage must be s3://\n";
158const FULL_OBJECT_CHECKSUM_NOT_SUPPORTED: &str =
159    "Only CRC32/CRC32C/CRC64NVME supports full object checksum\n";
160const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE: &str =
161    "with --source-accelerate, source storage must be s3://\n";
162const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE: &str =
163    "with --target-accelerate, target storage must be s3://\n";
164const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER: &str =
165    "with --source-request-payer, source storage must be s3://\n";
166const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER: &str =
167    "with --target-request-payer, target storage must be s3://\n";
168const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY: &str =
169    "with --server-side-copy, source storage must be s3://\n";
170const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY: &str =
171    "with --server-side-copy, target storage must be s3://\n";
172
173const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_METADATA_REGEX: &str =
174    "with --filter-include-metadata-regex, source storage must be s3://\n";
175const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_METADATA_REGEX: &str =
176    "with --filter-exclude-metadata-regex, source storage must be s3://\n";
177
178const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_TAG_REGEX: &str =
179    "with --filter-include-tag-regex, source storage must be s3://\n";
180const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_TAG_REGEX: &str =
181    "with --filter-exclude-tag-regex, source storage must be s3://\n";
182
183const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA: &str =
184    "with --no-sync-system-metadata, source storage must be s3://\n";
185const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA: &str =
186    "with --no-sync-system-metadata, target storage must be s3://\n";
187const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA: &str =
188    "with --no-sync-user-defined-metadata, source storage must be s3://\n";
189const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA: &str =
190    "with --no-sync-user-defined-metadata, target storage must be s3://\n";
191const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_POINT_IN_TIME: &str =
192    "with --point-in-time, source storage must be s3://\n";
193const POINT_IN_TIME_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE: &str =
194    "--point-in-time is not supported with express onezone storage class\n";
195const POINT_IN_TIME_VALUE_INVALID: &str = "--point-in-time value is later than current time\n";
196const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS: &str =
197    "with --report-metadata-sync-status, source storage must be s3://\n";
198const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS: &str =
199    "with --report-metadata-sync-status, target storage must be s3://\n";
200const SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS: &str =
201    "with --report-tagging-sync-status, source storage must be s3://\n";
202const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS: &str =
203    "with --report-tagging-sync-status, target storage must be s3://\n";
204const NO_SOURCE_CREDENTIAL_REQUIRED: &str = "no source credential required\n";
205const NO_TARGET_CREDENTIAL_REQUIRED: &str = "no target credential required\n";
206#[allow(dead_code)]
207const LUA_SCRIPT_LOAD_ERROR: &str = "Failed to load and compile Lua script callback: ";
208const TARGET_LOCAL_STORAGE_SPECIFIED_WITH_IF_MATCH: &str =
209    "with --if-match, target storage must be s3://\n";
210const IF_MATCH_CONFLICT: &str =
211    "--head-each-target is required for --if-match, or remove --remove-modified-filter\n";
212
213#[cfg(feature = "version")]
214shadow!(build);
215
216#[derive(Parser, Clone, Debug)]
217#[cfg_attr(feature = "version", command(version=format!("{} ({} {}), {}", build::PKG_VERSION, build::SHORT_COMMIT, build::BUILD_TARGET, build::RUST_VERSION)))]
218pub struct CLIArgs {
219    #[arg(env, help = "s3://<BUCKET_NAME>[/prefix] or local path", value_parser = storage_path::check_storage_path, default_value_if("auto_complete_shell", ArgPredicate::IsPresent, "s3://ignored"), required = false)]
220    source: String,
221
222    #[arg(env, help = "s3://<BUCKET_NAME>[/prefix] or local path", value_parser = storage_path::check_storage_path, default_value_if("auto_complete_shell", ArgPredicate::IsPresent, "s3://ignored"), required = false)]
223    target: String,
224
225    /// A simulation mode. No actions will be performed.
226    #[arg(long, env, default_value_t = DEFAULT_DRY_RUN, help_heading = "General")]
227    dry_run: bool,
228
229    /// Don't show the progress bar.
230    #[arg(long, env, default_value_t = DEFAULT_SHOW_NO_PROGRESS, help_heading = "General")]
231    show_no_progress: bool,
232
233    #[arg(long, env, default_value_t = DEFAULT_SERVER_SIDE_COPY, help_heading = "General",
234    long_help = r#"Use server-side copy. This option is only available both source and target are S3 storage.
235It cannot work with between different object storages or regions."#)]
236    server_side_copy: bool,
237
238    /// Location of the file that the AWS CLI uses to store configuration profiles
239    #[arg(long, env, value_name = "FILE", help_heading = "AWS Configuration")]
240    aws_config_file: Option<PathBuf>,
241
242    /// Location of the file that the AWS CLI uses to store access keys
243    #[arg(long, env, value_name = "FILE", help_heading = "AWS Configuration")]
244    aws_shared_credentials_file: Option<PathBuf>,
245
246    /// Source AWS CLI profile
247    #[arg(long, env, conflicts_with_all = ["source_access_key", "source_secret_access_key", "source_session_token"], help_heading = "AWS Configuration")]
248    source_profile: Option<String>,
249
250    /// Source access key
251    #[arg(long, env, conflicts_with_all = ["source_profile"], requires = "source_secret_access_key", help_heading = "AWS Configuration")]
252    source_access_key: Option<String>,
253
254    /// Source secret access key
255    #[arg(long, env, conflicts_with_all = ["source_profile"], requires = "source_access_key", help_heading = "AWS Configuration")]
256    source_secret_access_key: Option<String>,
257
258    /// Source session token
259    #[arg(long, env, conflicts_with_all = ["source_profile"], requires = "source_access_key", help_heading = "AWS Configuration")]
260    source_session_token: Option<String>,
261
262    /// Source region
263    #[arg(long, env, value_parser = NonEmptyStringValueParser::new(), help_heading = "Source Options")]
264    source_region: Option<String>,
265
266    /// Source endpoint url
267    #[arg(long, env, value_parser = url::check_scheme, help_heading = "Source Options")]
268    source_endpoint_url: Option<String>,
269
270    /// Use Amazon S3 Transfer Acceleration for the source bucket.
271    #[arg(long, env, default_value_t = DEFAULT_ACCELERATE, help_heading = "Source Options")]
272    source_accelerate: bool,
273
274    /// Use request payer for the source bucket.
275    #[arg(long, env, default_value_t = DEFAULT_REQUEST_PAYER, help_heading = "Source Options")]
276    source_request_payer: bool,
277
278    /// Force path-style addressing for source endpoint.
279    #[arg(long, env, default_value_t = DEFAULT_FORCE_PATH_STYLE, help_heading = "Source Options")]
280    source_force_path_style: bool,
281
282    /// Target AWS CLI profile
283    #[arg(long, env, conflicts_with_all = ["target_access_key", "target_secret_access_key", "target_session_token"], help_heading = "AWS Configuration")]
284    target_profile: Option<String>,
285
286    /// Target access key
287    #[arg(long, env, conflicts_with_all = ["target_profile"], requires = "target_secret_access_key", help_heading = "AWS Configuration")]
288    target_access_key: Option<String>,
289
290    /// Target secret access key
291    #[arg(long, env, conflicts_with_all = ["target_profile"], requires = "target_access_key", help_heading = "AWS Configuration")]
292    target_secret_access_key: Option<String>,
293
294    /// Target session token
295    #[arg(long, env, conflicts_with_all = ["target_profile"], requires = "target_access_key", help_heading = "AWS Configuration")]
296    target_session_token: Option<String>,
297
298    /// Target region
299    #[arg(long, env, value_parser = NonEmptyStringValueParser::new(), help_heading = "Target Options")]
300    target_region: Option<String>,
301
302    /// Target endpoint url
303    #[arg(long, env, value_parser = url::check_scheme, help_heading = "Target Options")]
304    target_endpoint_url: Option<String>,
305
306    /// Use Amazon S3 Transfer Acceleration for the target bucket.
307    #[arg(long, env, default_value_t = DEFAULT_ACCELERATE, help_heading = "Target Options")]
308    target_accelerate: bool,
309
310    /// Use request payer for the target bucket.
311    #[arg(long, env, default_value_t = DEFAULT_REQUEST_PAYER,help_heading = "Target Options")]
312    target_request_payer: bool,
313
314    /// Force path-style addressing for target endpoint.
315    #[arg(long, env, default_value_t = DEFAULT_FORCE_PATH_STYLE, help_heading = "Target Options")]
316    target_force_path_style: bool,
317
318    #[arg(long, env, value_parser = storage_class::parse_storage_class, help_heading = "Target Options",
319    long_help = r#"Type of storage to use for the target object.
320Valid choices: STANDARD | REDUCED_REDUNDANCY | STANDARD_IA | ONE-ZONE_IA | INTELLIGENT_TIERING | GLACIER |
321               DEEP_ARCHIVE | GLACIER_IR | EXPRESS_ONEZONE"#)]
322    storage_class: Option<String>,
323
324    #[arg(
325        long,
326        env,
327        help_heading = "Filtering",
328        long_help = r#"Sync only objects older than given time (RFC3339 datetime).
329Example: 2023-02-19T12:00:00Z"#
330    )]
331    filter_mtime_before: Option<DateTime<Utc>>,
332
333    #[arg(
334        long,
335        env,
336        help_heading = "Filtering",
337        long_help = r#"Sync only objects newer than OR EQUAL TO given time (RFC3339 datetime).
338Example: 2023-02-19T12:00:00Z"#
339    )]
340    filter_mtime_after: Option<DateTime<Utc>>,
341
342    #[arg(long, env, value_parser = human_bytes::check_human_bytes_without_limit, help_heading = "Filtering", long_help=r#"Sync only objects smaller than given size.
343Allow suffixes: KB, KiB, MB, MiB, GB, GiB, TB, TiB"#)]
344    filter_smaller_size: Option<String>,
345
346    #[arg(long, env, value_parser = human_bytes::check_human_bytes_without_limit, help_heading = "Filtering", long_help=r#"Sync only objects larger than OR EQUAL TO given size.
347Allow suffixes: KB, KiB, MB, MiB, GB, GiB, TB, TiB"#)]
348    filter_larger_size: Option<String>,
349
350    /// Sync only objects that match a given regular expression.
351    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering")]
352    filter_include_regex: Option<String>,
353
354    /// Do not sync objects that match a given regular expression.
355    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering")]
356    filter_exclude_regex: Option<String>,
357
358    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
359    long_help = r#"Sync only objects that have Content-Type matching a given regular expression.
360If the source is local storage, Content-Type is guessed by the file extension,
361Unless --no-guess-mime-type is specified.
362It may take an extra API call to get Content-Type of the object.
363"#)]
364    filter_include_content_type_regex: Option<String>,
365
366    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
367    long_help=r#"Do not sync objects that have Content-Type matching a given regular expression.
368If the source is local storage, Content-Type is guessed by the file extension,
369Unless --no-guess-mime-type is specified.
370It may take an extra API call to get Content-Type of the object.
371"#)]
372    filter_exclude_content_type_regex: Option<String>,
373
374    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
375    long_help=r#"Sync only objects that have metadata matching a given regular expression.
376Keys(lowercase) must be sorted in alphabetical order, and comma separated.
377This filter is applied after all other filters(except tag filters).
378It may take an extra API call to get metadata of the object.
379
380Example: "key1=(value1|value2),key2=value2""#)]
381    filter_include_metadata_regex: Option<String>,
382
383    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
384    long_help=r#"Do not sync objects that have metadata matching a given regular expression.
385Keys(lowercase) must be sorted in alphabetical order, and comma separated.
386This filter is applied after all other filters(except tag filters).
387It may take an extra API call to get metadata of the object.
388
389Example: "key1=(value1|value2),key2=value2""#)]
390    filter_exclude_metadata_regex: Option<String>,
391
392    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
393    long_help=r#"Sync only objects that have tag matching a given regular expression.
394Keys must be sorted in alphabetical order, and '&' separated.
395This filter is applied after all other filters.
396It takes an extra API call to get tags of the object.
397
398Example: "key1=(value1|value2)&key2=value2""#)]
399    filter_include_tag_regex: Option<String>,
400
401    #[arg(long, env, value_parser = crate::config::args::value_parser::regex::parse_regex, help_heading = "Filtering",
402    long_help=r#"Do not sync objects that have tag matching a given regular expression.
403Keys must be sorted in alphabetical order, and '&' separated.
404This filter is applied after all other filters.
405It takes an extra API call to get tags of the object.
406
407Example: "key1=(value1|value2)&key2=value2""#)]
408    filter_exclude_tag_regex: Option<String>,
409
410    /// Do not update checking(ListObjectsV2) for modification in the target storage.
411    #[arg(long, env, conflicts_with_all = ["enable_versioning"], default_value_t = DEFAULT_REMOVE_MODIFIED_FILTER, help_heading = "Update Checking")]
412    remove_modified_filter: bool,
413
414    /// Use object size for update checking.
415    #[arg(long, env, conflicts_with_all = ["enable_versioning", "check_etag", "check_mtime_and_etag", "check_mtime_and_additional_checksum"], default_value_t = DEFAULT_CHECK_SIZE, help_heading = "Update Checking")]
416    check_size: bool,
417
418    /// Use etag for update checking.
419    #[arg(long, env, conflicts_with_all = ["enable_versioning", "check_size", "check_mtime_and_etag", "check_mtime_and_additional_checksum", "source_sse_c_key", "target_sse_c_key"], default_value_t = DEFAULT_CHECK_ETAG, help_heading = "Update Checking",)]
420    check_etag: bool,
421
422    #[arg(long, env, conflicts_with_all = ["enable_versioning", "remove_modified_filter", "check_size", "check_etag", "source_sse_c_key", "target_sse_c_key"], default_value_t = DEFAULT_CHECK_MTIME_AND_ETAG, help_heading = "Update Checking",
423    long_help=r#"Use the modification time and ETag for update checking.
424If the source modification date is newer, check the ETag.
425"#)]
426    check_mtime_and_etag: bool,
427
428    /// Use additional checksum for update checking.
429    #[arg(long, env, conflicts_with_all = ["enable_versioning", "check_size", "check_etag", "check_mtime_and_etag", "check_mtime_and_additional_checksum"], value_parser = checksum_algorithm::parse_checksum_algorithm, help_heading = "Update Checking")]
430    check_additional_checksum: Option<String>,
431
432    #[arg(long, env, conflicts_with_all = ["enable_versioning", "remove_modified_filter", "check_size", "check_etag", "check_mtime_and_etag", "check_additional_checksum"], value_parser = checksum_algorithm::parse_checksum_algorithm, help_heading = "Update Checking",
433    long_help=r#"Use the modification time and additional checksum for update checking.
434If the source modification date is newer, check the additional checksum.
435"#)]
436    check_mtime_and_additional_checksum: Option<String>,
437
438    /// Additional checksum algorithm for upload
439    #[arg(long, env, value_parser = checksum_algorithm::parse_checksum_algorithm, help_heading = "Verification")]
440    additional_checksum_algorithm: Option<String>,
441
442    #[arg(long, env, default_value_t = DEFAULT_FULL_OBJECT_CHECKSUM, help_heading = "Verification", long_help=r#"Use full object checksum for verification. CRC64NVME automatically use full object checksum.
443This option cannot be used with SHA1/SHA256 additional checksum."#)]
444    full_object_checksum: bool,
445
446    /// Enable additional checksum for download
447    #[arg(long, env, default_value_t = DEFAULT_ENABLE_ADDITIONAL_CHECKSUM, help_heading = "Verification")]
448    enable_additional_checksum: bool,
449
450    /// Disable multipart upload verification with ETag/additional checksum.
451    #[arg(long, env, default_value_t = DEFAULT_DISABLE_MULTIPART_VERIFY, help_heading = "Verification")]
452    disable_multipart_verify: bool,
453
454    /// Disable etag verification.
455    #[arg(long, env, default_value_t = DEFAULT_DISABLE_ETAG_VERIFY, help_heading = "Verification")]
456    disable_etag_verify: bool,
457
458    #[arg(long, env, requires = "additional_checksum_algorithm", default_value_t = DEFAULT_DISABLE_ADDITIONAL_CHECKSUM_VERIFY, help_heading = "Verification",
459    long_help=r#"Disable additional checksum verification 
460But use additional checksum for upload (The hash value is stored in the target object)."#)]
461    disable_additional_checksum_verify: bool,
462
463    /// Number of workers for synchronization
464    #[arg(long, env, default_value_t = DEFAULT_WORKER_SIZE, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance")]
465    worker_size: u16,
466
467    /// Maximum number of parallel multipart uploads/downloads
468    #[arg(long, env, default_value_t = DEFAULT_MAX_PARALLEL_MULTIPART_UPLOADS, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance")]
469    max_parallel_uploads: u16,
470
471    #[arg(long, env, default_value_t = DEFAULT_MAX_PARALLEL_LISTINGS, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance", long_help=r#"Maximum number of parallel listings of objects."#)]
472    max_parallel_listings: u16,
473
474    #[arg(long, env, default_value_t = DEFAULT_PARALLEL_LISTING_MAX_DEPTH, value_parser = clap::value_parser!(u16).range(1..), help_heading = "Performance", long_help=r#"Maximum depth(sub directroy/prefix) of parallel listings."#)]
475    max_parallel_listing_max_depth: u16,
476
477    /// Queue size for object listings
478    #[arg(long, env, default_value_t = DEFAULT_OBJECT_LISTING_QUEUE_SIZE, value_parser = clap::value_parser!(u32).range(1..), help_heading = "Performance")]
479    object_listing_queue_size: u32,
480
481    #[arg(long, env, default_value_t = DEFAULT_ALLOW_PARALLEL_LISTINGS_IN_EXPRESS_ONE_ZONE, help_heading = "Performance", long_help=r#"Allow parallel listings in express one zone storage class.
482It may include multipart upload in progress objects in the listing result."#)]
483    allow_parallel_listings_in_express_one_zone: bool,
484
485    /// Rate limit objects per second
486    #[arg(long, env,  value_parser = clap::value_parser!(u32).range(10..), help_heading = "Performance")]
487    rate_limit_objects: Option<u32>,
488
489    /// Rate limit bandwidth(bytes per sec). Allow suffixes: MB, MiB, GB, GiB
490    #[arg(long, env, value_parser = human_bytes::check_human_bandwidth, help_heading = "Performance")]
491    rate_limit_bandwidth: Option<String>,
492
493    #[arg(long, env, conflicts_with_all = ["auto_chunksize"], default_value = DEFAULT_MULTIPART_THRESHOLD, value_parser = human_bytes::check_human_bytes, help_heading = "Multipart Settings",
494    long_help=r#"Object size threshold that s3sync uses for multipart upload
495Allow suffixes: MB, MiB, GB, GiB.
496The larger the size, the larger the memory usage."#)]
497    multipart_threshold: String,
498
499    #[arg(long, env, conflicts_with_all = ["auto_chunksize"], default_value = DEFAULT_MULTIPART_CHUNKSIZE, value_parser = human_bytes::check_human_bytes, help_heading = "Multipart Settings",
500    long_help=r#"Chunk size that s3sync uses for multipart upload of individual files
501Allow suffixes: MB, MiB, GB, GiB.
502The larger the size, the larger the memory usage."#)]
503    multipart_chunksize: String,
504
505    #[arg(long, env, conflicts_with_all = ["multipart_threshold", "multipart_chunksize"], default_value_t = DEFAULT_AUTO_CHUNKSIZE, help_heading = "Multipart Settings",
506    long_help=r#"Automatically adjusts a chunk size to match the source or target.
507It takes extra HEAD requests(1 API call per part)."#)]
508    auto_chunksize: bool,
509
510    /// Cache-Control HTTP header to set on the target object
511    #[arg(long, env, help_heading = "Metadata/Headers")]
512    cache_control: Option<String>,
513
514    /// Content-Disposition HTTP header to set on the target object
515    #[arg(long, env, help_heading = "Metadata/Headers")]
516    content_disposition: Option<String>,
517
518    /// Content-Encoding HTTP header to set on the target object
519    #[arg(long, env, help_heading = "Metadata/Headers")]
520    content_encoding: Option<String>,
521
522    /// Content-Language HTTP header to set on the target object
523    #[arg(long, env, help_heading = "Metadata/Headers")]
524    content_language: Option<String>,
525
526    /// Content-Type HTTP header to set on the target object
527    #[arg(long, env, help_heading = "Metadata/Headers")]
528    content_type: Option<String>,
529
530    #[arg(
531        long,
532        env,
533        help_heading = "Metadata/Headers",
534        long_help = r#"Expires HTTP header to set on the target object(RFC3339 datetime)
535Example: 2023-02-19T12:00:00Z"#
536    )]
537    expires: Option<DateTime<Utc>>,
538
539    #[arg(long, env, value_parser = metadata::check_metadata, help_heading = "Metadata/Headers", long_help=r#"Metadata to set on the target object
540Example: key1=value1,key2=value2"#)]
541    metadata: Option<String>,
542
543    /// x-amz-website-redirect-location header to set on the target object
544    #[arg(long, env, help_heading = "Metadata/Headers")]
545    website_redirect: Option<String>,
546
547    #[arg(long, env, default_value_t =  DEFAULT_NO_SYNC_SYSTEM_METADATA, help_heading = "Metadata/Headers",
548    long_help= r#"Do not sync system metadata
549System metadata: content-disposition, content-encoding, content-language, content-type,
550                 cache-control, expires, website-redirect"#)]
551    no_sync_system_metadata: bool,
552
553    /// Do not sync user-defined metadata.
554    #[arg(long, env, default_value_t =  DEFAULT_NO_SYNC_USER_DEFINED_METADATA, help_heading = "Metadata/Headers")]
555    no_sync_user_defined_metadata: bool,
556
557    #[arg(long, env, conflicts_with_all = ["disable_tagging", "sync_latest_tagging"], value_parser = tagging::parse_tagging, help_heading = "Tagging",
558    long_help=r#"Tagging to set on the target object.
559Key/value must be encoded as UTF-8 then URLEncoded URL query parameters without tag name duplicates.
560
561Example: key1=value1&key2=value2"#)]
562    tagging: Option<String>,
563
564    /// Do not copy tagging.
565    #[arg(long, env, default_value_t = DEFAULT_DISABLE_TAGGING, help_heading = "Tagging")]
566    disable_tagging: bool,
567
568    #[arg(long, env, conflicts_with_all = ["enable_versioning", "disable_tagging"], default_value_t = DEFAULT_SYNC_LATEST_TAGGING, help_heading = "Tagging",
569    long_help=r#"Copy the latest tagging from the source if necessary.
570If this option is enabled, the --remove-modified-filter and
571--head-each-target options are automatically enabled."#)]
572    sync_latest_tagging: bool,
573
574    #[arg(long, env, conflicts_with_all = ["delete", "head_each_target", "remove_modified_filter"], default_value_t = DEFAULT_ENABLE_VERSIONING, help_heading = "Versioning",
575    long_help=r#"Sync all version objects in the source storage to the target versioning storage.
576 "#)]
577    enable_versioning: bool,
578
579    #[arg(
580        long,
581        env,
582        conflicts_with_all = ["delete", "head_each_target", "enable_versioning", "check_etag", "check_mtime_and_etag", "check_size", "check_mtime_and_additional_checksum"],
583        help_heading = "Versioning",
584        long_help = r#"Sync only objects at a specific point in time (RFC3339 datetime).
585The source storage must be a versioning enabled S3 bucket.
586By default, the target storage's objects will always be overwritten with the source's objects.
587
588Example: 2025-07-16T12:00:00Z"#
589    )]
590    point_in_time: Option<DateTime<Utc>>,
591
592    /// Server-side encryption. Valid choices: AES256 | aws:kms | aws:kms:dsse
593    #[arg(long, env, value_parser = sse::parse_sse, help_heading = "Encryption")]
594    sse: Option<String>,
595
596    /// SSE KMS ID key
597    #[arg(long, env, help_heading = "Encryption")]
598    sse_kms_key_id: Option<String>,
599
600    /// Source SSE-C algorithm. Valid choices: AES256
601    #[arg(long, env, conflicts_with_all = ["sse", "sse_kms_key_id"], requires = "source_sse_c_key", value_parser = sse::parse_sse_c, help_heading = "Encryption")]
602    source_sse_c: Option<String>,
603
604    /// Source SSE-C customer-provided encryption key(256bit key. must be base64 encoded)
605    #[arg(
606        long,
607        env,
608        requires = "source_sse_c_key_md5",
609        help_heading = "Encryption"
610    )]
611    source_sse_c_key: Option<String>,
612
613    /// Source base64 encoded MD5 digest of source_sse_c_key
614    #[arg(long, env, requires = "source_sse_c", help_heading = "Encryption")]
615    source_sse_c_key_md5: Option<String>,
616
617    /// Target SSE-C algorithm. Valid choices: AES256
618    #[arg(long, env, conflicts_with_all = ["sse", "sse_kms_key_id"], requires = "target_sse_c_key", value_parser = sse::parse_sse_c, help_heading = "Encryption")]
619    target_sse_c: Option<String>,
620
621    /// Target SSE-C customer-provided encryption key(256bit key. must be base64 encoded)
622    #[arg(
623        long,
624        env,
625        requires = "target_sse_c_key_md5",
626        help_heading = "Encryption"
627    )]
628    target_sse_c_key: Option<String>,
629
630    /// Target base64 encoded MD5 digest of target-sse-c-key
631    #[arg(long, env, requires = "target_sse_c", help_heading = "Encryption")]
632    target_sse_c_key_md5: Option<String>,
633
634    /// Trace verbosity(-v: show info, -vv: show debug, -vvv show trace)
635    #[clap(flatten)]
636    verbosity: Verbosity<WarnLevel>,
637
638    #[arg(
639        long,
640        env,
641        default_value_t = DEFAULT_REPORT_SYNC_STATUS,
642        conflicts_with_all = ["dry_run", "delete", "enable_versioning", "head_each_target"],
643        help_heading = "Reporting",
644        long_help = r#"Report sync status to the target storage.
645Default verification is for etag. For additional checksum, use --check-additional-checksum.
646For more precise control, use with --auto-chunksize."#
647    )]
648    report_sync_status: bool,
649
650    #[arg(
651        long,
652        env,
653        default_value_t = DEFAULT_REPORT_METADATA_SYNC_STATUS,
654        requires = "report_sync_status",
655        help_heading = "Reporting",
656        long_help = r#"Report metadata sync status to the target storage.
657It must be used with --report-sync-status.
658Note: s3sync generated user-defined metadata(s3sync_origin_version_id/s3sync_origin_last_modified) were ignored.
659      Because they are usually different from the source storage."#
660    )]
661    report_metadata_sync_status: bool,
662
663    #[arg(
664        long,
665        env,
666        default_value_t = DEFAULT_REPORT_TAGGING_SYNC_STATUS,
667        requires = "report_sync_status",
668        help_heading = "Reporting",
669        long_help = r#"Report tagging sync status to the target storage.
670It must be used with --report-sync-status."#
671    )]
672    report_tagging_sync_status: bool,
673
674    /// Show trace as json format.
675    #[arg(long, env, default_value_t = DEFAULT_JSON_TRACING, help_heading = "Tracing/Logging")]
676    json_tracing: bool,
677
678    /// Enable aws sdk tracing.
679    #[arg(long, env, default_value_t = DEFAULT_AWS_SDK_TRACING, help_heading = "Tracing/Logging")]
680    aws_sdk_tracing: bool,
681
682    /// Show span event tracing.
683    #[arg(long, env, default_value_t = DEFAULT_SPAN_EVENTS_TRACING, help_heading = "Tracing/Logging")]
684    span_events_tracing: bool,
685
686    /// Disable ANSI terminal colors.
687    #[arg(long, env, default_value_t = DEFAULT_DISABLE_COLOR_TRACING, help_heading = "Tracing/Logging")]
688    disable_color_tracing: bool,
689
690    /// Maximum retry attempts that s3sync retry handler use
691    #[arg(long, env, default_value_t = DEFAULT_AWS_MAX_ATTEMPTS, value_name = "max_attempts", help_heading = "Retry Options")]
692    aws_max_attempts: u32,
693
694    #[arg(long, env, default_value_t = DEFAULT_INITIAL_BACKOFF_MILLISECONDS, value_name = "initial_backoff", help_heading = "Retry Options",
695    long_help=r#"A multiplier value used when calculating backoff times as part of an exponential backoff with jitter strategy.
696"#)]
697    initial_backoff_milliseconds: u64,
698
699    /// Maximum force retry attempts that s3sync retry handler use
700    #[arg(long, env, default_value_t = DEFAULT_FORCE_RETRY_COUNT, help_heading = "Retry Options")]
701    force_retry_count: u32,
702
703    #[arg(long, env, default_value_t = DEFAULT_FORCE_RETRY_INTERVAL_MILLISECONDS, value_name = "force_retry_interval", help_heading = "Retry Options",
704    long_help=r#"Sleep interval (milliseconds) between s3sync force retries on error.
705"#)]
706    force_retry_interval_milliseconds: u64,
707
708    #[arg(
709        long,
710        env,
711        value_name = "operation_timeout",
712        help_heading = "Timeout Options",
713        long_help = r#"Operation timeout (milliseconds). For details, see the AWS SDK for Rust TimeoutConfig documentation.
714The default has no timeout."#
715    )]
716    operation_timeout_milliseconds: Option<u64>,
717
718    #[arg(
719        long,
720        env,
721        value_name = "operation_attempt_timeout",
722        help_heading = "Timeout Options",
723        long_help = r#"Operation attempt timeout (milliseconds). For details, see the AWS SDK for Rust TimeoutConfig documentation.
724The default has no timeout."#
725    )]
726    operation_attempt_timeout_milliseconds: Option<u64>,
727
728    #[arg(
729        long,
730        env,
731        value_name = "connect_timeout",
732        help_heading = "Timeout Options",
733        long_help = r#"Connect timeout (milliseconds).The default has AWS SDK default timeout (Currently 3100 milliseconds).
734"#
735    )]
736    connect_timeout_milliseconds: Option<u64>,
737
738    #[arg(
739        long,
740        env,
741        value_name = "read_timeout",
742        help_heading = "Timeout Options",
743        long_help = r#"Read timeout (milliseconds). The default has no timeout."#
744    )]
745    read_timeout_milliseconds: Option<u64>,
746
747    /// Treat warnings as errors(except for the case of etag/checksum mismatch, etc.).
748    #[arg(long, env, default_value_t = DEFAULT_WARN_AS_ERROR, help_heading = "Advanced")]
749    warn_as_error: bool,
750
751    /// Ignore symbolic links.
752    #[arg(long, env, default_value_t = DEFAULT_IGNORE_SYMLINKS, help_heading = "Advanced")]
753    ignore_symlinks: bool,
754
755    #[arg(long, env, conflicts_with_all = ["enable_versioning"], default_value_t = DEFAULT_HEAD_EACH_TARGET, help_heading = "Advanced",
756    long_help=r#"HeadObject is used to check whether an object has been modified in the target storage.
757It reduces the possibility of race condition issue"#)]
758    head_each_target: bool,
759
760    #[arg(long, env, value_parser = canned_acl::parse_canned_acl, help_heading = "Advanced",
761    long_help=r#"ACL for the objects
762Valid choices: private | public-read | public-read-write | authenticated-read | aws-exec-read |
763               bucket-owner-read | bucket-owner-full-control"#)]
764    acl: Option<String>,
765
766    /// Do not try to guess the mime type of local file.
767    #[arg(long, env, default_value_t = DEFAULT_NO_GUESS_MIME_TYPE, help_heading = "Advanced")]
768    no_guess_mime_type: bool,
769
770    /// Maximum number of objects returned in a single list object request
771    #[arg(long, env, default_value_t = DEFAULT_MAX_KEYS, value_parser = clap::value_parser!(i32).range(1..=32767), help_heading = "Advanced")]
772    max_keys: i32,
773
774    /// Put last modified of the source to metadata.
775    #[arg(long, env, default_value_t = DEFAULT_PUT_LAST_MODIFIED_METADATA, help_heading = "Advanced")]
776    put_last_modified_metadata: bool,
777
778    #[arg(long, env, value_name = "SHELL", value_parser = clap_complete::shells::Shell::from_str, help_heading = "Advanced",
779    long_help=r#"Generate a auto completions script.
780Valid choices: bash, fish, zsh, powershell, elvish."#)]
781    auto_complete_shell: Option<clap_complete::shells::Shell>,
782
783    /// Disable stalled stream protection.
784    #[arg(long, env, default_value_t = DEFAULT_DISABLE_STALLED_STREAM_PROTECTION, help_heading = "Advanced")]
785    disable_stalled_stream_protection: bool,
786
787    /// Disable payload signing for object uploads.
788    #[arg(long, env, default_value_t = DEFAULT_DISABLE_PAYLOAD_SIGNING, help_heading = "Advanced")]
789    disable_payload_signing: bool,
790
791    #[arg(long, env, default_value_t = DEFAULT_DISABLE_CONTENT_MD5_HEADER, help_heading = "Advanced",
792    long_help=r#"Disable Content-MD5 header for object uploads. It disables the ETag verification for the uploaded object.
793"#)]
794    disable_content_md5_header: bool,
795
796    #[arg(long, env, default_value_t = DEFAULT_DISABLE_EXPRESS_ONE_ZONE_ADDITIONAL_CHECKSUM, help_heading = "Advanced",
797    long_help=r#"Disable default additional checksum verification in Express One Zone storage class.
798 "#)]
799    disable_express_one_zone_additional_checksum: bool,
800
801    #[arg(long, env, requires = "delete", default_value_t = DEFAULT_DELETE_EXCLUDED, help_heading = "Advanced",
802    long_help=r#"When used in combination with --delete options, supplied --filter-exclude-regex patterns will not prevent an object from being deleted.
803"#)]
804    delete_excluded: bool,
805
806    #[arg(long, env, conflicts_with_all = ["enable_versioning", "point_in_time"], default_value_t = DEFAULT_IF_MATCH, help_heading = "Advanced", long_help=r#"Add an If-Match header for PutObject/CompleteMultipartUpload/DeleteObject requests.
807This is for like an optimistic lock."#)]
808    if_match: bool,
809
810    #[arg(long, env, conflicts_with_all = ["enable_versioning", "point_in_time"], requires = "server_side_copy", default_value_t = DEFAULT_COPY_SOURCE_IF_MATCH, help_heading = "Advanced", long_help=r#"Add an x-amz-copy-source-if-match header for CopyObject/UploadPartCopy requests.
811This is for like an optimistic lock."#)]
812    copy_source_if_match: bool,
813
814    /// Don't delete more than a specified number of objects
815    #[arg(long, env, requires = "delete", value_parser = clap::value_parser!(u64).range(1..), help_heading = "Advanced")]
816    max_delete: Option<u64>,
817
818    /// Suppress warnings related to Amazon S3 Glacier storage class objects during GetObject requests
819    #[arg(long, env, default_value_t = DEFAULT_IGNORE_GLACIER_WARNINGS, help_heading = "Advanced")]
820    ignore_glacier_warnings: bool,
821
822    #[cfg(feature = "lua_support")]
823    #[arg(
824        long,
825        env,
826        help_heading = "Lua scripting support",
827        value_parser = file_exist::is_file_exist,
828        long_help = r#"Path to the Lua script that is executed as preprocess callback"#
829    )]
830    preprocess_callback_lua_script: Option<String>,
831
832    #[cfg(feature = "lua_support")]
833    #[arg(
834        long,
835        env,
836        help_heading = "Lua scripting support",
837        value_parser = file_exist::is_file_exist,
838        long_help = r#"Path to the Lua script that is executed as event callback"#
839    )]
840    event_callback_lua_script: Option<String>,
841
842    #[cfg(feature = "lua_support")]
843    #[arg(
844        long,
845        env,
846        help_heading = "Lua scripting support",
847        value_parser = file_exist::is_file_exist,
848        long_help = r#"Path to the Lua script that is executed as filter callback"#
849    )]
850    filter_callback_lua_script: Option<String>,
851
852    #[cfg(feature = "lua_support")]
853    #[arg(long, env, conflicts_with_all = ["allow_lua_unsafe_vm"], default_value_t = DEFAULT_ALLOW_LUA_OS_LIBRARY, help_heading = "Lua scripting support", long_help="Allow Lua OS and I/O library functions in the Lua script.")]
854    allow_lua_os_library: bool,
855
856    #[cfg(feature = "lua_support")]
857    #[arg(long, env, default_value = DEFAULT_LUA_VM_MEMORY_LIMIT, value_parser = human_bytes::check_human_bytes_without_limit, help_heading = "Lua scripting support",
858    long_help=r#"Memory limit for the Lua VM. Allow suffixes: KB, KiB, MB, MiB, GB, GiB.
859Zero means no limit.
860If the memory limit is exceeded, the whole process will be terminated."#)]
861    lua_vm_memory_limit: String,
862
863    #[cfg(feature = "lua_support")]
864    #[arg(long, env, conflicts_with_all = ["allow_lua_os_library"], default_value_t = DEFAULT_ALLOW_LUA_UNSAFE_VM, help_heading = "Dangerous",
865    long_help=r#"Allow unsafe Lua VM functions in the Lua script.
866It allows the Lua script to load unsafe standard libraries or C modules."#)]
867    allow_lua_unsafe_vm: bool,
868
869    #[arg(long, env, conflicts_with_all = ["enable_versioning"], default_value_t = DEFAULT_SYNC_WITH_DELETE, help_heading = "Dangerous",
870    long_help=r#"Delete objects that exist in the target but not in the source.
871Exclude filters other than --filter-exclude-regex will not prevent an object from being deleted.
872[Warning] Since this can cause data loss, test first with the --dry-run option.
873 "#)]
874    delete: bool,
875
876    /// Unit test purpose only
877    #[arg(long, hide = true, default_value_t = false, help_heading = "Dangerous")]
878    allow_both_local_storage: bool,
879
880    /// test purpose only
881    #[cfg(feature = "e2e_test_dangerous_simulations")]
882    #[arg(long, hide = true, default_value_t = false, help_heading = "Dangerous")]
883    test_user_defined_callback: bool,
884
885    /// [dangerous] Test purpose only
886    #[cfg(feature = "e2e_test_dangerous_simulations")]
887    #[arg(long, hide = true, default_value_t = false, help_heading = "Dangerous")]
888    allow_e2e_test_dangerous_simulation: bool,
889
890    /// [dangerous] Test purpose only
891    #[cfg(feature = "e2e_test_dangerous_simulations")]
892    #[arg(long, hide = true, help_heading = "Dangerous")]
893    cancellation_point: Option<String>,
894
895    /// [dangerous] Test purpose only
896    #[cfg(feature = "e2e_test_dangerous_simulations")]
897    #[arg(long, hide = true, help_heading = "Dangerous")]
898    panic_simulation_point: Option<String>,
899
900    /// [dangerous] Test purpose only
901    #[cfg(feature = "e2e_test_dangerous_simulations")]
902    #[arg(long, hide = true, help_heading = "Dangerous")]
903    error_simulation_point: Option<String>,
904}
905
906pub fn parse_from_args<I, T>(args: I) -> Result<CLIArgs, clap::Error>
907where
908    I: IntoIterator<Item = T>,
909    T: Into<OsString> + Clone,
910{
911    CLIArgs::try_parse_from(args)
912}
913
914pub fn build_config_from_args<I, T>(args: I) -> Result<Config, String>
915where
916    I: IntoIterator<Item = T>,
917    T: Into<OsString> + Clone,
918{
919    let config_args = CLIArgs::try_parse_from(args).map_err(|e| e.to_string())?;
920    crate::Config::try_from(config_args)
921}
922
923impl CLIArgs {
924    // skipcq: RS-R1000
925    fn validate_storage_config(&self) -> Result<(), String> {
926        self.check_source_local_storage()?;
927        self.check_target_local_storage()?;
928        self.check_storage_conflict()?;
929        self.check_versioning_option_conflict()?;
930        self.check_tagging_option_conflict()?;
931        self.check_storage_class_conflict()?;
932        self.check_storage_credentials_conflict()?;
933        self.check_sse_conflict()?;
934        self.check_sse_c_conflict()?;
935        self.check_acl_conflict()?;
936        self.check_enable_additional_checksum_conflict()?;
937        self.check_additional_checksum_algorithm_conflict()?;
938        self.check_auto_chunksize_conflict()?;
939        self.check_metadata_conflict()?;
940        self.check_check_size_conflict()?;
941        self.check_check_e_tag_conflict()?;
942        self.check_check_mtime_and_e_tag_conflict()?;
943        self.check_ignore_symlinks_conflict()?;
944        self.check_no_guess_mime_type_conflict()?;
945        self.check_endpoint_url_conflict()?;
946        self.check_disable_payload_signing_conflict()?;
947        self.check_disable_content_md5_header_conflict()?;
948        self.check_full_object_checksum_conflict()?;
949        self.check_accelerate_conflict()?;
950        self.check_request_payer_conflict()?;
951        self.check_server_side_copy_conflict()?;
952        self.check_filter_include_metadata_regex_conflict()?;
953        self.check_filter_exclude_metadata_regex_conflict()?;
954        self.check_filter_include_tag_regex_conflict()?;
955        self.check_filter_exclude_tag_regex_conflict()?;
956        self.check_server_no_sync_system_metadata_conflict()?;
957        self.check_server_no_sync_user_defined_metadata_conflict()?;
958        self.check_point_in_time_conflict()?;
959        self.check_report_metadata_sync_status_conflict()?;
960        self.check_report_tagging_sync_status_conflict()?;
961        self.check_if_match_conflict()?;
962        self.check_copy_source_if_match_conflict()?;
963
964        Ok(())
965    }
966
967    fn check_source_local_storage(&self) -> Result<(), String> {
968        let source = storage_path::parse_storage_path(&self.source);
969
970        if let StoragePath::Local(path) = source {
971            if !path.is_dir() && self.delete {
972                return Err(SOURCE_LOCAL_STORAGE_FILE_WITH_DELETE_OPTION.to_string());
973            }
974            if !path.exists() {
975                return Err(SOURCE_LOCAL_STORAGE_PATH_NOT_FOUND.to_string());
976            }
977        }
978
979        Ok(())
980    }
981
982    fn check_target_local_storage(&self) -> Result<(), String> {
983        let target = storage_path::parse_storage_path(&self.target);
984
985        if let StoragePath::Local(path) = target {
986            let exist_result = path.try_exists();
987            if exist_result.is_err() {
988                return Err(TARGET_LOCAL_STORAGE_INVALID.to_string());
989            }
990        }
991        Ok(())
992    }
993
994    fn check_storage_conflict(&self) -> Result<(), String> {
995        if self.allow_both_local_storage {
996            return Ok(());
997        }
998
999        let source = storage_path::parse_storage_path(&self.source);
1000        let target = storage_path::parse_storage_path(&self.target);
1001
1002        if storage_path::is_both_storage_local(&source, &target) {
1003            return Err(NO_S3_STORAGE_SPECIFIED.to_string());
1004        }
1005
1006        Ok(())
1007    }
1008
1009    fn check_versioning_option_conflict(&self) -> Result<(), String> {
1010        if !self.enable_versioning {
1011            return Ok(());
1012        }
1013
1014        let source = storage_path::parse_storage_path(&self.source);
1015        let target = storage_path::parse_storage_path(&self.target);
1016
1017        if !storage_path::is_both_storage_s3(&source, &target) {
1018            return Err(LOCAL_STORAGE_SPECIFIED.to_string());
1019        }
1020
1021        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1022            if is_express_onezone_storage(&bucket) {
1023                return Err(VERSIONING_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1024            }
1025        }
1026
1027        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1028            if is_express_onezone_storage(&bucket) {
1029                return Err(VERSIONING_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1030            }
1031        }
1032
1033        Ok(())
1034    }
1035
1036    fn check_tagging_option_conflict(&self) -> Result<(), String> {
1037        let source = storage_path::parse_storage_path(&self.source);
1038        let target = storage_path::parse_storage_path(&self.target);
1039
1040        if self.sync_latest_tagging && !storage_path::is_both_storage_s3(&source, &target) {
1041            return Err(LOCAL_STORAGE_SPECIFIED.to_string());
1042        }
1043
1044        Ok(())
1045    }
1046
1047    fn check_storage_class_conflict(&self) -> Result<(), String> {
1048        let target = storage_path::parse_storage_path(&self.target);
1049
1050        if self.storage_class.is_some() && matches!(target, StoragePath::Local(_)) {
1051            return Err(LOCAL_STORAGE_SPECIFIED_WITH_STORAGE_CLASS.to_string());
1052        }
1053
1054        Ok(())
1055    }
1056
1057    fn check_storage_credentials_conflict(&self) -> Result<(), String> {
1058        let source = storage_path::parse_storage_path(&self.source);
1059        let target = storage_path::parse_storage_path(&self.target);
1060
1061        if matches!(source, StoragePath::Local(_))
1062            && (self.source_profile.is_some() || self.source_access_key.is_some())
1063        {
1064            return Err(NO_SOURCE_CREDENTIAL_REQUIRED.to_string());
1065        }
1066
1067        if matches!(target, StoragePath::Local(_))
1068            && (self.target_profile.is_some() || self.target_access_key.is_some())
1069        {
1070            return Err(NO_TARGET_CREDENTIAL_REQUIRED.to_string());
1071        }
1072
1073        Ok(())
1074    }
1075
1076    fn check_sse_conflict(&self) -> Result<(), String> {
1077        if self.sse.is_none() && self.sse_kms_key_id.is_none() {
1078            return Ok(());
1079        }
1080
1081        let target = storage_path::parse_storage_path(&self.target);
1082        if matches!(target, StoragePath::Local(_)) {
1083            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SSE.to_string());
1084        }
1085
1086        if self.sse_kms_key_id.is_some()
1087            && (self.sse.is_none()
1088                || (ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap()
1089                    != ServerSideEncryption::AwsKms
1090                    && ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap()
1091                        != ServerSideEncryption::AwsKmsDsse))
1092        {
1093            return Err(SSE_KMS_KEY_ID_ARGUMENTS_CONFLICT.to_string());
1094        }
1095
1096        Ok(())
1097    }
1098
1099    fn check_sse_c_conflict(&self) -> Result<(), String> {
1100        if self.source_sse_c.is_none() && self.target_sse_c.is_none() {
1101            return Ok(());
1102        }
1103
1104        if self.source_sse_c.is_some() {
1105            let source = storage_path::parse_storage_path(&self.source);
1106            if matches!(source, StoragePath::Local(_)) {
1107                return Err(LOCAL_STORAGE_SPECIFIED_WITH_SSE_C.to_string());
1108            }
1109        }
1110
1111        if self.target_sse_c.is_some() {
1112            let target = storage_path::parse_storage_path(&self.target);
1113            if matches!(target, StoragePath::Local(_)) {
1114                return Err(LOCAL_STORAGE_SPECIFIED_WITH_SSE_C.to_string());
1115            }
1116        }
1117
1118        Ok(())
1119    }
1120
1121    fn check_acl_conflict(&self) -> Result<(), String> {
1122        if self.acl.is_none() {
1123            return Ok(());
1124        }
1125
1126        let target = storage_path::parse_storage_path(&self.target);
1127        if matches!(target, StoragePath::Local(_)) {
1128            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACL.to_string());
1129        }
1130
1131        Ok(())
1132    }
1133
1134    fn check_additional_checksum_algorithm_conflict(&self) -> Result<(), String> {
1135        if self.additional_checksum_algorithm.is_none() {
1136            return Ok(());
1137        }
1138
1139        let target = storage_path::parse_storage_path(&self.target);
1140        if matches!(target, StoragePath::Local(_)) {
1141            return Err(
1142                TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ADDITIONAL_CHECKSUM_ALGORITHM.to_string(),
1143            );
1144        }
1145
1146        Ok(())
1147    }
1148
1149    fn check_enable_additional_checksum_conflict(&self) -> Result<(), String> {
1150        if !self.enable_additional_checksum {
1151            return Ok(());
1152        }
1153
1154        let source = storage_path::parse_storage_path(&self.source);
1155        if matches!(source, StoragePath::Local(_)) {
1156            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENABLE_ADDITIONAL_CHECKSUM.to_string());
1157        }
1158
1159        Ok(())
1160    }
1161
1162    fn check_auto_chunksize_conflict(&self) -> Result<(), String> {
1163        if !self.auto_chunksize {
1164            return Ok(());
1165        }
1166
1167        let source = storage_path::parse_storage_path(&self.source);
1168
1169        if !self.check_etag && !self.check_mtime_and_etag && matches!(source, StoragePath::Local(_))
1170        {
1171            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_AUTO_CHUNKSIZE.to_string());
1172        }
1173
1174        Ok(())
1175    }
1176
1177    fn check_metadata_conflict(&self) -> Result<(), String> {
1178        if self.cache_control.is_none()
1179            && self.content_disposition.is_none()
1180            && self.content_encoding.is_none()
1181            && self.content_language.is_none()
1182            && self.content_type.is_none()
1183            && self.website_redirect.is_none()
1184            && self.expires.is_none()
1185            && self.tagging.is_none()
1186            && !self.put_last_modified_metadata
1187        {
1188            return Ok(());
1189        }
1190
1191        let target = storage_path::parse_storage_path(&self.target);
1192        if matches!(target, StoragePath::Local(_)) {
1193            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_METADATA_OPTION.to_string());
1194        }
1195
1196        Ok(())
1197    }
1198
1199    fn check_check_size_conflict(&self) -> Result<(), String> {
1200        if self.check_size && self.remove_modified_filter && !self.head_each_target {
1201            return Err(CHECK_SIZE_CONFLICT.to_string());
1202        }
1203
1204        Ok(())
1205    }
1206
1207    fn check_check_e_tag_conflict(&self) -> Result<(), String> {
1208        if !self.check_etag {
1209            return Ok(());
1210        }
1211        if self.remove_modified_filter && !self.head_each_target {
1212            return Err(CHECK_ETAG_CONFLICT.to_string());
1213        }
1214
1215        if self.sse.is_some() {
1216            let sse = ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap();
1217            if sse == ServerSideEncryption::AwsKms || sse == ServerSideEncryption::AwsKmsDsse {
1218                return Err(CHECK_ETAG_CONFLICT_SSE_KMS.to_string());
1219            }
1220        }
1221
1222        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1223            if is_express_onezone_storage(&bucket) {
1224                return Err(CHECK_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1225            }
1226        }
1227
1228        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1229            if is_express_onezone_storage(&bucket) {
1230                return Err(CHECK_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1231            }
1232        }
1233
1234        Ok(())
1235    }
1236
1237    fn check_check_mtime_and_e_tag_conflict(&self) -> Result<(), String> {
1238        if !self.check_mtime_and_etag {
1239            return Ok(());
1240        }
1241
1242        if self.sse.is_some() {
1243            let sse = ServerSideEncryption::from_str(self.sse.as_ref().unwrap()).unwrap();
1244            if sse == ServerSideEncryption::AwsKms || sse == ServerSideEncryption::AwsKmsDsse {
1245                return Err(CHECK_MTIME_AND_ETAG_CONFLICT_SSE_KMS.to_string());
1246            }
1247        }
1248
1249        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1250            if is_express_onezone_storage(&bucket) {
1251                return Err(CHECK_MTIME_AND_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1252            }
1253        }
1254
1255        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.target) {
1256            if is_express_onezone_storage(&bucket) {
1257                return Err(CHECK_MTIME_AND_ETAG_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1258            }
1259        }
1260
1261        Ok(())
1262    }
1263
1264    fn check_ignore_symlinks_conflict(&self) -> Result<(), String> {
1265        if !self.ignore_symlinks {
1266            return Ok(());
1267        }
1268
1269        let source = storage_path::parse_storage_path(&self.source);
1270        if matches!(source, StoragePath::S3 { .. }) {
1271            return Err(SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_IGNORE_SYMLINKS.to_string());
1272        }
1273
1274        Ok(())
1275    }
1276
1277    fn check_no_guess_mime_type_conflict(&self) -> Result<(), String> {
1278        if !self.no_guess_mime_type {
1279            return Ok(());
1280        }
1281
1282        let source = storage_path::parse_storage_path(&self.source);
1283        if matches!(source, StoragePath::S3 { .. }) {
1284            return Err(SOURCE_REMOTE_STORAGE_SPECIFIED_WITH_NO_GUESS_MIME_TYPE.to_string());
1285        }
1286
1287        Ok(())
1288    }
1289
1290    fn check_endpoint_url_conflict(&self) -> Result<(), String> {
1291        let source = storage_path::parse_storage_path(&self.source);
1292        if matches!(source, StoragePath::Local(_)) && self.source_endpoint_url.is_some() {
1293            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL.to_string());
1294        }
1295
1296        let target = storage_path::parse_storage_path(&self.target);
1297        if matches!(target, StoragePath::Local(_)) && self.target_endpoint_url.is_some() {
1298            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ENDPOINT_URL.to_string());
1299        }
1300
1301        Ok(())
1302    }
1303
1304    fn check_disable_payload_signing_conflict(&self) -> Result<(), String> {
1305        if !self.disable_payload_signing {
1306            return Ok(());
1307        }
1308
1309        let target = storage_path::parse_storage_path(&self.target);
1310        if matches!(target, StoragePath::Local(_)) {
1311            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_PAYLOAD_SIGNING.to_string());
1312        }
1313
1314        Ok(())
1315    }
1316
1317    fn check_disable_content_md5_header_conflict(&self) -> Result<(), String> {
1318        if !self.disable_content_md5_header {
1319            return Ok(());
1320        }
1321
1322        let target = storage_path::parse_storage_path(&self.target);
1323        if matches!(target, StoragePath::Local(_)) {
1324            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_DISABLE_CONTENT_MD5_HEADER.to_string());
1325        }
1326
1327        Ok(())
1328    }
1329
1330    fn check_full_object_checksum_conflict(&self) -> Result<(), String> {
1331        if !self.full_object_checksum {
1332            return Ok(());
1333        }
1334
1335        let target = storage_path::parse_storage_path(&self.target);
1336        if matches!(target, StoragePath::Local(_)) {
1337            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_FULL_OBJECT_CHECKSUM.to_string());
1338        }
1339
1340        if let Some(additional_checksum_algorithm) = &self.additional_checksum_algorithm {
1341            if additional_checksum_algorithm == "SHA1" || additional_checksum_algorithm == "SHA256"
1342            {
1343                return Err(FULL_OBJECT_CHECKSUM_NOT_SUPPORTED.to_string());
1344            }
1345        }
1346
1347        Ok(())
1348    }
1349
1350    fn check_accelerate_conflict(&self) -> Result<(), String> {
1351        let source = storage_path::parse_storage_path(&self.source);
1352        if matches!(source, StoragePath::Local(_)) && self.source_accelerate {
1353            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE.to_string());
1354        }
1355
1356        let target = storage_path::parse_storage_path(&self.target);
1357        if matches!(target, StoragePath::Local(_)) && self.target_accelerate {
1358            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_ACCELERATE.to_string());
1359        }
1360
1361        Ok(())
1362    }
1363
1364    fn check_request_payer_conflict(&self) -> Result<(), String> {
1365        let source = storage_path::parse_storage_path(&self.source);
1366        if matches!(source, StoragePath::Local(_)) && self.source_request_payer {
1367            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER.to_string());
1368        }
1369
1370        let target = storage_path::parse_storage_path(&self.target);
1371        if matches!(target, StoragePath::Local(_)) && self.target_request_payer {
1372            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REQUEST_PAYER.to_string());
1373        }
1374
1375        Ok(())
1376    }
1377
1378    fn check_server_side_copy_conflict(&self) -> Result<(), String> {
1379        let source = storage_path::parse_storage_path(&self.source);
1380        if matches!(source, StoragePath::Local(_)) && self.server_side_copy {
1381            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY.to_string());
1382        }
1383
1384        let target = storage_path::parse_storage_path(&self.target);
1385        if matches!(target, StoragePath::Local(_)) && self.server_side_copy {
1386            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_SERVER_SIDE_COPY.to_string());
1387        }
1388
1389        Ok(())
1390    }
1391
1392    fn check_filter_include_metadata_regex_conflict(&self) -> Result<(), String> {
1393        let source = storage_path::parse_storage_path(&self.source);
1394        if matches!(source, StoragePath::Local(_)) && self.filter_include_metadata_regex.is_some() {
1395            return Err(
1396                SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_METADATA_REGEX.to_string(),
1397            );
1398        }
1399
1400        Ok(())
1401    }
1402
1403    fn check_filter_exclude_metadata_regex_conflict(&self) -> Result<(), String> {
1404        let source = storage_path::parse_storage_path(&self.source);
1405        if matches!(source, StoragePath::Local(_)) && self.filter_exclude_metadata_regex.is_some() {
1406            return Err(
1407                SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_METADATA_REGEX.to_string(),
1408            );
1409        }
1410
1411        Ok(())
1412    }
1413
1414    fn check_filter_include_tag_regex_conflict(&self) -> Result<(), String> {
1415        let source = storage_path::parse_storage_path(&self.source);
1416        if matches!(source, StoragePath::Local(_)) && self.filter_include_tag_regex.is_some() {
1417            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_INCLUDE_TAG_REGEX.to_string());
1418        }
1419
1420        Ok(())
1421    }
1422
1423    fn check_filter_exclude_tag_regex_conflict(&self) -> Result<(), String> {
1424        let source = storage_path::parse_storage_path(&self.source);
1425        if matches!(source, StoragePath::Local(_)) && self.filter_exclude_tag_regex.is_some() {
1426            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_FILTER_EXCLUDE_TAG_REGEX.to_string());
1427        }
1428
1429        Ok(())
1430    }
1431
1432    fn check_server_no_sync_system_metadata_conflict(&self) -> Result<(), String> {
1433        let source = storage_path::parse_storage_path(&self.source);
1434        if matches!(source, StoragePath::Local(_)) && self.no_sync_system_metadata {
1435            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA.to_string());
1436        }
1437
1438        let target = storage_path::parse_storage_path(&self.target);
1439        if matches!(target, StoragePath::Local(_)) && self.no_sync_system_metadata {
1440            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_SYSTEM_METADATA.to_string());
1441        }
1442
1443        Ok(())
1444    }
1445
1446    fn check_server_no_sync_user_defined_metadata_conflict(&self) -> Result<(), String> {
1447        let source = storage_path::parse_storage_path(&self.source);
1448        if matches!(source, StoragePath::Local(_)) && self.no_sync_user_defined_metadata {
1449            return Err(
1450                SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA.to_string(),
1451            );
1452        }
1453
1454        let target = storage_path::parse_storage_path(&self.target);
1455        if matches!(target, StoragePath::Local(_)) && self.no_sync_user_defined_metadata {
1456            return Err(
1457                TARGET_LOCAL_STORAGE_SPECIFIED_WITH_NO_SYNC_USER_DEFINED_METADATA.to_string(),
1458            );
1459        }
1460
1461        Ok(())
1462    }
1463    fn check_point_in_time_conflict(&self) -> Result<(), String> {
1464        if self.point_in_time.is_none() {
1465            return Ok(());
1466        }
1467
1468        let source = storage_path::parse_storage_path(&self.source);
1469        if matches!(source, StoragePath::Local(_)) {
1470            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_POINT_IN_TIME.to_string());
1471        }
1472
1473        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&self.source) {
1474            if is_express_onezone_storage(&bucket) {
1475                return Err(POINT_IN_TIME_NOT_SUPPORTED_WITH_EXPRESS_ONEZONE.to_string());
1476            }
1477        }
1478
1479        if Utc::now() < self.point_in_time.unwrap() {
1480            return Err(POINT_IN_TIME_VALUE_INVALID.to_string());
1481        }
1482
1483        Ok(())
1484    }
1485
1486    fn check_report_metadata_sync_status_conflict(&self) -> Result<(), String> {
1487        if !self.report_metadata_sync_status {
1488            return Ok(());
1489        }
1490
1491        let source = storage_path::parse_storage_path(&self.source);
1492        if matches!(source, StoragePath::Local(_)) {
1493            return Err(
1494                SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS.to_string(),
1495            );
1496        }
1497
1498        let target = storage_path::parse_storage_path(&self.target);
1499        if matches!(target, StoragePath::Local(_)) {
1500            return Err(
1501                TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_METADATA_SYNC_STATUS.to_string(),
1502            );
1503        }
1504
1505        Ok(())
1506    }
1507
1508    fn check_report_tagging_sync_status_conflict(&self) -> Result<(), String> {
1509        if !self.report_tagging_sync_status {
1510            return Ok(());
1511        }
1512
1513        let source = storage_path::parse_storage_path(&self.source);
1514        if matches!(source, StoragePath::Local(_)) {
1515            return Err(SOURCE_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS.to_string());
1516        }
1517
1518        let target = storage_path::parse_storage_path(&self.target);
1519        if matches!(target, StoragePath::Local(_)) {
1520            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_REPORT_TAGGING_SYNC_STATUS.to_string());
1521        }
1522
1523        Ok(())
1524    }
1525
1526    fn check_if_match_conflict(&self) -> Result<(), String> {
1527        if !self.if_match {
1528            return Ok(());
1529        }
1530
1531        let target = storage_path::parse_storage_path(&self.target);
1532        if matches!(target, StoragePath::Local(_)) {
1533            return Err(TARGET_LOCAL_STORAGE_SPECIFIED_WITH_IF_MATCH.to_string());
1534        }
1535
1536        if self.remove_modified_filter && !self.head_each_target {
1537            return Err(IF_MATCH_CONFLICT.to_string());
1538        }
1539
1540        Ok(())
1541    }
1542
1543    fn check_copy_source_if_match_conflict(&self) -> Result<(), String> {
1544        if !self.copy_source_if_match {
1545            return Ok(());
1546        }
1547
1548        let source = storage_path::parse_storage_path(&self.source);
1549        let target = storage_path::parse_storage_path(&self.target);
1550
1551        if !storage_path::is_both_storage_s3(&source, &target) {
1552            return Err(LOCAL_STORAGE_SPECIFIED.to_string());
1553        }
1554
1555        Ok(())
1556    }
1557
1558    fn build_client_configs(
1559        &self,
1560        request_checksum_calculation: RequestChecksumCalculation,
1561    ) -> (Option<ClientConfig>, Option<ClientConfig>) {
1562        let source_credential = if let Some(source_profile) = self.source_profile.clone() {
1563            Some(S3Credentials::Profile(source_profile))
1564        } else if self.source_access_key.is_some() {
1565            self.source_access_key
1566                .clone()
1567                .map(|access_key| S3Credentials::Credentials {
1568                    access_keys: AccessKeys {
1569                        access_key,
1570                        secret_access_key: self
1571                            .source_secret_access_key
1572                            .as_ref()
1573                            .unwrap()
1574                            .to_string(),
1575                        session_token: self.source_session_token.clone(),
1576                    },
1577                })
1578        } else {
1579            Some(S3Credentials::FromEnvironment)
1580        };
1581
1582        let target_credential = if let Some(target_profile) = self.target_profile.clone() {
1583            Some(S3Credentials::Profile(target_profile))
1584        } else if self.target_access_key.is_some() {
1585            self.target_access_key
1586                .clone()
1587                .map(|access_key| S3Credentials::Credentials {
1588                    access_keys: AccessKeys {
1589                        access_key,
1590                        secret_access_key: self
1591                            .target_secret_access_key
1592                            .as_ref()
1593                            .unwrap()
1594                            .to_string(),
1595                        session_token: self.target_session_token.clone(),
1596                    },
1597                })
1598        } else {
1599            Some(S3Credentials::FromEnvironment)
1600        };
1601
1602        let parallel_upload_semaphore =
1603            Arc::new(Semaphore::new(self.max_parallel_uploads as usize));
1604
1605        let source_request_payer = if self.source_request_payer {
1606            Some(RequestPayer::Requester)
1607        } else {
1608            None
1609        };
1610
1611        let source_client_config = source_credential.map(|source_credential| ClientConfig {
1612            client_config_location: ClientConfigLocation {
1613                aws_config_file: self.aws_config_file.clone(),
1614                aws_shared_credentials_file: self.aws_shared_credentials_file.clone(),
1615            },
1616            credential: source_credential,
1617            region: self.source_region.clone(),
1618            endpoint_url: self.source_endpoint_url.clone(),
1619            force_path_style: self.source_force_path_style,
1620            retry_config: RetryConfig {
1621                aws_max_attempts: self.aws_max_attempts,
1622                initial_backoff_milliseconds: self.initial_backoff_milliseconds,
1623            },
1624            cli_timeout_config: CLITimeoutConfig {
1625                operation_timeout_milliseconds: self.operation_timeout_milliseconds,
1626                operation_attempt_timeout_milliseconds: self.operation_attempt_timeout_milliseconds,
1627                connect_timeout_milliseconds: self.connect_timeout_milliseconds,
1628                read_timeout_milliseconds: self.read_timeout_milliseconds,
1629            },
1630            disable_stalled_stream_protection: self.disable_stalled_stream_protection,
1631            request_checksum_calculation: RequestChecksumCalculation::WhenRequired,
1632            parallel_upload_semaphore: parallel_upload_semaphore.clone(),
1633            accelerate: self.source_accelerate,
1634            request_payer: source_request_payer,
1635        });
1636
1637        let target_request_payer = if self.target_request_payer {
1638            Some(RequestPayer::Requester)
1639        } else {
1640            None
1641        };
1642
1643        let target_client_config = target_credential.map(|target_credential| ClientConfig {
1644            client_config_location: ClientConfigLocation {
1645                aws_config_file: self.aws_config_file.clone(),
1646                aws_shared_credentials_file: self.aws_shared_credentials_file.clone(),
1647            },
1648            credential: target_credential,
1649            region: self.target_region.clone(),
1650            endpoint_url: self.target_endpoint_url.clone(),
1651            force_path_style: self.target_force_path_style,
1652            retry_config: RetryConfig {
1653                aws_max_attempts: self.aws_max_attempts,
1654                initial_backoff_milliseconds: self.initial_backoff_milliseconds,
1655            },
1656            cli_timeout_config: CLITimeoutConfig {
1657                operation_timeout_milliseconds: self.operation_timeout_milliseconds,
1658                operation_attempt_timeout_milliseconds: self.operation_attempt_timeout_milliseconds,
1659                connect_timeout_milliseconds: self.connect_timeout_milliseconds,
1660                read_timeout_milliseconds: self.read_timeout_milliseconds,
1661            },
1662            disable_stalled_stream_protection: self.disable_stalled_stream_protection,
1663            request_checksum_calculation,
1664            parallel_upload_semaphore,
1665            accelerate: self.target_accelerate,
1666            request_payer: target_request_payer,
1667        });
1668
1669        (source_client_config, target_client_config)
1670    }
1671}
1672
1673impl TryFrom<CLIArgs> for Config {
1674    type Error = String;
1675
1676    fn try_from(value: CLIArgs) -> Result<Self, Self::Error> {
1677        value.validate_storage_config()?;
1678
1679        let original_cloned_value = value.clone();
1680
1681        let mut tracing_config = value.verbosity.log_level().map(|log_level| TracingConfig {
1682            tracing_level: log_level,
1683            json_tracing: value.json_tracing,
1684            aws_sdk_tracing: value.aws_sdk_tracing,
1685            span_events_tracing: value.span_events_tracing,
1686            disable_color_tracing: value.disable_color_tracing,
1687        });
1688
1689        let storage_class = value
1690            .storage_class
1691            .map(|storage_class| StorageClass::from_str(&storage_class).unwrap());
1692
1693        let sse = value
1694            .sse
1695            .map(|sse| ServerSideEncryption::from_str(&sse).unwrap());
1696
1697        let canned_acl = value
1698            .acl
1699            .map(|acl| ObjectCannedAcl::from_str(&acl).unwrap());
1700
1701        let include_regex = value
1702            .filter_include_regex
1703            .map(|regex| Regex::new(&regex).unwrap());
1704
1705        let exclude_regex = value
1706            .filter_exclude_regex
1707            .map(|regex| Regex::new(&regex).unwrap());
1708        let include_content_type_regex = value
1709            .filter_include_content_type_regex
1710            .map(|regex| Regex::new(&regex).unwrap());
1711        let exclude_content_type_regex = value
1712            .filter_exclude_content_type_regex
1713            .map(|regex| Regex::new(&regex).unwrap());
1714
1715        let include_metadata_regex = value
1716            .filter_include_metadata_regex
1717            .map(|regex| Regex::new(&regex).unwrap());
1718        let exclude_metadata_regex = value
1719            .filter_exclude_metadata_regex
1720            .map(|regex| Regex::new(&regex).unwrap());
1721        let include_tag_regex = value
1722            .filter_include_tag_regex
1723            .map(|regex| Regex::new(&regex).unwrap());
1724        let exclude_tag_regex = value
1725            .filter_exclude_tag_regex
1726            .map(|regex| Regex::new(&regex).unwrap());
1727
1728        let rate_limit_bandwidth = value
1729            .rate_limit_bandwidth
1730            .map(|bandwidth| human_bytes::parse_human_bandwidth(&bandwidth).unwrap());
1731
1732        let mut additional_checksum_algorithm = value
1733            .additional_checksum_algorithm
1734            .map(|algorithm| ChecksumAlgorithm::from(algorithm.as_str()));
1735
1736        let check_additional_checksum_algorithm = value
1737            .check_additional_checksum
1738            .map(|algorithm| ChecksumAlgorithm::from(algorithm.as_str()));
1739
1740        let check_mtime_and_additional_checksum = value
1741            .check_mtime_and_additional_checksum
1742            .map(|algorithm| ChecksumAlgorithm::from(algorithm.as_str()));
1743
1744        let mut checksum_mode = if value.enable_additional_checksum {
1745            Some(ChecksumMode::Enabled)
1746        } else {
1747            None
1748        };
1749
1750        let tagging = value
1751            .tagging
1752            .map(|tagging| tagging::parse_tagging(&tagging).unwrap());
1753        let filter_larger_size = value
1754            .filter_larger_size
1755            .map(|human_bytes| human_bytes::parse_human_bytes_without_limit(&human_bytes).unwrap());
1756        let filter_smaller_size = value
1757            .filter_smaller_size
1758            .map(|human_bytes| human_bytes::parse_human_bytes_without_limit(&human_bytes).unwrap());
1759
1760        let metadata = if value.metadata.is_some() {
1761            Some(metadata::parse_metadata(&value.metadata.unwrap())?)
1762        } else {
1763            None
1764        };
1765
1766        let mut full_object_checksum = if additional_checksum_algorithm
1767            .as_ref()
1768            .is_some_and(|algorithm| algorithm == &ChecksumAlgorithm::Crc64Nvme)
1769        {
1770            true
1771        } else {
1772            value.full_object_checksum
1773        };
1774
1775        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&value.source) {
1776            if is_express_onezone_storage(&bucket)
1777                && !value.disable_express_one_zone_additional_checksum
1778            {
1779                checksum_mode = Some(ChecksumMode::Enabled);
1780            }
1781        }
1782
1783        let mut request_checksum_calculation = RequestChecksumCalculation::WhenRequired;
1784        if let StoragePath::S3 { bucket, .. } = storage_path::parse_storage_path(&value.target) {
1785            if is_express_onezone_storage(&bucket)
1786                && additional_checksum_algorithm.is_none()
1787                && !value.disable_express_one_zone_additional_checksum
1788            {
1789                additional_checksum_algorithm = Some(ChecksumAlgorithm::Crc64Nvme);
1790                full_object_checksum = true;
1791                request_checksum_calculation = RequestChecksumCalculation::WhenSupported;
1792            } else if additional_checksum_algorithm.is_some() {
1793                request_checksum_calculation = RequestChecksumCalculation::WhenSupported;
1794            }
1795        }
1796
1797        let (source_client_config, target_client_config) =
1798            original_cloned_value.build_client_configs(request_checksum_calculation);
1799
1800        #[allow(unused_assignments)]
1801        #[allow(unused_mut)]
1802        let mut allow_e2e_test_dangerous_simulation = false;
1803
1804        #[allow(unused_assignments)]
1805        #[allow(unused_mut)]
1806        let mut cancellation_point = None;
1807        #[allow(unused_assignments)]
1808        #[allow(unused_mut)]
1809        let mut panic_simulation_point = None;
1810        #[allow(unused_assignments)]
1811        #[allow(unused_mut)]
1812        let mut error_simulation_point = None;
1813        #[cfg(feature = "e2e_test_dangerous_simulations")]
1814        {
1815            allow_e2e_test_dangerous_simulation = value.allow_e2e_test_dangerous_simulation;
1816            cancellation_point = value.cancellation_point;
1817            panic_simulation_point = value.panic_simulation_point;
1818            error_simulation_point = value.error_simulation_point;
1819        }
1820
1821        #[allow(unused_assignments)]
1822        #[allow(unused_mut)]
1823        let mut test_user_defined_callback = false;
1824        #[cfg(feature = "e2e_test_dangerous_simulations")]
1825        {
1826            test_user_defined_callback = value.test_user_defined_callback;
1827        }
1828
1829        // If point-in-time is specified, we need to remove the modified filter.
1830        // Because target objects may need to be modified.
1831        let mut remove_modified_filter = if value.point_in_time.is_some() {
1832            true
1833        } else {
1834            value.remove_modified_filter
1835        };
1836
1837        // Reporting need to be done in dry run mode.
1838        let dry_run = if value.report_sync_status {
1839            true
1840        } else {
1841            value.dry_run
1842        };
1843
1844        // If report_sync_status is true, we need to remove the modified filter.
1845        // And must warn as error.
1846        let warn_as_error;
1847        let check_etag;
1848        let head_each_target;
1849        if value.report_sync_status {
1850            remove_modified_filter = true;
1851            warn_as_error = true;
1852            head_each_target = true;
1853            check_etag = check_additional_checksum_algorithm.is_none();
1854        } else {
1855            warn_as_error = value.warn_as_error;
1856            check_etag = value.check_etag;
1857            head_each_target = value.head_each_target;
1858        }
1859
1860        if dry_run {
1861            if tracing_config.is_none() {
1862                tracing_config = Some(TracingConfig {
1863                    tracing_level: log::Level::Info,
1864                    json_tracing: DEFAULT_JSON_TRACING,
1865                    aws_sdk_tracing: DEFAULT_AWS_SDK_TRACING,
1866                    span_events_tracing: DEFAULT_SPAN_EVENTS_TRACING,
1867                    disable_color_tracing: DEFAULT_DISABLE_COLOR_TRACING,
1868                });
1869            } else if tracing_config.unwrap().tracing_level < log::Level::Info {
1870                tracing_config = Some(TracingConfig {
1871                    tracing_level: log::Level::Info,
1872                    json_tracing: tracing_config.unwrap().json_tracing,
1873                    aws_sdk_tracing: tracing_config.unwrap().aws_sdk_tracing,
1874                    span_events_tracing: tracing_config.unwrap().span_events_tracing,
1875                    disable_color_tracing: tracing_config.unwrap().disable_color_tracing,
1876                });
1877            }
1878        }
1879
1880        cfg_if! {
1881            if #[cfg(feature = "lua_support")] {
1882                let preprocess_callback_lua_script =  value.preprocess_callback_lua_script.clone();
1883                let event_callback_lua_script = value.event_callback_lua_script.clone();
1884                let filter_callback_lua_script = value.filter_callback_lua_script.clone();
1885                let allow_lua_os_library = value.allow_lua_os_library;
1886                let allow_lua_unsafe_vm = value.allow_lua_unsafe_vm;
1887                let lua_vm_memory_limit = human_bytes::parse_human_bytes_without_low_limit(&value.lua_vm_memory_limit)? as usize;
1888            } else {
1889                let preprocess_callback_lua_script =  None;
1890                let event_callback_lua_script = None;
1891                let filter_callback_lua_script = None;
1892                let allow_lua_os_library = false;
1893                let allow_lua_unsafe_vm = false;
1894                let lua_vm_memory_limit = 0;
1895            }
1896        }
1897
1898        #[allow(unused_mut)]
1899        let mut preprocess_manager = PreprocessManager::new();
1900        cfg_if! {
1901            if #[cfg(feature = "lua_support")] {
1902                if let Some(preprocess_callback_lua_script) = preprocess_callback_lua_script.as_ref() {
1903                    let mut lua_preprocess_callback = crate::callback::lua_preprocess_callback::LuaPreprocessCallback::new(
1904                        lua_vm_memory_limit,
1905                        allow_lua_os_library,
1906                        allow_lua_unsafe_vm,
1907                    );
1908                    if let Err(e) =
1909                        lua_preprocess_callback.load_and_compile(preprocess_callback_lua_script.as_str())
1910                    {
1911                        return Err(LUA_SCRIPT_LOAD_ERROR.to_string() + &e.to_string());
1912                    }
1913                    preprocess_manager.register_callback(lua_preprocess_callback);
1914                }
1915            }
1916        }
1917
1918        #[allow(unused_mut)]
1919        let mut event_manager = EventManager::new();
1920        cfg_if! {
1921            if #[cfg(feature = "lua_support")] {
1922                if let Some(event_callback_lua_script) = event_callback_lua_script.as_ref() {
1923                    let mut lua_event_callback = crate::callback::lua_event_callback::LuaEventCallback::new(
1924                        lua_vm_memory_limit,
1925                        allow_lua_os_library,
1926                        allow_lua_unsafe_vm,
1927                    );
1928                    if let Err(e) = lua_event_callback.load_and_compile(event_callback_lua_script.as_str())
1929                    {
1930                        return Err(LUA_SCRIPT_LOAD_ERROR.to_string() + &e.to_string());
1931                    }
1932                    // Lua event callback is registered for all events.
1933                    event_manager.register_callback(EventType::ALL_EVENTS, lua_event_callback, dry_run);
1934                }
1935            }
1936        }
1937
1938        #[allow(unused_mut)]
1939        let mut filter_manager = FilterManager::new();
1940        cfg_if! {
1941            if #[cfg(feature = "lua_support")] {
1942                if let Some(filter_callback_lua_script) = filter_callback_lua_script.as_ref() {
1943                    let mut lua_filter_callback = crate::callback::lua_filter_callback::LuaFilterCallback::new(
1944                        lua_vm_memory_limit,
1945                        allow_lua_os_library,
1946                        allow_lua_unsafe_vm,
1947                    );
1948                    if let Err(e) =
1949                        lua_filter_callback.load_and_compile(filter_callback_lua_script.as_str())
1950                    {
1951                        return Err(LUA_SCRIPT_LOAD_ERROR.to_string() + &e.to_string());
1952                    }
1953                    filter_manager.register_callback(lua_filter_callback);
1954                }
1955            }
1956        }
1957
1958        Ok(Config {
1959            source: storage_path::parse_storage_path(&value.source),
1960            target: storage_path::parse_storage_path(&value.target),
1961
1962            show_no_progress: value.show_no_progress,
1963
1964            source_client_config,
1965            target_client_config,
1966
1967            tracing_config,
1968
1969            force_retry_config: ForceRetryConfig {
1970                force_retry_count: value.force_retry_count,
1971                force_retry_interval_milliseconds: value.force_retry_interval_milliseconds,
1972            },
1973
1974            transfer_config: TransferConfig {
1975                multipart_threshold: human_bytes::parse_human_bytes(&value.multipart_threshold)?,
1976                multipart_chunksize: human_bytes::parse_human_bytes(&value.multipart_chunksize)?,
1977                auto_chunksize: value.auto_chunksize,
1978            },
1979
1980            worker_size: value.worker_size,
1981
1982            warn_as_error,
1983            follow_symlinks: !value.ignore_symlinks,
1984            head_each_target,
1985            sync_with_delete: value.delete,
1986            disable_tagging: value.disable_tagging,
1987            sync_latest_tagging: value.sync_latest_tagging,
1988            server_side_copy: value.server_side_copy,
1989            no_guess_mime_type: value.no_guess_mime_type,
1990            disable_multipart_verify: value.disable_multipart_verify,
1991            disable_etag_verify: value.disable_etag_verify,
1992            disable_additional_checksum_verify: value.disable_additional_checksum_verify,
1993            enable_versioning: value.enable_versioning,
1994            point_in_time: value.point_in_time,
1995            storage_class,
1996            sse,
1997            sse_kms_key_id: SseKmsKeyId {
1998                id: value.sse_kms_key_id,
1999            },
2000            source_sse_c: value.source_sse_c,
2001            source_sse_c_key: SseCustomerKey {
2002                key: value.source_sse_c_key,
2003            },
2004            source_sse_c_key_md5: value.source_sse_c_key_md5,
2005            target_sse_c: value.target_sse_c,
2006            target_sse_c_key: SseCustomerKey {
2007                key: value.target_sse_c_key,
2008            },
2009            target_sse_c_key_md5: value.target_sse_c_key_md5,
2010            canned_acl,
2011            additional_checksum_algorithm,
2012            additional_checksum_mode: checksum_mode,
2013            dry_run,
2014            rate_limit_objects: value.rate_limit_objects,
2015            rate_limit_bandwidth,
2016            max_parallel_listings: value.max_parallel_listings,
2017            object_listing_queue_size: value.object_listing_queue_size,
2018            max_parallel_listing_max_depth: value.max_parallel_listing_max_depth,
2019            allow_parallel_listings_in_express_one_zone: value
2020                .allow_parallel_listings_in_express_one_zone,
2021            cache_control: value.cache_control,
2022            content_disposition: value.content_disposition,
2023            content_encoding: value.content_encoding,
2024            content_language: value.content_language,
2025            content_type: value.content_type,
2026            expires: value.expires,
2027            metadata,
2028            website_redirect: value.website_redirect,
2029            no_sync_system_metadata: value.no_sync_system_metadata,
2030            no_sync_user_defined_metadata: value.no_sync_user_defined_metadata,
2031            tagging,
2032            filter_config: FilterConfig {
2033                before_time: value.filter_mtime_before,
2034                after_time: value.filter_mtime_after,
2035                remove_modified_filter,
2036                check_size: value.check_size,
2037                check_etag,
2038                check_mtime_and_etag: value.check_mtime_and_etag,
2039                check_checksum_algorithm: check_additional_checksum_algorithm,
2040                check_mtime_and_additional_checksum,
2041                include_regex,
2042                exclude_regex,
2043                include_content_type_regex,
2044                exclude_content_type_regex,
2045                include_metadata_regex,
2046                exclude_metadata_regex,
2047                include_tag_regex,
2048                exclude_tag_regex,
2049                larger_size: filter_larger_size,
2050                smaller_size: filter_smaller_size,
2051                filter_manager,
2052            },
2053            max_keys: value.max_keys,
2054            put_last_modified_metadata: value.put_last_modified_metadata,
2055            auto_complete_shell: value.auto_complete_shell,
2056            disable_payload_signing: value.disable_payload_signing,
2057            disable_content_md5_header: value.disable_content_md5_header,
2058            delete_excluded: value.delete_excluded,
2059            full_object_checksum,
2060            allow_e2e_test_dangerous_simulation,
2061            test_user_defined_callback,
2062            cancellation_point,
2063            panic_simulation_point,
2064            error_simulation_point,
2065            source_accelerate: value.source_accelerate,
2066            target_accelerate: value.target_accelerate,
2067            source_request_payer: value.source_request_payer,
2068            target_request_payer: value.target_request_payer,
2069            report_sync_status: value.report_sync_status,
2070            report_metadata_sync_status: value.report_metadata_sync_status,
2071            report_tagging_sync_status: value.report_tagging_sync_status,
2072            event_manager,
2073            preprocess_manager,
2074            preprocess_callback_lua_script,
2075            event_callback_lua_script,
2076            filter_callback_lua_script,
2077            allow_lua_os_library,
2078            allow_lua_unsafe_vm,
2079            lua_vm_memory_limit,
2080            if_match: value.if_match,
2081            copy_source_if_match: value.copy_source_if_match,
2082            max_delete: value.max_delete,
2083            ignore_glacier_warnings: value.ignore_glacier_warnings,
2084        })
2085    }
2086}
2087
2088fn is_express_onezone_storage(bucket: &str) -> bool {
2089    bucket.ends_with(EXPRESS_ONEZONE_STORAGE_SUFFIX)
2090}