// Copyright 2024 RustFS Team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::{GroupAddRemove, GroupDesc, SRSvcAccCreate, UserInfo}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::{BTreeMap, HashMap}; use std::fmt; use time::OffsetDateTime; pub const SITE_REPL_API_VERSION: &str = "1"; #[derive(Clone, Serialize, Deserialize, Default)] pub struct PeerSite { #[serde(default)] pub name: String, #[serde(rename = "endpoints", default)] pub endpoint: String, #[serde(rename = "accessKey", default)] pub access_key: String, #[serde(rename = "secretKey", default)] pub secret_key: String, #[serde(rename = "skipTlsVerify", default)] pub skip_tls_verify: bool, #[serde(rename = "caCertPem", default)] pub ca_cert_pem: String, } impl fmt::Debug for PeerSite { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PeerSite") .field("name", &self.name) .field("endpoint", &self.endpoint) .field("skip_tls_verify", &self.skip_tls_verify) .field("has_custom_ca", &!self.ca_cert_pem.is_empty()) .finish() } } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ReplicateAddStatus { #[serde(default)] pub success: bool, #[serde(default)] pub status: String, #[serde(rename = "errorDetail", skip_serializing_if = "String::is_empty", default)] pub err_detail: String, #[serde(rename = "initialSyncErrorMessage", skip_serializing_if = "String::is_empty", default)] pub initial_sync_error_message: String, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SiteReplicationInfo { #[serde(default)] pub enabled: bool, #[serde(default, skip_serializing_if = "String::is_empty")] pub name: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub sites: Vec, #[serde(rename = "serviceAccountAccessKey", default, skip_serializing_if = "String::is_empty")] pub service_account_access_key: String, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRPeerJoinReq { #[serde(rename = "svcAcctAccessKey", default)] pub svc_acct_access_key: String, #[serde(rename = "svcAcctSecretKey", default)] pub svc_acct_secret_key: String, #[serde(rename = "svcAcctParent", default)] pub svc_acct_parent: String, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub peers: BTreeMap, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct BucketBandwidth { #[serde(rename = "bandwidthLimitPerBucket", default)] pub limit: u64, #[serde(default)] pub set: bool, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum SyncStatus { #[serde(rename = "enable")] Enable, #[serde(rename = "disable")] Disable, #[default] #[serde(other)] Unknown, } #[derive(Clone, Serialize, Deserialize, Default)] pub struct PeerInfo { #[serde(default)] pub endpoint: String, #[serde(default)] pub name: String, #[serde(rename = "deploymentID", default)] pub deployment_id: String, #[serde(rename = "sync", default)] pub sync_state: SyncStatus, #[serde(rename = "defaultbandwidth", default)] pub default_bandwidth: BucketBandwidth, #[serde(rename = "replicate-ilm-expiry", default)] pub replicate_ilm_expiry: bool, #[serde(rename = "objectNamingMode", default, skip_serializing_if = "String::is_empty")] pub object_naming_mode: String, #[serde(rename = "skipTlsVerify", default)] pub skip_tls_verify: bool, #[serde(rename = "caCertPem", default)] pub ca_cert_pem: String, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } impl fmt::Debug for PeerInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PeerInfo") .field("endpoint", &self.endpoint) .field("name", &self.name) .field("deployment_id", &self.deployment_id) .field("skip_tls_verify", &self.skip_tls_verify) .field("has_custom_ca", &!self.ca_cert_pem.is_empty()) .finish() } } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRPolicyMapping { #[serde(rename = "userOrGroup", default)] pub user_or_group: String, #[serde(rename = "userType", default)] pub user_type: u64, #[serde(rename = "isGroup", default)] pub is_group: bool, #[serde(default)] pub policy: String, #[serde(default, skip_serializing_if = "String::is_empty")] pub provider: String, #[serde(rename = "configID", default, skip_serializing_if = "String::is_empty")] pub config_id: String, #[serde( rename = "createdAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub created_at: Option, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRSTSCredential { #[serde(rename = "accessKey", default)] pub access_key: String, #[serde(rename = "secretKey", default)] pub secret_key: String, #[serde(rename = "sessionToken", default)] pub session_token: String, #[serde(rename = "parentUser", default)] pub parent_user: String, #[serde(rename = "parentPolicyMapping", default, skip_serializing_if = "String::is_empty")] pub parent_policy_mapping: String, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRExternalUser { #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, #[serde(default)] pub name: String, #[serde(rename = "isDeleteReq", default)] pub is_delete_req: bool, #[serde(rename = "openIDUser", skip_serializing_if = "Option::is_none")] pub open_id_user: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRLDAPUser { #[serde(default)] pub dn: String, #[serde(default)] pub username: String, #[serde(rename = "validatedDN", default, skip_serializing_if = "String::is_empty")] pub validated_dn: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub groups: Vec, #[serde(default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none")] pub expiry: Option, #[serde(rename = "isDeleteReq", default)] pub is_delete_req: bool, #[serde(rename = "configName", default, skip_serializing_if = "String::is_empty")] pub config_name: String, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Serialize, Deserialize, Default)] pub struct SRIAMUser { #[serde(rename = "accessKey", default)] pub access_key: String, #[serde(rename = "isDeleteReq", default)] pub is_delete_req: bool, #[serde(rename = "userReq", skip_serializing_if = "Option::is_none")] pub user_req: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Serialize, Deserialize, Default)] pub struct SRGroupInfo { #[serde(rename = "updateReq", default)] pub update_req: GroupAddRemove, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRSvcAccUpdate { #[serde(rename = "accessKey", default)] pub access_key: String, #[serde(rename = "secretKey", default)] pub secret_key: String, #[serde(default)] pub status: String, #[serde(default)] pub name: String, #[serde(default)] pub description: String, #[serde(rename = "sessionPolicy", default)] pub session_policy: crate::SRSessionPolicy, #[serde( rename = "expiration", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub expiration: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRSvcAccDelete { #[serde(rename = "accessKey", default)] pub access_key: String, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRSvcAccReplicationEnvelope { pub version: u64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRSvcAccChange { #[serde(rename = "crSvcAccCreate", skip_serializing_if = "Option::is_none")] pub create: Option, #[serde(rename = "crSvcAccUpdate", skip_serializing_if = "Option::is_none")] pub update: Option, #[serde(rename = "crSvcAccDelete", skip_serializing_if = "Option::is_none")] pub delete: Option, #[serde(rename = "oidcServiceAccountEnvelope", skip_serializing_if = "Option::is_none")] pub oidc_service_account_envelope: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRCredInfo { #[serde(rename = "accessKey", default)] pub access_key: String, #[serde(rename = "iamUserType", default)] pub iam_user_type: u64, #[serde(rename = "isDeleteReq", default)] pub is_delete_req: bool, #[serde(rename = "userIdentityJSON", default, skip_serializing_if = "Option::is_none")] pub user_identity_json: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Serialize, Deserialize, Default)] pub struct SRIAMItem { #[serde(default)] pub r#type: String, #[serde(default)] pub name: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub policy: Option, #[serde(rename = "policyMapping", skip_serializing_if = "Option::is_none")] pub policy_mapping: Option, #[serde(rename = "groupInfo", skip_serializing_if = "Option::is_none")] pub group_info: Option, #[serde(rename = "credentialChange", skip_serializing_if = "Option::is_none")] pub credential_info: Option, #[serde(rename = "serviceAccountChange", skip_serializing_if = "Option::is_none")] pub svc_acc_change: Option, #[serde(rename = "stsCredential", skip_serializing_if = "Option::is_none")] pub sts_credential: Option, #[serde(rename = "iamUser", skip_serializing_if = "Option::is_none")] pub iam_user: Option, #[serde(rename = "externalUser", skip_serializing_if = "Option::is_none")] pub external_user: Option, #[serde(rename = "ldapUser", skip_serializing_if = "Option::is_none")] pub ldap_user: Option, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRBucketMeta { #[serde(default)] pub r#type: String, #[serde(default)] pub bucket: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub policy: Option, #[serde(rename = "versioningConfig", skip_serializing_if = "Option::is_none")] pub versioning: Option, #[serde(skip_serializing_if = "Option::is_none")] pub tags: Option, #[serde(rename = "objectLockConfig", skip_serializing_if = "Option::is_none")] pub object_lock_config: Option, #[serde(rename = "sseConfig", skip_serializing_if = "Option::is_none")] pub sse_config: Option, #[serde(rename = "replicationConfig", skip_serializing_if = "Option::is_none")] pub replication_config: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub quota: Option, #[serde(rename = "expLCConfig", skip_serializing_if = "Option::is_none")] pub expiry_lc_config: Option, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, #[serde( rename = "expiryUpdatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub expiry_updated_at: Option, #[serde(skip_serializing_if = "Option::is_none")] pub cors: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRBucketInfo { #[serde(default)] pub bucket: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub policy: Option, #[serde(rename = "versioningConfig", skip_serializing_if = "Option::is_none")] pub versioning: Option, #[serde(skip_serializing_if = "Option::is_none")] pub tags: Option, #[serde(rename = "objectLockConfig", skip_serializing_if = "Option::is_none")] pub object_lock_config: Option, #[serde(rename = "sseConfig", skip_serializing_if = "Option::is_none")] pub sse_config: Option, #[serde(rename = "replicationConfig", skip_serializing_if = "Option::is_none")] pub replication_config: Option, #[serde(rename = "quotaConfig", skip_serializing_if = "Option::is_none")] pub quota_config: Option, #[serde(rename = "expLCConfig", skip_serializing_if = "Option::is_none")] pub expiry_lc_config: Option, #[serde(rename = "corsConfig", skip_serializing_if = "Option::is_none")] pub cors_config: Option, #[serde( rename = "policyTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub policy_updated_at: Option, #[serde( rename = "tagTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub tag_config_updated_at: Option, #[serde( rename = "olockTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub object_lock_config_updated_at: Option, #[serde( rename = "sseTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub sse_config_updated_at: Option, #[serde( rename = "versioningTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub versioning_config_updated_at: Option, #[serde( rename = "replicationConfigTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub replication_config_updated_at: Option, #[serde( rename = "quotaTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub quota_config_updated_at: Option, #[serde( rename = "expLCTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub expiry_lc_config_updated_at: Option, #[serde( rename = "bucketTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub created_at: Option, #[serde( rename = "bucketDeletedTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub deleted_at: Option, #[serde( rename = "corsTimestamp", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub cors_config_updated_at: Option, #[serde(default, skip_serializing_if = "String::is_empty")] pub location: String, /// Whether every `site-repl-*` rule on the reporting site resolves to a usable bucket /// target. A rule without one silently drops every object, and the rule set alone cannot /// reveal it — the config is well-formed, the endpoint behind it is not reachable. /// /// `None` means the peer predates this field: treat it as "unknown", never as a fault. #[serde(rename = "replicationTargetsOnline", default, skip_serializing_if = "Option::is_none")] pub replication_targets_online: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct OpenIDProviderSettings { #[serde(rename = "ClaimName", default, skip_serializing_if = "String::is_empty")] pub claim_name: String, #[serde(rename = "ClaimUserinfoEnabled", default)] pub claim_userinfo_enabled: bool, #[serde(rename = "RolePolicy", default, skip_serializing_if = "String::is_empty")] pub role_policy: String, #[serde(rename = "ClientID", default, skip_serializing_if = "String::is_empty")] pub client_id: String, #[serde(rename = "HashedClientSecret", default, skip_serializing_if = "String::is_empty")] pub hashed_client_secret: String, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct OpenIDSettings { #[serde(rename = "Enabled", default)] pub enabled: bool, #[serde(rename = "Region", default, skip_serializing_if = "String::is_empty")] pub region: String, #[serde(rename = "Roles", default, skip_serializing_if = "BTreeMap::is_empty")] pub roles: BTreeMap, #[serde(rename = "ClaimProvider", default)] pub claim_provider: OpenIDProviderSettings, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct LDAPSettings { #[serde(rename = "IsLDAPEnabled", default)] pub is_ldap_enabled: bool, #[serde(rename = "LDAPUserDNSearchBase", default, skip_serializing_if = "String::is_empty")] pub ldap_user_dn_search_base: String, #[serde(rename = "LDAPUserDNSearchFilter", default, skip_serializing_if = "String::is_empty")] pub ldap_user_dn_search_filter: String, #[serde(rename = "LDAPGroupSearchBase", default, skip_serializing_if = "String::is_empty")] pub ldap_group_search_base: String, #[serde(rename = "LDAPGroupSearchFilter", default, skip_serializing_if = "String::is_empty")] pub ldap_group_search_filter: String, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct LDAPProviderSettings { #[serde(rename = "UserDNSearchBase", default, skip_serializing_if = "String::is_empty")] pub user_dn_search_base: String, #[serde(rename = "UserDNSearchFilter", default, skip_serializing_if = "String::is_empty")] pub user_dn_search_filter: String, #[serde(rename = "GroupSearchBase", default, skip_serializing_if = "String::is_empty")] pub group_search_base: String, #[serde(rename = "GroupSearchFilter", default, skip_serializing_if = "String::is_empty")] pub group_search_filter: String, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct LDAPConfigSettings { #[serde(rename = "Enabled", default)] pub enabled: bool, #[serde(rename = "Configs", default, skip_serializing_if = "BTreeMap::is_empty")] pub configs: BTreeMap, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct IDPSettings { #[serde(rename = "LDAP", default)] pub ldap: LDAPSettings, #[serde(rename = "LDAPConfigs", default)] pub ldap_configs: LDAPConfigSettings, #[serde(rename = "OpenID", default)] pub open_id: OpenIDSettings, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRIAMPolicy { #[serde(default, skip_serializing_if = "Option::is_none")] pub policy: Option, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ILMExpiryRule { #[serde(rename = "ilm-rule", default, skip_serializing_if = "String::is_empty")] pub ilm_rule: String, #[serde(default)] pub bucket: String, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRStateInfo { #[serde(default, skip_serializing_if = "String::is_empty")] pub name: String, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub peers: BTreeMap, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Serialize, Deserialize, Default)] pub struct SRInfo { #[serde(default)] pub enabled: bool, #[serde(default, skip_serializing_if = "String::is_empty")] pub name: String, #[serde(rename = "deploymentID", default, skip_serializing_if = "String::is_empty")] pub deployment_id: String, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub buckets: BTreeMap, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub policies: BTreeMap, #[serde(rename = "userPolicies", default, skip_serializing_if = "BTreeMap::is_empty")] pub user_policies: BTreeMap, #[serde(rename = "userInfoMap", default, skip_serializing_if = "BTreeMap::is_empty")] pub user_info_map: BTreeMap, #[serde(rename = "groupDescMap", default, skip_serializing_if = "BTreeMap::is_empty")] pub group_desc_map: BTreeMap, #[serde(rename = "groupPolicies", default, skip_serializing_if = "BTreeMap::is_empty")] pub group_policies: BTreeMap, #[serde(rename = "replicationCfg", default, skip_serializing_if = "BTreeMap::is_empty")] pub replication_cfg: BTreeMap, #[serde(rename = "ilmExpiryRules", default, skip_serializing_if = "BTreeMap::is_empty")] pub ilm_expiry_rules: BTreeMap, #[serde(default)] pub state: SRStateInfo, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRPolicyStatsSummary { #[serde(rename = "DeploymentID", default, skip_serializing_if = "String::is_empty")] pub deployment_id: String, #[serde(rename = "PolicyMismatch", default)] pub policy_mismatch: bool, #[serde(rename = "HasPolicy", default)] pub has_policy: bool, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRUserStatsSummary { #[serde(rename = "DeploymentID", default, skip_serializing_if = "String::is_empty")] pub deployment_id: String, #[serde(rename = "PolicyMismatch", default)] pub policy_mismatch: bool, #[serde(rename = "UserInfoMismatch", default)] pub user_info_mismatch: bool, #[serde(rename = "HasUser", default)] pub has_user: bool, #[serde(rename = "HasPolicyMapping", default)] pub has_policy_mapping: bool, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRGroupStatsSummary { #[serde(rename = "DeploymentID", default, skip_serializing_if = "String::is_empty")] pub deployment_id: String, #[serde(rename = "PolicyMismatch", default)] pub policy_mismatch: bool, #[serde(rename = "HasGroup", default)] pub has_group: bool, #[serde(rename = "GroupDescMismatch", default)] pub group_desc_mismatch: bool, #[serde(rename = "HasPolicyMapping", default)] pub has_policy_mapping: bool, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRBucketStatsSummary { #[serde(rename = "DeploymentID", default, skip_serializing_if = "String::is_empty")] pub deployment_id: String, #[serde(rename = "HasBucket", default)] pub has_bucket: bool, #[serde(rename = "BucketMarkedDeleted", default)] pub bucket_marked_deleted: bool, #[serde(rename = "TagMismatch", default)] pub tag_mismatch: bool, #[serde(rename = "VersioningConfigMismatch", default)] pub versioning_config_mismatch: bool, #[serde(rename = "OLockConfigMismatch", default)] pub object_lock_config_mismatch: bool, #[serde(rename = "PolicyMismatch", default)] pub policy_mismatch: bool, #[serde(rename = "SSEConfigMismatch", default)] pub sse_config_mismatch: bool, #[serde(rename = "ReplicationCfgMismatch", default)] pub replication_cfg_mismatch: bool, #[serde(rename = "QuotaCfgMismatch", default)] pub quota_cfg_mismatch: bool, #[serde(rename = "CorsCfgMismatch", default)] pub cors_cfg_mismatch: bool, #[serde(rename = "HasTagsSet", default)] pub has_tags_set: bool, #[serde(rename = "HasOLockConfigSet", default)] pub has_object_lock_config_set: bool, #[serde(rename = "HasPolicySet", default)] pub has_policy_set: bool, #[serde(rename = "HasSSECfgSet", default)] pub has_sse_cfg_set: bool, #[serde(rename = "HasReplicationCfg", default)] pub has_replication_cfg: bool, #[serde(rename = "HasQuotaCfgSet", default)] pub has_quota_cfg_set: bool, #[serde(rename = "HasCorsCfgSet", default)] pub has_cors_cfg_set: bool, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRILMExpiryStatsSummary { #[serde(rename = "DeploymentID", default, skip_serializing_if = "String::is_empty")] pub deployment_id: String, #[serde(rename = "ILMExpiryRuleMismatch", default)] pub ilm_expiry_rule_mismatch: bool, #[serde(rename = "HasILMExpiryRules", default)] pub has_ilm_expiry_rules: bool, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRSiteSummary { #[serde(rename = "ReplicatedBuckets", default)] pub replicated_buckets: usize, #[serde(rename = "ReplicatedTags", default)] pub replicated_tags: usize, #[serde(rename = "ReplicatedBucketPolicies", default)] pub replicated_bucket_policies: usize, #[serde(rename = "ReplicatedIAMPolicies", default)] pub replicated_iam_policies: usize, #[serde(rename = "ReplicatedUsers", default)] pub replicated_users: usize, #[serde(rename = "ReplicatedGroups", default)] pub replicated_groups: usize, #[serde(rename = "ReplicatedLockConfig", default)] pub replicated_lock_config: usize, #[serde(rename = "ReplicatedSSEConfig", default)] pub replicated_sse_config: usize, #[serde(rename = "ReplicatedVersioningConfig", default)] pub replicated_versioning_config: usize, #[serde(rename = "ReplicatedQuotaConfig", default)] pub replicated_quota_config: usize, #[serde(rename = "ReplicatedUserPolicyMappings", default)] pub replicated_user_policy_mappings: usize, #[serde(rename = "ReplicatedGroupPolicyMappings", default)] pub replicated_group_policy_mappings: usize, #[serde(rename = "ReplicatedILMExpiryRules", default)] pub replicated_ilm_expiry_rules: usize, #[serde(rename = "ReplicatedCorsConfig", default)] pub replicated_cors_config: usize, #[serde(rename = "TotalBucketsCount", default)] pub total_buckets_count: usize, #[serde(rename = "TotalTagsCount", default)] pub total_tags_count: usize, #[serde(rename = "TotalBucketPoliciesCount", default)] pub total_bucket_policies_count: usize, #[serde(rename = "TotalIAMPoliciesCount", default)] pub total_iam_policies_count: usize, #[serde(rename = "TotalLockConfigCount", default)] pub total_lock_config_count: usize, #[serde(rename = "TotalSSEConfigCount", default)] pub total_sse_config_count: usize, #[serde(rename = "TotalVersioningConfigCount", default)] pub total_versioning_config_count: usize, #[serde(rename = "TotalQuotaConfigCount", default)] pub total_quota_config_count: usize, #[serde(rename = "TotalUsersCount", default)] pub total_users_count: usize, #[serde(rename = "TotalGroupsCount", default)] pub total_groups_count: usize, #[serde(rename = "TotalUserPolicyMappingCount", default)] pub total_user_policy_mapping_count: usize, #[serde(rename = "TotalGroupPolicyMappingCount", default)] pub total_group_policy_mapping_count: usize, #[serde(rename = "TotalILMExpiryRulesCount", default)] pub total_ilm_expiry_rules_count: usize, #[serde(rename = "TotalCorsConfigCount", default)] pub total_cors_config_count: usize, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct WorkerStat { #[serde(rename = "curr", default)] pub curr: i32, #[serde(rename = "avg", default)] pub avg: f64, #[serde(rename = "max", default)] pub max: i32, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct QStat { #[serde(default)] pub count: f64, #[serde(default)] pub bytes: f64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct InQueueMetric { #[serde(default)] pub curr: QStat, #[serde(default)] pub avg: QStat, #[serde(default)] pub max: QStat, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct InProgressMetric { #[serde(default)] pub curr: QStat, #[serde(default)] pub avg: QStat, #[serde(default)] pub max: QStat, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Counter { #[serde(rename = "last1hr", default)] pub last_1hr: u64, #[serde(rename = "last1m", default)] pub last_1m: u64, #[serde(default)] pub total: u64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ReplicationWindowedStats { #[serde(default)] pub curr: u64, #[serde(rename = "avgRate", default)] pub avg_rate: f64, #[serde(rename = "peakRate", default)] pub peak_rate: f64, #[serde(default)] pub total: u64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ReplProxyMetric { #[serde(rename = "putTaggingProxyTotal", default)] pub put_tag_total: u64, #[serde(rename = "getTaggingProxyTotal", default)] pub get_tag_total: u64, #[serde(rename = "removeTaggingProxyTotal", default)] pub remove_tag_total: u64, #[serde(rename = "getProxyTotal", default)] pub get_total: u64, #[serde(rename = "headProxyTotal", default)] pub head_total: u64, #[serde(rename = "putTaggingProxyFailed", default)] pub put_tag_failed_total: u64, #[serde(rename = "getTaggingProxyFailed", default)] pub get_tag_failed_total: u64, #[serde(rename = "removeTaggingProxyFailed", default)] pub remove_tag_failed_total: u64, #[serde(rename = "getProxyFailed", default)] pub get_failed_total: u64, #[serde(rename = "headProxyFailed", default)] pub head_failed_total: u64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct LatencyStat { #[serde(rename = "curr", default)] pub curr_ns: i64, #[serde(rename = "avg", default)] pub average_ns: i64, #[serde(rename = "max", default)] pub max_ns: i64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct RStat { #[serde(default)] pub count: f64, #[serde(default)] pub bytes: i64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct TimedErrStats { #[serde(rename = "lastMinute", default)] pub last_minute: RStat, #[serde(rename = "lastHour", default)] pub last_hour: RStat, #[serde(rename = "totals", default)] pub totals: RStat, #[serde(rename = "errCounts", default, skip_serializing_if = "BTreeMap::is_empty")] pub err_counts: BTreeMap, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct StatRecorder { #[serde(default)] pub total: i64, #[serde(default)] pub avg: i64, #[serde(default)] pub max: i64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct DowntimeInfo { #[serde(default)] pub duration: StatRecorder, #[serde(default)] pub count: StatRecorder, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRMetric { #[serde(rename = "deploymentID", default, skip_serializing_if = "String::is_empty")] pub deployment_id: String, #[serde(default, skip_serializing_if = "String::is_empty")] pub endpoint: String, #[serde(rename = "totalDowntime", default)] pub total_downtime_ns: i64, #[serde( rename = "lastOnline", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub last_online: Option, #[serde(rename = "isOnline", default)] pub online: bool, #[serde(default)] pub latency: LatencyStat, #[serde(rename = "replicatedSize", default)] pub replicated_size: i64, #[serde(rename = "replicatedCount", default)] pub replicated_count: i64, #[serde(default)] pub failed: TimedErrStats, #[serde(rename = "transferSummary", default, skip_serializing_if = "HashMap::is_empty")] pub transfer_summary: HashMap, #[serde(rename = "mrfStats", default, skip_serializing_if = "HashMap::is_empty")] pub mrf_stats: HashMap, #[serde(rename = "downtimeInfo", default)] pub downtime_info: DowntimeInfo, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRMetricsSummary { #[serde(rename = "activeWorkers", default)] pub active_workers: WorkerStat, #[serde(rename = "replicaSize", default)] pub replica_size: i64, #[serde(rename = "replicaCount", default)] pub replica_count: i64, #[serde(default)] pub queued: InQueueMetric, #[serde(rename = "inProgress", default)] pub in_progress: InProgressMetric, #[serde(default)] pub proxied: ReplProxyMetric, #[serde(rename = "replMetrics", default, skip_serializing_if = "BTreeMap::is_empty")] pub metrics: BTreeMap, #[serde(default)] pub uptime: i64, #[serde(default)] pub retries: Counter, #[serde(default)] pub errors: Counter, #[serde(default)] pub replicated: ReplicationWindowedStats, #[serde(default)] pub received: ReplicationWindowedStats, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRPeerError { #[serde(default, skip_serializing_if = "String::is_empty")] pub name: String, #[serde(default, skip_serializing_if = "String::is_empty")] pub endpoint: String, #[serde(default, skip_serializing_if = "String::is_empty")] pub error: String, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRPendingOperation { #[serde(default, skip_serializing_if = "String::is_empty")] pub operation: String, #[serde(default, skip_serializing_if = "String::is_empty")] pub id: String, #[serde(rename = "pendingPeers", default, skip_serializing_if = "Vec::is_empty")] pub pending_peers: Vec, #[serde(rename = "ackedPeers", default, skip_serializing_if = "Vec::is_empty")] pub acked_peers: Vec, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRRetryStats { #[serde(default)] pub pending: usize, #[serde(default)] pub failed: usize, #[serde(rename = "lastError", default, skip_serializing_if = "String::is_empty")] pub last_error: String, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRStatusInfo { #[serde(default)] pub enabled: bool, #[serde(rename = "MaxBuckets", default)] pub max_buckets: usize, #[serde(rename = "MaxUsers", default)] pub max_users: usize, #[serde(rename = "MaxGroups", default)] pub max_groups: usize, #[serde(rename = "MaxPolicies", default)] pub max_policies: usize, #[serde(rename = "MaxILMExpiryRules", default)] pub max_ilm_expiry_rules: usize, #[serde(rename = "Sites", default, skip_serializing_if = "BTreeMap::is_empty")] pub sites: BTreeMap, #[serde(rename = "StatsSummary", default, skip_serializing_if = "BTreeMap::is_empty")] pub stats_summary: BTreeMap, #[serde(rename = "BucketStats", default, skip_serializing_if = "BTreeMap::is_empty")] pub bucket_stats: BTreeMap>, #[serde(rename = "PolicyStats", default, skip_serializing_if = "BTreeMap::is_empty")] pub policy_stats: BTreeMap>, #[serde(rename = "UserStats", default, skip_serializing_if = "BTreeMap::is_empty")] pub user_stats: BTreeMap>, #[serde(rename = "GroupStats", default, skip_serializing_if = "BTreeMap::is_empty")] pub group_stats: BTreeMap>, #[serde(rename = "PeerStates", default, skip_serializing_if = "BTreeMap::is_empty")] pub peer_states: BTreeMap, #[serde(rename = "PeerErrors", default, skip_serializing_if = "BTreeMap::is_empty")] pub peer_errors: BTreeMap, #[serde(rename = "PendingOperation", default, skip_serializing_if = "Option::is_none")] pub pending_operation: Option, #[serde(rename = "RetryStats", default, skip_serializing_if = "Option::is_none")] pub retry_stats: Option, #[serde(rename = "Metrics", default)] pub metrics: SRMetricsSummary, #[serde(rename = "ILMExpiryStats", default, skip_serializing_if = "BTreeMap::is_empty")] pub ilm_expiry_stats: BTreeMap>, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ReplicateEditStatus { #[serde(default)] pub success: bool, #[serde(default)] pub status: String, #[serde(rename = "errorDetail", skip_serializing_if = "String::is_empty", default)] pub err_detail: String, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ReplicateRemoveStatus { #[serde(default)] pub status: String, #[serde(rename = "errorDetail", skip_serializing_if = "String::is_empty", default)] pub err_detail: String, #[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")] pub api_version: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRRemoveReq { #[serde(rename = "requestingDepID", default, skip_serializing_if = "String::is_empty")] pub requesting_dep_id: String, #[serde( rename = "sites", default, deserialize_with = "deserialize_vec_null_default", skip_serializing_if = "Vec::is_empty" )] pub site_names: Vec, #[serde(rename = "all", default)] pub remove_all: bool, } fn deserialize_vec_null_default<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { Ok(Option::>::deserialize(deserializer)?.unwrap_or_default()) } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRStateEditReq { #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub peers: BTreeMap, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ResyncBucketStatus { #[serde(default)] pub bucket: String, #[serde(rename = "targetArn", default, skip_serializing_if = "String::is_empty")] pub target_arn: String, #[serde(default)] pub status: String, #[serde(rename = "errorDetail", skip_serializing_if = "String::is_empty", default)] pub err_detail: String, #[serde( rename = "createdAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub created_at: Option, #[serde( rename = "startedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub started_at: Option, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, #[serde( rename = "completedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub completed_at: Option, #[serde(default, skip_serializing_if = "is_zero_u64")] pub generation: u64, #[serde(rename = "replicatedObjects", default, skip_serializing_if = "is_zero_u64")] pub replicated_objects: u64, #[serde(rename = "replicatedBytes", default, skip_serializing_if = "is_zero_u64")] pub replicated_bytes: u64, #[serde(rename = "failedObjects", default, skip_serializing_if = "is_zero_u64")] pub failed_objects: u64, #[serde(rename = "failedBytes", default, skip_serializing_if = "is_zero_u64")] pub failed_bytes: u64, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SRResyncOpStatus { #[serde(rename = "op", default)] pub op_type: String, #[serde(rename = "id", default)] pub resync_id: String, #[serde(default)] pub status: String, #[serde(default, skip_serializing_if = "String::is_empty")] pub state: String, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub buckets: Vec, #[serde(rename = "errorDetail", skip_serializing_if = "String::is_empty", default)] pub err_detail: String, #[serde( rename = "createdAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub created_at: Option, #[serde( rename = "startedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub started_at: Option, #[serde( rename = "updatedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub updated_at: Option, #[serde( rename = "completedAt", default, with = "time::serde::rfc3339::option", skip_serializing_if = "Option::is_none" )] pub completed_at: Option, #[serde(default, skip_serializing_if = "is_zero_u64")] pub generation: u64, #[serde(rename = "totalBuckets", default, skip_serializing_if = "is_zero_u64")] pub total_buckets: u64, #[serde(rename = "pendingBuckets", default, skip_serializing_if = "is_zero_u64")] pub pending_buckets: u64, #[serde(rename = "runningBuckets", default, skip_serializing_if = "is_zero_u64")] pub running_buckets: u64, #[serde(rename = "completedBuckets", default, skip_serializing_if = "is_zero_u64")] pub completed_buckets: u64, #[serde(rename = "failedBuckets", default, skip_serializing_if = "is_zero_u64")] pub failed_buckets: u64, #[serde(rename = "canceledBuckets", default, skip_serializing_if = "is_zero_u64")] pub canceled_buckets: u64, #[serde(rename = "replicatedObjects", default, skip_serializing_if = "is_zero_u64")] pub replicated_objects: u64, #[serde(rename = "replicatedBytes", default, skip_serializing_if = "is_zero_u64")] pub replicated_bytes: u64, #[serde(rename = "failedObjects", default, skip_serializing_if = "is_zero_u64")] pub failed_objects: u64, #[serde(rename = "failedBytes", default, skip_serializing_if = "is_zero_u64")] pub failed_bytes: u64, #[serde(default, skip_serializing_if = "is_false")] pub truncated: bool, #[serde(rename = "nextContinuationToken", default, skip_serializing_if = "String::is_empty")] pub next_continuation_token: String, } fn is_zero_u64(value: &u64) -> bool { *value == 0 } fn is_false(value: &bool) -> bool { !*value } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SiteNetPerfNodeResult { #[serde(default, skip_serializing_if = "String::is_empty")] pub endpoint: String, #[serde(default)] pub tx: u64, #[serde(rename = "txTotalDuration", default)] pub tx_total_duration_ns: i64, #[serde(default)] pub rx: u64, #[serde(rename = "rxTotalDuration", default)] pub rx_total_duration_ns: i64, #[serde(rename = "totalConn", default)] pub total_conn: u64, #[serde(default, skip_serializing_if = "String::is_empty")] pub error: String, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SiteNetPerfResult { #[serde(rename = "nodeResults", default, skip_serializing_if = "Vec::is_empty")] pub node_results: Vec, } #[cfg(test)] mod tests { use super::{PeerInfo, PeerSite, SRResyncOpStatus}; use serde_json::{Value, json}; const TEST_CA_CERT: &str = "-----BEGIN CERTIFICATE-----\ntest-ca\n-----END CERTIFICATE-----"; #[test] fn peer_tls_fields_default_when_missing_from_legacy_json() { let site: PeerSite = serde_json::from_value(json!({ "name": "site-a", "endpoints": "https://site-a.example.com" })) .expect("legacy PeerSite JSON should deserialize"); let peer: PeerInfo = serde_json::from_value(json!({ "endpoint": "https://site-a.example.com", "name": "site-a", "deploymentID": "deployment-a" })) .expect("legacy PeerInfo JSON should deserialize"); assert!(!site.skip_tls_verify); assert_eq!(site.ca_cert_pem, ""); assert!(!peer.skip_tls_verify); assert_eq!(peer.ca_cert_pem, ""); } #[test] fn peer_tls_fields_round_trip_with_exact_json_names() { let site_json = json!({ "name": "site-a", "endpoints": "https://site-a.example.com", "accessKey": "access-key", "secretKey": "secret-key", "skipTlsVerify": true, "caCertPem": TEST_CA_CERT }); let peer_json = json!({ "endpoint": "https://site-a.example.com", "name": "site-a", "deploymentID": "deployment-a", "sync": "unknown", "defaultbandwidth": { "bandwidthLimitPerBucket": 0, "set": false }, "replicate-ilm-expiry": false, "objectNamingMode": "path", "skipTlsVerify": true, "caCertPem": TEST_CA_CERT }); let site: PeerSite = serde_json::from_value(site_json.clone()).expect("PeerSite JSON should deserialize"); let peer: PeerInfo = serde_json::from_value(peer_json.clone()).expect("PeerInfo JSON should deserialize"); assert_eq!(serde_json::to_value(site).expect("PeerSite should serialize"), site_json); assert_eq!(serde_json::to_value(peer).expect("PeerInfo should serialize"), peer_json); } #[test] fn peer_tls_false_and_empty_ca_are_still_serialized() { let site = serde_json::to_value(PeerSite::default()).expect("PeerSite should serialize"); let peer = serde_json::to_value(PeerInfo::default()).expect("PeerInfo should serialize"); for value in [site, peer] { let object = value.as_object().expect("peer JSON should be an object"); assert_eq!(object.get("skipTlsVerify"), Some(&Value::Bool(false))); assert_eq!(object.get("caCertPem"), Some(&Value::String(String::new()))); } } #[test] fn peer_debug_output_redacts_secrets_and_ca_contents() { let site = PeerSite { name: "site-a".to_owned(), endpoint: "https://site-a.example.com".to_owned(), access_key: "sensitive-access-key".to_owned(), secret_key: "sensitive-secret-key".to_owned(), skip_tls_verify: true, ca_cert_pem: TEST_CA_CERT.to_owned(), }; let peer = PeerInfo { endpoint: "https://site-a.example.com".to_owned(), name: "site-a".to_owned(), deployment_id: "deployment-a".to_owned(), skip_tls_verify: false, ca_cert_pem: TEST_CA_CERT.to_owned(), ..PeerInfo::default() }; let site_debug = format!("{site:?}"); let peer_debug = format!("{peer:?}"); assert!(!site_debug.contains("BEGIN CERTIFICATE")); assert!(!site_debug.contains("sensitive-access-key")); assert!(!site_debug.contains("sensitive-secret-key")); assert!(site_debug.contains("skip_tls_verify: true")); assert!(site_debug.contains("has_custom_ca: true")); assert!(!peer_debug.contains("BEGIN CERTIFICATE")); assert!(peer_debug.contains("skip_tls_verify: false")); assert!(peer_debug.contains("has_custom_ca: true")); } #[test] fn resync_status_legacy_json_defaults_new_lifecycle_fields() { let legacy_json = json!({ "op": "start", "id": "resync-1", "status": "success", "buckets": [{ "bucket": "photos", "status": "success" }] }); let status: SRResyncOpStatus = serde_json::from_value(legacy_json.clone()).expect("legacy resync status should deserialize"); assert_eq!(status.generation, 0); assert!(status.state.is_empty()); assert!(status.created_at.is_none()); assert_eq!(status.total_buckets, 0); assert_eq!(status.replicated_objects, 0); assert!(!status.truncated); assert!(status.next_continuation_token.is_empty()); assert!(status.buckets[0].created_at.is_none()); assert!(status.buckets[0].target_arn.is_empty()); assert_eq!(status.buckets[0].generation, 0); assert_eq!(status.buckets[0].replicated_bytes, 0); assert_eq!(serde_json::to_value(status).expect("legacy resync status should serialize"), legacy_json); } #[test] fn resync_status_lifecycle_fields_round_trip_with_exact_json_names() { let status_json = json!({ "op": "status", "id": "resync-2", "status": "success", "state": "running", "createdAt": "2026-07-22T01:00:00Z", "startedAt": "2026-07-22T01:00:01Z", "updatedAt": "2026-07-22T01:01:00Z", "completedAt": "2026-07-22T01:02:00Z", "generation": 7, "totalBuckets": 6, "pendingBuckets": 1, "runningBuckets": 1, "completedBuckets": 1, "failedBuckets": 1, "canceledBuckets": 2, "replicatedObjects": 12, "replicatedBytes": 4096, "failedObjects": 3, "failedBytes": 512, "truncated": true, "nextContinuationToken": "bucket-page-2", "buckets": [{ "bucket": "photos", "targetArn": "arn:rustfs:replication::peer-a:photos", "status": "failed", "errorDetail": "target unavailable", "createdAt": "2026-07-22T01:00:00Z", "startedAt": "2026-07-22T01:00:01Z", "updatedAt": "2026-07-22T01:01:00Z", "completedAt": "2026-07-22T01:02:00Z", "generation": 7, "replicatedObjects": 12, "replicatedBytes": 4096, "failedObjects": 3, "failedBytes": 512 }] }); let status: SRResyncOpStatus = serde_json::from_value(status_json.clone()).expect("expanded resync status should deserialize"); assert_eq!(status.generation, 7); assert_eq!(status.state, "running"); assert_eq!(status.total_buckets, 6); assert_eq!(status.completed_buckets, 1); assert_eq!(status.replicated_bytes, 4096); assert!(status.truncated); assert_eq!(status.next_continuation_token, "bucket-page-2"); assert_eq!(status.buckets[0].generation, 7); assert_eq!(status.buckets[0].target_arn, "arn:rustfs:replication::peer-a:photos"); assert_eq!(status.buckets[0].failed_objects, 3); assert!(status.buckets[0].completed_at.is_some()); assert_eq!( serde_json::to_value(status).expect("expanded resync status should serialize"), status_json ); } }