From 7187dcaabf41254c45e802e4900f64b374532973 Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Mon, 20 Oct 2025 17:42:16 -0400 Subject: [PATCH 1/2] Add missing fields to Transfer Utility request objects --- .../9d07dc1e-d82d-4f94-8700-c7b57f87205d.json | 11 + .../S3/Custom/Model/HeadersCollection.cs | 15 + .../S3/Custom/Transfer/BaseDownloadRequest.cs | 116 ++- .../S3/Custom/Transfer/BaseUploadRequest.cs | 505 ++++++++++++++ .../Internal/AbortMultipartUploadsCommand.cs | 4 +- .../Custom/Transfer/Internal/BaseCommand.cs | 17 +- .../Internal/DownloadDirectoryCommand.cs | 10 +- .../Internal/MultipartUploadCommand.cs | 27 +- .../Transfer/Internal/OpenStreamCommand.cs | 2 +- .../Transfer/Internal/SimpleUploadCommand.cs | 9 +- .../Internal/UploadDirectoryCommand.cs | 8 +- .../_async/MultipartUploadCommand.async.cs | 20 +- ...TransferUtilityDownloadDirectoryRequest.cs | 107 +++ .../TransferUtilityUploadDirectoryRequest.cs | 280 -------- .../Transfer/TransferUtilityUploadRequest.cs | 335 --------- .../EmbeddedResource/property-aliases.json | 121 +++- .../UnitTests/Custom/ResponseMapperTests.cs | 658 ++++++++++++++++-- 17 files changed, 1548 insertions(+), 697 deletions(-) create mode 100644 generator/.DevConfigs/9d07dc1e-d82d-4f94-8700-c7b57f87205d.json diff --git a/generator/.DevConfigs/9d07dc1e-d82d-4f94-8700-c7b57f87205d.json b/generator/.DevConfigs/9d07dc1e-d82d-4f94-8700-c7b57f87205d.json new file mode 100644 index 000000000000..52e892ab03ab --- /dev/null +++ b/generator/.DevConfigs/9d07dc1e-d82d-4f94-8700-c7b57f87205d.json @@ -0,0 +1,11 @@ +{ + "services": [ + { + "serviceName": "S3", + "type": "patch", + "changeLogMessages": [ + "Add missing fields to Transfer Utility request objects" + ] + } + ] +} \ No newline at end of file diff --git a/sdk/src/Services/S3/Custom/Model/HeadersCollection.cs b/sdk/src/Services/S3/Custom/Model/HeadersCollection.cs index ed2945257f9b..32e712cf5018 100644 --- a/sdk/src/Services/S3/Custom/Model/HeadersCollection.cs +++ b/sdk/src/Services/S3/Custom/Model/HeadersCollection.cs @@ -152,6 +152,21 @@ internal bool IsSetContentType() return !string.IsNullOrEmpty(this.ContentType); } + /// + /// The language that the content is in. For more information, + /// see https://www.rfc-editor.org/rfc/rfc9110.html#name-content-language. + /// + public string ContentLanguage + { + get { return this["Content-Language"]; } + set { this["Content-Language"] = value; } + } + + internal bool IsSetContentLanguage() + { + return !string.IsNullOrEmpty(this.ContentLanguage); + } + /// /// /// The date and time at which the object is no longer cacheable. For more information, diff --git a/sdk/src/Services/S3/Custom/Transfer/BaseDownloadRequest.cs b/sdk/src/Services/S3/Custom/Transfer/BaseDownloadRequest.cs index d29a3747f24a..84bc08c29225 100644 --- a/sdk/src/Services/S3/Custom/Transfer/BaseDownloadRequest.cs +++ b/sdk/src/Services/S3/Custom/Transfer/BaseDownloadRequest.cs @@ -24,6 +24,7 @@ using System.Text; using Amazon.Runtime.Internal; +using Amazon.S3.Model; namespace Amazon.S3.Transfer { @@ -45,6 +46,11 @@ public abstract class BaseDownloadRequest private RequestPayer requestPayer; + private string expectedBucketOwner; + private string ifMatch; + private string ifNoneMatch; + private ResponseHeaderOverrides responseHeaders; + /// /// Gets or sets the name of the bucket. /// @@ -66,7 +72,7 @@ public string BucketName /// internal bool IsSetBucketName() { - return !System.String.IsNullOrEmpty(this.bucketName); + return !String.IsNullOrEmpty(this.bucketName); } @@ -91,7 +97,7 @@ public string Key /// internal bool IsSetKey() { - return !System.String.IsNullOrEmpty(this.key); + return !String.IsNullOrEmpty(this.key); } /// @@ -112,7 +118,7 @@ public string VersionId /// true if VersionId property is set. internal bool IsSetVersionId() { - return !System.String.IsNullOrEmpty(this.versionId); + return !String.IsNullOrEmpty(this.versionId); } /// @@ -220,5 +226,109 @@ public RequestPayer RequestPayer get { return this.requestPayer; } set { this.requestPayer = value; } } + + /// + /// Gets and sets the property ExpectedBucketOwner. + /// + /// The account ID of the expected bucket owner. If the account ID that you provide does + /// not match the actual owner of the bucket, the request fails with the HTTP status code + /// 403 Forbidden (access denied). + /// + /// + public string ExpectedBucketOwner + { + get { return this.expectedBucketOwner; } + set { this.expectedBucketOwner = value; } + } + + /// + /// Checks to see if ExpectedBucketOwner is set. + /// + /// true, if ExpectedBucketOwner property is set. + internal bool IsSetExpectedBucketOwner() + { + return !String.IsNullOrEmpty(this.expectedBucketOwner); + } + + /// + /// Gets and sets the property IfMatch. + /// + /// Return the object only if its entity tag (ETag) is the same as the one specified in this header; + /// otherwise, return a 412 Precondition Failed error. + /// + /// + /// If both of the If-Match and If-Unmodified-Since headers are present in the request as follows: + /// If-Match condition evaluates to true, and; If-Unmodified-Since condition evaluates to false; + /// then, S3 returns 200 OK and the data requested. + /// + /// + /// For more information about conditional requests, see RFC 7232. + /// + /// The property is equivalent to the . + /// + public string IfMatch + { + get { return this.ifMatch; } + set { this.ifMatch = value; } + } + + /// + /// Checks to see if IfMatch is set. + /// + /// true, if IfMatch property is set. + internal bool IsSetIfMatch() + { + return !String.IsNullOrEmpty(this.ifMatch); + } + + /// + /// Gets and sets the property IfNoneMatch. + /// + /// Return the object only if its entity tag (ETag) is different from the one specified in this header; + /// otherwise, return a 304 Not Modified error. + /// + /// + /// If both of the If-None-Match and If-Modified-Since headers are present in the request as follows: + /// If-None-Match condition evaluates to false, and; If-Modified-Since condition evaluates to true; + /// then, S3 returns 304 Not Modified HTTP status code. + /// + /// + /// For more information about conditional requests, see RFC 7232. + /// + /// The property is equivalent to the . + /// + public string IfNoneMatch + { + get { return this.ifNoneMatch; } + set { this.ifNoneMatch = value; } + } + + /// + /// Checks to see if IfNoneMatch is set. + /// + /// true, if IfNoneMatch property is set. + internal bool IsSetIfNoneMatch() + { + return !String.IsNullOrEmpty(this.ifNoneMatch); + } + + /// + /// A set of response headers that should be returned with the object. + /// + public ResponseHeaderOverrides ResponseHeaderOverrides + { + get + { + if (this.responseHeaders == null) + { + this.responseHeaders = new ResponseHeaderOverrides(); + } + return this.responseHeaders; + } + set + { + this.responseHeaders = value; + } + } } } \ No newline at end of file diff --git a/sdk/src/Services/S3/Custom/Transfer/BaseUploadRequest.cs b/sdk/src/Services/S3/Custom/Transfer/BaseUploadRequest.cs index 8a1cec4a957c..d087be435f4f 100644 --- a/sdk/src/Services/S3/Custom/Transfer/BaseUploadRequest.cs +++ b/sdk/src/Services/S3/Custom/Transfer/BaseUploadRequest.cs @@ -21,6 +21,9 @@ * */ using System; +using System.Collections.Generic; +using Amazon.Runtime.Internal; +using Amazon.S3.Model; namespace Amazon.S3.Transfer { @@ -29,7 +32,28 @@ namespace Amazon.S3.Transfer /// public abstract class BaseUploadRequest { + private string bucketName; private RequestPayer requestPayer; + private ServerSideEncryptionMethod encryption; + private ServerSideEncryptionCustomerMethod serverSideCustomerEncryption; + private string serverSideEncryptionCustomerProvidedKey; + private string serverSideEncryptionCustomerProvidedKeyMD5; + private string serverSideEncryptionKeyManagementServiceKeyId; + private ChecksumAlgorithm checksumAlgorithm; + private S3CannedACL cannedACL; + private S3StorageClass storageClass; + private MetadataCollection metadataCollection = new MetadataCollection(); + private List tagset; + private ObjectLockLegalHoldStatus objectLockLegalHoldStatus; + private ObjectLockMode objectLockMode; + private DateTime? objectLockRetainUntilDate; + private bool? disablePayloadSigning; + private bool? bucketKeyEnabled; + private string expectedBucketOwner; + private string sseKMSEncryptionContext; + private string websiteRedirectLocation; + private HeadersCollection headersCollection = new HeadersCollection(); + private List _grants = AWSConfigs.InitializeCollections ? new List() : null; /// /// Confirms that the requester knows that they will be charged for the request. @@ -40,5 +64,486 @@ public RequestPayer RequestPayer get { return this.requestPayer; } set { this.requestPayer = value; } } + + #region BucketName + + /// + /// Gets or sets the name of the bucket. + /// + /// + /// The name of the bucket. + /// + [AWSProperty(Required = true)] + public string BucketName + { + get { return this.bucketName; } + set { this.bucketName = value; } + } + + + /// + /// Checks if BucketName property is set. + /// + /// true if BucketName property is set. + internal bool IsSetBucketName() + { + return !String.IsNullOrEmpty(this.bucketName); + } + + #endregion + + #region ContentType + /// + /// Gets or sets the content type of the uploaded Amazon S3 object. + /// This is a convenience property for Headers.ContentType. + /// + /// The content type of the uploaded Amazon S3 object. + /// + /// + public string ContentType + { + get { return this.Headers.ContentType; } + set { this.Headers.ContentType = value; } + } + + + /// + /// Checks if ContentType property is set. + /// + /// true if ContentType property is set. + internal bool IsSetContentType() + { + return !String.IsNullOrEmpty(this.Headers.ContentType); + } + + #endregion + + #region ServerSideEncryption + + /// + /// Gets and sets the ServerSideEncryptionMethod property. + /// Specifies the encryption used on the server to + /// store the content. + /// + public ServerSideEncryptionMethod ServerSideEncryptionMethod + { + get { return this.encryption; } + set { this.encryption = value; } + } + + /// + /// The Server-side encryption algorithm to be used with the customer provided key. + /// + public ServerSideEncryptionCustomerMethod ServerSideEncryptionCustomerMethod + { + get { return this.serverSideCustomerEncryption; } + set { this.serverSideCustomerEncryption = value; } + } + + /// + /// The id of the AWS Key Management Service key that Amazon S3 should use to encrypt and decrypt the object. + /// If a key id is not specified, the default key will be used for encryption and decryption. + /// + [AWSProperty(Sensitive=true)] + public string ServerSideEncryptionKeyManagementServiceKeyId + { + get { return this.serverSideEncryptionKeyManagementServiceKeyId; } + set { this.serverSideEncryptionKeyManagementServiceKeyId = value; } + } + + /// + /// Checks if ServerSideEncryptionKeyManagementServiceKeyId property is set. + /// + /// true if ServerSideEncryptionKeyManagementServiceKeyId property is set. + internal bool IsSetServerSideEncryptionKeyManagementServiceKeyId() + { + return !String.IsNullOrEmpty(this.serverSideEncryptionKeyManagementServiceKeyId); + } + + /// + /// The Base64 encoded encryption key for Amazon S3 to use to encrypt the object + /// + /// Using the encryption key you provide as part of your request Amazon S3 manages both the encryption, as it writes + /// to disks, and decryption, when you access your objects. Therefore, you don't need to maintain any data encryption code. The only + /// thing you do is manage the encryption keys you provide. + /// + /// + /// When you retrieve an object, you must provide the same encryption key as part of your request. Amazon S3 first verifies + /// the encryption key you provided matches, and then decrypts the object before returning the object data to you. + /// + /// + /// Important: Amazon S3 does not store the encryption key you provide. + /// + /// + [AWSProperty(Sensitive=true)] + public string ServerSideEncryptionCustomerProvidedKey + { + get { return this.serverSideEncryptionCustomerProvidedKey; } + set { this.serverSideEncryptionCustomerProvidedKey = value; } + } + + /// + /// The MD5 of the customer encryption key specified in the ServerSideEncryptionCustomerProvidedKey property. The MD5 is + /// base 64 encoded. This field is optional, the SDK will calculate the MD5 if this is not set. + /// + public string ServerSideEncryptionCustomerProvidedKeyMD5 + { + get { return this.serverSideEncryptionCustomerProvidedKeyMD5; } + set { this.serverSideEncryptionCustomerProvidedKeyMD5 = value; } + } + + #endregion + + /// + /// Gets and sets the property ChecksumAlgorithm. + /// + /// Indicates the algorithm used to create the checksum for the object. + /// For more information, see + /// Checking object integrity in the Amazon S3 User Guide. + /// + /// + /// + /// If you provide an individual checksum, Amazon S3 will ignore any provided ChecksumAlgorithm. + /// + /// + public ChecksumAlgorithm ChecksumAlgorithm + { + get { return this.checksumAlgorithm; } + set { this.checksumAlgorithm = value; } + } + + #region CannedACL + + /// + /// Gets or sets the canned access control list (ACL) + /// for the uploaded object. + /// Please refer to + /// for + /// information on Amazon S3 canned ACLs. + /// + /// + /// The canned access control list (ACL) + /// for the uploaded object. + /// + public S3CannedACL CannedACL + { + get { return this.cannedACL; } + set { this.cannedACL = value; } + } + + /// + /// Checks if the CannedACL property is set. + /// + /// true if there is the CannedACL property is set. + internal bool IsSetCannedACL() + { + return (cannedACL != null); + } + + /// + /// Removes the canned access control list (ACL) + /// for the uploaded object. + /// + public void RemoveCannedACL() + { + this.cannedACL = null; + } + + #endregion + + #region StorageClass + + /// + /// Gets or sets the storage class for the uploaded Amazon S3 object. + /// Please refer to + /// for + /// information on S3 Storage Classes. + /// + /// + /// The storage class for the uploaded Amazon S3 object. + /// + public S3StorageClass StorageClass + { + get { return this.storageClass; } + set { this.storageClass = value; } + } + + #endregion + + /// + /// The collection of meta data for the request. + /// + public MetadataCollection Metadata + { + get + { + if (this.metadataCollection == null) + this.metadataCollection = new MetadataCollection(); + return this.metadataCollection; + } + internal set { this.metadataCollection = value; } + } + + /// + /// The tag-set for the object. + /// + public List TagSet + { + get { return this.tagset; } + set { this.tagset = value; } + } + + /// + /// Gets and sets the property ObjectLockLegalHoldStatus. + /// + /// Specifies whether a legal hold will be applied to this object. For more information + /// about S3 Object Lock, see Object + /// Lock. + /// + /// + public ObjectLockLegalHoldStatus ObjectLockLegalHoldStatus + { + get { return this.objectLockLegalHoldStatus; } + set { this.objectLockLegalHoldStatus = value; } + } + + /// + /// Gets and sets the property ObjectLockMode. + /// + /// The Object Lock mode that you want to apply to this object. + /// + /// + public ObjectLockMode ObjectLockMode + { + get { return this.objectLockMode; } + set { this.objectLockMode = value; } + } + + /// + /// Gets and sets the property ObjectLockRetainUntilDate. + /// + /// The date and time when you want this object's Object Lock to expire. + /// + /// + public DateTime? ObjectLockRetainUntilDate + { + get { return this.objectLockRetainUntilDate.GetValueOrDefault(); } + set { this.objectLockRetainUntilDate = value; } + } + + // Check to see if ObjectLockRetainUntilDate property is set + internal bool IsSetObjectLockRetainUntilDate() + { + return this.objectLockRetainUntilDate.HasValue; + } + + /// + /// WARNING: Setting DisablePayloadSigning to true disables the SigV4 payload signing + /// data integrity check on this request. + /// If using SigV4, the DisablePayloadSigning flag controls if the payload should be + /// signed on a request by request basis. By default this flag is null which will use the + /// default client behavior. The default client behavior is to sign the payload. When + /// DisablePayloadSigning is true, the request will be signed with an UNSIGNED-PAYLOAD value. + /// Setting DisablePayloadSigning to true requires that the request is sent over a HTTPS + /// connection. + /// Under certain circumstances, such as uploading to S3 while using MD5 hashing, it may + /// be desirable to use UNSIGNED-PAYLOAD to decrease signing CPU usage. This flag only applies + /// to Amazon S3 PutObject and UploadPart requests. + /// MD5Stream, SigV4 payload signing, and HTTPS each provide some data integrity + /// verification. If DisableMD5Stream is true and DisablePayloadSigning is true, then the + /// possibility of data corruption is completely dependent on HTTPS being the only remaining + /// source of data integrity verification. + /// + public bool? DisablePayloadSigning + { + get { return this.disablePayloadSigning; } + set { this.disablePayloadSigning = value; } + } + + /// + /// WARNING: Setting DisableDefaultChecksumValidation to true disables the default data + /// integrity check on upload requests. + /// When true, checksum verification will not be used in upload requests. This may increase upload + /// performance under high CPU loads. Setting DisableDefaultChecksumValidation sets the deprecated property + /// DisableMD5Stream to the same value. The default value is false. + /// Checksums, SigV4 payload signing, and HTTPS each provide some data integrity + /// verification. If DisableDefaultChecksumValidation is true and DisablePayloadSigning is true, then the + /// possibility of data corruption is completely dependent on HTTPS being the only remaining + /// source of data integrity verification. + /// + public bool? DisableDefaultChecksumValidation { get; set; } + + /// + /// Gets and sets the property BucketKeyEnabled. + /// + /// Specifies whether Amazon S3 should use an S3 Bucket Key for object encryption with + /// server-side encryption using Key Management Service (KMS) keys (SSE-KMS). + /// + /// + /// + /// General purpose buckets - Setting this header to true causes Amazon S3 to use an + /// S3 Bucket Key for object encryption with SSE-KMS. + /// Also, specifying this header with a PUT action doesn't affect bucket-level settings for S3 Bucket Key. + /// + /// + /// + /// Directory buckets - S3 Bucket Keys are always enabled for GET and PUT operations in a directory bucket and can't be disabled. + /// S3 Bucket Keys aren't supported, when you copy SSE-KMS encrypted objects from general purpose buckets to directory buckets, + /// from directory buckets to general purpose buckets, or between directory buckets, through + /// CopyObject, + /// UploadPartCopy, + /// the Copy operation in Batch Operations, + /// or the import jobs. + /// + /// In this case, Amazon S3 makes a call to KMS every time a copy request is made for a KMS-encrypted object. + /// + /// + public bool? BucketKeyEnabled + { + get { return this.bucketKeyEnabled; } + set { this.bucketKeyEnabled = value; } + } + + internal bool IsSetBucketKeyEnabled() + { + return bucketKeyEnabled.HasValue; + } + + /// + /// Gets and sets the property ExpectedBucketOwner. + /// + /// The account ID of the expected bucket owner. + /// If the account ID that you provide does not match the actual owner of the bucket, + /// the request fails with the HTTP status code 403 Forbidden (access denied). + /// + /// + public string ExpectedBucketOwner + { + get { return this.expectedBucketOwner; } + set { this.expectedBucketOwner = value; } + } + + /// + /// Checks to see if ExpectedBucketOwner is set. + /// + /// true, if ExpectedBucketOwner property is set. + internal bool IsSetExpectedBucketOwner() + { + return !String.IsNullOrEmpty(this.expectedBucketOwner); + } + + /// + /// Gets the access control lists (ACLs) for this request. + /// Please refer to for information on + /// S3 Grants. + /// + public List Grants + { + get { return _grants; } + set { _grants = value; } + } + + /// + /// Gets and sets the property SSEKMSEncryptionContext. + /// + /// Specifies the Amazon Web Services KMS Encryption Context as + /// an additional encryption context to use for object encryption. + /// The value of this header is a Base64 encoded string of a UTF-8 encoded JSON, + /// which contains the encryption context as key-value pairs. + /// This value is stored as object metadata and automatically gets passed on to + /// Amazon Web Services KMS for future GetObject operations on this object. + /// + /// General purpose buckets + /// - This value must be explicitly added during CopyObject operations + /// if you want an additional encryption context for your object. + /// For more information, see Encryption context + /// in the Amazon S3 User Guide. + /// + /// Directory buckets + /// - You can optionally provide an explicit encryption context value. + /// The value must match the default encryption context + /// - the bucket Amazon Resource Name (ARN). + /// An additional encryption context value is not supported. + /// + /// + public string SSEKMSEncryptionContext + { + get { return this.sseKMSEncryptionContext; } + set { this.sseKMSEncryptionContext = value; } + } + + /// + /// Checks to see if SSEKMSEncryptionContext is set. + /// + /// true, if SSEKMSEncryptionContext property is set. + internal bool IsSetSSEKMSEncryptionContext() + { + return !String.IsNullOrEmpty(this.sseKMSEncryptionContext); + } + + /// + /// Gets and sets the property WebsiteRedirectLocation. + /// + /// If the bucket is configured as a website, + /// redirects requests for this object to another object in the + /// same bucket or to an external URL. + /// Amazon S3 stores the value of this header in the object metadata. + /// For information about object metadata, see Object Key and Metadata + /// in the Amazon S3 User Guide. + /// + /// + /// In the following example, + /// the request header sets the redirect to an object (anotherPage.html) in the same bucket: + /// + /// + /// x-amz-website-redirect-location: /anotherPage.html + /// + /// + /// In the following example, + /// the request header sets the object redirect to another website: + /// + /// + /// x-amz-website-redirect-location: http://www.example.com/ + /// + /// + /// For more information about website hosting in Amazon S3, + /// see Hosting Websites on Amazon S3 + /// and How to Configure Website Page Redirects + /// in the Amazon S3 User Guide. + /// + /// + /// + /// This functionality is not supported for directory buckets. + /// + /// + /// + public string WebsiteRedirectLocation + { + get { return this.websiteRedirectLocation; } + set { this.websiteRedirectLocation = value; } + } + + /// + /// Checks to see if WebsiteRedirectLocation is set. + /// + /// true, if WebsiteRedirectLocation property is set. + internal bool IsSetWebsiteRedirectLocation() + { + return !String.IsNullOrEmpty(this.websiteRedirectLocation); + } + + /// + /// The collection of headers for the request. + /// + public HeadersCollection Headers + { + get + { + if (this.headersCollection == null) + this.headersCollection = new HeadersCollection(); + return this.headersCollection; + } + internal set { this.headersCollection = value; } + } } } \ No newline at end of file diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/AbortMultipartUploadsCommand.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/AbortMultipartUploadsCommand.cs index 09ed0dba2e8f..3e3ca44376df 100644 --- a/sdk/src/Services/S3/Custom/Transfer/Internal/AbortMultipartUploadsCommand.cs +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/AbortMultipartUploadsCommand.cs @@ -41,7 +41,7 @@ internal AbortMultipartUploadsCommand(IAmazonS3 s3Client, string bucketName, Dat this._initiatedDate = initiateDate; } - private ListMultipartUploadsRequest ConstructListMultipartUploadsRequest(ListMultipartUploadsResponse listResponse) + internal ListMultipartUploadsRequest ConstructListMultipartUploadsRequest(ListMultipartUploadsResponse listResponse) { ListMultipartUploadsRequest listRequest = new ListMultipartUploadsRequest() { @@ -53,7 +53,7 @@ private ListMultipartUploadsRequest ConstructListMultipartUploadsRequest(ListMul return listRequest; } - private AbortMultipartUploadRequest ConstructAbortMultipartUploadRequest(MultipartUpload upload) + internal AbortMultipartUploadRequest ConstructAbortMultipartUploadRequest(MultipartUpload upload) { var abortRequest = new AbortMultipartUploadRequest() { diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/BaseCommand.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/BaseCommand.cs index 5e5e83fdbae2..428758fa54e6 100644 --- a/sdk/src/Services/S3/Custom/Transfer/Internal/BaseCommand.cs +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/BaseCommand.cs @@ -37,7 +37,7 @@ public virtual object Return get { return null; } } - protected GetObjectRequest ConvertToGetObjectRequest(BaseDownloadRequest request) + internal GetObjectRequest ConvertToGetObjectRequest(BaseDownloadRequest request) { GetObjectRequest getRequest = new GetObjectRequest() { @@ -62,6 +62,21 @@ protected GetObjectRequest ConvertToGetObjectRequest(BaseDownloadRequest request getRequest.ChecksumMode = request.ChecksumMode; getRequest.RequestPayer = request.RequestPayer; + if (request.IsSetExpectedBucketOwner()) + { + getRequest.ExpectedBucketOwner = request.ExpectedBucketOwner; + } + if (request.IsSetIfMatch()) + { + getRequest.EtagToMatch = request.IfMatch; + } + if (request.IsSetIfNoneMatch()) + { + getRequest.EtagToNotMatch = request.IfNoneMatch; + } + + getRequest.ResponseHeaderOverrides = request.ResponseHeaderOverrides; + return getRequest; } diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/DownloadDirectoryCommand.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/DownloadDirectoryCommand.cs index 356091b417ee..0140554ded39 100644 --- a/sdk/src/Services/S3/Custom/Transfer/Internal/DownloadDirectoryCommand.cs +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/DownloadDirectoryCommand.cs @@ -91,7 +91,7 @@ private void EnsureDirectoryExists(DirectoryInfo directory) directory.Create(); } - private TransferUtilityDownloadRequest ConstructTransferUtilityDownloadRequest(S3Object s3Object, int prefixLength) + internal TransferUtilityDownloadRequest ConstructTransferUtilityDownloadRequest(S3Object s3Object, int prefixLength) { var downloadRequest = new TransferUtilityDownloadRequest(); downloadRequest.BucketName = this._request.BucketName; @@ -102,6 +102,10 @@ private TransferUtilityDownloadRequest ConstructTransferUtilityDownloadRequest(S downloadRequest.ServerSideEncryptionCustomerProvidedKey = this._request.ServerSideEncryptionCustomerProvidedKey; downloadRequest.ServerSideEncryptionCustomerProvidedKeyMD5 = this._request.ServerSideEncryptionCustomerProvidedKeyMD5; downloadRequest.RequestPayer = this._request.RequestPayer; + downloadRequest.ExpectedBucketOwner = this._request.ExpectedBucketOwner; + downloadRequest.IfMatch = this._request.IfMatch; + downloadRequest.IfNoneMatch = this._request.IfNoneMatch; + downloadRequest.ResponseHeaderOverrides = this._request.ResponseHeaderOverrides; //Ensure the target file is a rooted within LocalDirectory. Otherwise error. if(!InternalSDKUtils.IsFilePathRootedWithDirectoryPath(downloadRequest.FilePath, _request.LocalDirectory)) @@ -137,11 +141,12 @@ private ListObjectsV2Request ConstructListObjectRequestV2() } listRequestV2.RequestPayer = this._request.RequestPayer; + listRequestV2.ExpectedBucketOwner = this._request.ExpectedBucketOwner; return listRequestV2; } - private ListObjectsRequest ConstructListObjectRequest() + internal ListObjectsRequest ConstructListObjectRequest() { ListObjectsRequest listRequest = new ListObjectsRequest(); listRequest.BucketName = this._request.BucketName; @@ -164,6 +169,7 @@ private ListObjectsRequest ConstructListObjectRequest() } listRequest.RequestPayer = this._request.RequestPayer; + listRequest.ExpectedBucketOwner = this._request.ExpectedBucketOwner; return listRequest; } diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartUploadCommand.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartUploadCommand.cs index c24da9add8e0..de6ae0cbf813 100644 --- a/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartUploadCommand.cs +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartUploadCommand.cs @@ -147,12 +147,12 @@ private int CalculateConcurrentServiceRequests() return threadCount; } - private CompleteMultipartUploadRequest ConstructCompleteMultipartUploadRequest(InitiateMultipartUploadResponse initResponse) + internal CompleteMultipartUploadRequest ConstructCompleteMultipartUploadRequest(InitiateMultipartUploadResponse initResponse) { return ConstructCompleteMultipartUploadRequest(initResponse, false, null); } - private CompleteMultipartUploadRequest ConstructCompleteMultipartUploadRequest(InitiateMultipartUploadResponse initResponse, bool skipPartValidation, RequestEventHandler requestEventHandler) + internal CompleteMultipartUploadRequest ConstructCompleteMultipartUploadRequest(InitiateMultipartUploadResponse initResponse, bool skipPartValidation, RequestEventHandler requestEventHandler) { if (!skipPartValidation) { @@ -177,6 +177,7 @@ private CompleteMultipartUploadRequest ConstructCompleteMultipartUploadRequest(I ChecksumCRC64NVME = this._fileTransporterRequest.ChecksumCRC64NVME, ChecksumSHA1 = this._fileTransporterRequest.ChecksumSHA1, ChecksumSHA256 = this._fileTransporterRequest.ChecksumSHA256, + ExpectedBucketOwner = this._fileTransporterRequest.ExpectedBucketOwner, }; if(this._fileTransporterRequest.ServerSideEncryptionCustomerMethod != null @@ -210,7 +211,7 @@ private CompleteMultipartUploadRequest ConstructCompleteMultipartUploadRequest(I return compRequest; } - private UploadPartRequest ConstructUploadPartRequest(int partNumber, long filePosition, InitiateMultipartUploadResponse initiateResponse) + internal UploadPartRequest ConstructUploadPartRequest(int partNumber, long filePosition, InitiateMultipartUploadResponse initiateResponse) { UploadPartRequest uploadPartRequest = ConstructGenericUploadPartRequest(initiateResponse); @@ -241,7 +242,7 @@ private UploadPartRequest ConstructUploadPartRequest(int partNumber, long filePo return uploadPartRequest; } - private UploadPartRequest ConstructGenericUploadPartRequest(InitiateMultipartUploadResponse initiateResponse) + internal UploadPartRequest ConstructGenericUploadPartRequest(InitiateMultipartUploadResponse initiateResponse) { UploadPartRequest uploadPartRequest = new UploadPartRequest() { @@ -254,7 +255,8 @@ private UploadPartRequest ConstructGenericUploadPartRequest(InitiateMultipartUpl DisableDefaultChecksumValidation = this._fileTransporterRequest.DisableDefaultChecksumValidation, DisablePayloadSigning = this._fileTransporterRequest.DisablePayloadSigning, ChecksumAlgorithm = this._fileTransporterRequest.ChecksumAlgorithm, - RequestPayer = this._fileTransporterRequest.RequestPayer + RequestPayer = this._fileTransporterRequest.RequestPayer, + ExpectedBucketOwner = this._fileTransporterRequest.ExpectedBucketOwner, }; // If the InitiateMultipartUploadResponse indicates that this upload is using KMS, force SigV4 for each UploadPart request @@ -265,7 +267,7 @@ private UploadPartRequest ConstructGenericUploadPartRequest(InitiateMultipartUpl return uploadPartRequest; } - private UploadPartRequest ConstructUploadPartRequestForNonSeekableStream(Stream inputStream, int partNumber, long partSize, bool isLastPart, InitiateMultipartUploadResponse initiateResponse) + internal UploadPartRequest ConstructUploadPartRequestForNonSeekableStream(Stream inputStream, int partNumber, long partSize, bool isLastPart, InitiateMultipartUploadResponse initiateResponse) { UploadPartRequest uploadPartRequest = ConstructGenericUploadPartRequest(initiateResponse); @@ -285,18 +287,19 @@ private UploadPartRequest ConstructUploadPartRequestForNonSeekableStream(Stream return uploadPartRequest; } - private InitiateMultipartUploadRequest ConstructInitiateMultipartUploadRequest() + internal InitiateMultipartUploadRequest ConstructInitiateMultipartUploadRequest() { return this.ConstructInitiateMultipartUploadRequest(null); } - private InitiateMultipartUploadRequest ConstructInitiateMultipartUploadRequest(RequestEventHandler requestEventHandler) + internal InitiateMultipartUploadRequest ConstructInitiateMultipartUploadRequest(RequestEventHandler requestEventHandler) { var initRequest = new InitiateMultipartUploadRequest() { BucketName = this._fileTransporterRequest.BucketName, Key = this._fileTransporterRequest.Key, CannedACL = this._fileTransporterRequest.CannedACL, + Headers = this._fileTransporterRequest.Headers, ContentType = determineContentType(), StorageClass = this._fileTransporterRequest.StorageClass, ServerSideEncryptionMethod = this._fileTransporterRequest.ServerSideEncryptionMethod, @@ -304,11 +307,17 @@ private InitiateMultipartUploadRequest ConstructInitiateMultipartUploadRequest(R ServerSideEncryptionCustomerMethod = this._fileTransporterRequest.ServerSideEncryptionCustomerMethod, ServerSideEncryptionCustomerProvidedKey = this._fileTransporterRequest.ServerSideEncryptionCustomerProvidedKey, ServerSideEncryptionCustomerProvidedKeyMD5 = this._fileTransporterRequest.ServerSideEncryptionCustomerProvidedKeyMD5, + ServerSideEncryptionKeyManagementServiceEncryptionContext = this._fileTransporterRequest.SSEKMSEncryptionContext, TagSet = this._fileTransporterRequest.TagSet, ChecksumAlgorithm = this._fileTransporterRequest.ChecksumAlgorithm, ObjectLockLegalHoldStatus = this._fileTransporterRequest.ObjectLockLegalHoldStatus, ObjectLockMode = this._fileTransporterRequest.ObjectLockMode, - RequestPayer = this._fileTransporterRequest.RequestPayer + RequestPayer = this._fileTransporterRequest.RequestPayer, + ExpectedBucketOwner = this._fileTransporterRequest.ExpectedBucketOwner, + Grants = this._fileTransporterRequest.Grants, + Metadata = this._fileTransporterRequest.Metadata, + WebsiteRedirectLocation = this._fileTransporterRequest.WebsiteRedirectLocation, + BucketKeyEnabled = this._fileTransporterRequest.BucketKeyEnabled, }; if (this._fileTransporterRequest.IsSetObjectLockRetainUntilDate()) diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/OpenStreamCommand.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/OpenStreamCommand.cs index e34df962f364..57eab52d3f98 100644 --- a/sdk/src/Services/S3/Custom/Transfer/Internal/OpenStreamCommand.cs +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/OpenStreamCommand.cs @@ -41,7 +41,7 @@ internal OpenStreamCommand(IAmazonS3 s3Client, TransferUtilityOpenStreamRequest this._request = request; } - private GetObjectRequest ConstructRequest() + internal GetObjectRequest ConstructRequest() { if (!this._request.IsSetBucketName()) { diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/SimpleUploadCommand.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/SimpleUploadCommand.cs index 95a15611d2f5..3f10fa35b1d0 100644 --- a/sdk/src/Services/S3/Custom/Transfer/Internal/SimpleUploadCommand.cs +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/SimpleUploadCommand.cs @@ -50,7 +50,7 @@ internal SimpleUploadCommand(IAmazonS3 s3Client, TransferUtilityConfig config, T var fileName = fileTransporterRequest.FilePath; } - private PutObjectRequest ConstructRequest() + internal PutObjectRequest ConstructRequest() { PutObjectRequest putRequest = new PutObjectRequest() { @@ -78,7 +78,12 @@ private PutObjectRequest ConstructRequest() ChecksumCRC64NVME = this._fileTransporterRequest.ChecksumCRC64NVME, ChecksumSHA1 = this._fileTransporterRequest.ChecksumSHA1, ChecksumSHA256 = this._fileTransporterRequest.ChecksumSHA256, - RequestPayer = this._fileTransporterRequest.RequestPayer + RequestPayer = this._fileTransporterRequest.RequestPayer, + BucketKeyEnabled = this._fileTransporterRequest.BucketKeyEnabled, + ExpectedBucketOwner = this._fileTransporterRequest.ExpectedBucketOwner, + Grants = this._fileTransporterRequest.Grants, + ServerSideEncryptionKeyManagementServiceEncryptionContext = this._fileTransporterRequest.SSEKMSEncryptionContext, + WebsiteRedirectLocation = this._fileTransporterRequest.WebsiteRedirectLocation, }; // Avoid setting ContentType to null, as that may clear diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/UploadDirectoryCommand.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/UploadDirectoryCommand.cs index 816c8ef12e6f..e4be9b27aa74 100644 --- a/sdk/src/Services/S3/Custom/Transfer/Internal/UploadDirectoryCommand.cs +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/UploadDirectoryCommand.cs @@ -50,7 +50,7 @@ internal UploadDirectoryCommand(TransferUtility utility, TransferUtilityConfig c this._config = config; } - private TransferUtilityUploadRequest ConstructRequest(string basePath, string filepath, string prefix) + internal TransferUtilityUploadRequest ConstructRequest(string basePath, string filepath, string prefix) { string key = filepath.Substring(basePath.Length); key = key.Replace(@"\", "/"); @@ -79,6 +79,12 @@ private TransferUtilityUploadRequest ConstructRequest(string basePath, string fi RequestPayer = this._request.RequestPayer, DisableDefaultChecksumValidation = this._request.DisableDefaultChecksumValidation, ChecksumAlgorithm = this._request.ChecksumAlgorithm, + BucketKeyEnabled = this._request.BucketKeyEnabled, + ExpectedBucketOwner = this._request.ExpectedBucketOwner, + SSEKMSEncryptionContext = this._request.SSEKMSEncryptionContext, + WebsiteRedirectLocation = this._request.WebsiteRedirectLocation, + Headers = this._request.Headers, + Grants = this._request.Grants }; if (this._request.IsSetObjectLockRetainUntilDate()) diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/_async/MultipartUploadCommand.async.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/_async/MultipartUploadCommand.async.cs index 34bd339dc9a3..74e5f6c874a8 100644 --- a/sdk/src/Services/S3/Custom/Transfer/Internal/_async/MultipartUploadCommand.async.cs +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/_async/MultipartUploadCommand.async.cs @@ -168,17 +168,23 @@ private void Cleanup(string uploadId, List> tasks) AbortMultipartUpload(uploadId); } + internal AbortMultipartUploadRequest ConstructAbortMultipartUploadRequest(string uploadId) + { + return new AbortMultipartUploadRequest() + { + BucketName = this._fileTransporterRequest.BucketName, + ExpectedBucketOwner = this._fileTransporterRequest.ExpectedBucketOwner, + Key = this._fileTransporterRequest.Key, + RequestPayer = this._fileTransporterRequest.RequestPayer, + UploadId = uploadId + }; + } + private void AbortMultipartUpload(string uploadId) { try { - this._s3Client.AbortMultipartUploadAsync(new AbortMultipartUploadRequest() - { - BucketName = this._fileTransporterRequest.BucketName, - Key = this._fileTransporterRequest.Key, - RequestPayer = this._fileTransporterRequest.RequestPayer, - UploadId = uploadId - }).Wait(); + this._s3Client.AbortMultipartUploadAsync(ConstructAbortMultipartUploadRequest(uploadId)).Wait(); } catch (Exception e) { diff --git a/sdk/src/Services/S3/Custom/Transfer/TransferUtilityDownloadDirectoryRequest.cs b/sdk/src/Services/S3/Custom/Transfer/TransferUtilityDownloadDirectoryRequest.cs index 2bffe3c249bb..b0556e92487a 100644 --- a/sdk/src/Services/S3/Custom/Transfer/TransferUtilityDownloadDirectoryRequest.cs +++ b/sdk/src/Services/S3/Custom/Transfer/TransferUtilityDownloadDirectoryRequest.cs @@ -52,6 +52,11 @@ public class TransferUtilityDownloadDirectoryRequest private RequestPayer requestPayer; + private string expectedBucketOwner; + private string ifMatch; + private string ifNoneMatch; + private ResponseHeaderOverrides responseHeaders; + /// /// Gets or sets the name of the bucket. /// @@ -255,6 +260,108 @@ public RequestPayer RequestPayer get { return this.requestPayer; } set { this.requestPayer = value; } } + + /// + /// Gets and sets the property ExpectedBucketOwner. + /// + /// The account ID of the expected bucket owner. If the account ID that you provide does + /// not match the actual owner of the bucket, the request fails with the HTTP status code + /// 403 Forbidden (access denied). + /// + /// + public string ExpectedBucketOwner + { + get { return this.expectedBucketOwner; } + set { this.expectedBucketOwner = value; } + } + + /// + /// Checks to see if ExpectedBucketOwner is set. + /// + /// true, if ExpectedBucketOwner property is set. + internal bool IsSetExpectedBucketOwner() + { + return !String.IsNullOrEmpty(this.expectedBucketOwner); + } + + /// + /// Gets and sets the property IfMatch. + /// + /// Return the object only if its entity tag (ETag) is the same as the one specified in this header; + /// otherwise, return a 412 Precondition Failed error. + /// + /// + /// If both of the If-Match and If-Unmodified-Since headers are present in the request as follows: + /// If-Match condition evaluates to true, and; If-Unmodified-Since condition evaluates to false; + /// then, S3 returns 200 OK and the data requested. + /// + /// + /// For more information about conditional requests, see RFC 7232. + /// + /// + public string IfMatch + { + get { return this.ifMatch; } + set { this.ifMatch = value; } + } + + /// + /// Checks to see if IfMatch is set. + /// + /// true, if IfMatch property is set. + internal bool IsSetIfMatch() + { + return !String.IsNullOrEmpty(this.ifMatch); + } + + /// + /// Gets and sets the property IfNoneMatch. + /// + /// Return the object only if its entity tag (ETag) is different from the one specified in this header; + /// otherwise, return a 304 Not Modified error. + /// + /// + /// If both of the If-None-Match and If-Modified-Since headers are present in the request as follows: + /// If-None-Match condition evaluates to false, and; If-Modified-Since condition evaluates to true; + /// then, S3 returns 304 Not Modified HTTP status code. + /// + /// + /// For more information about conditional requests, see RFC 7232. + /// + /// + public string IfNoneMatch + { + get { return this.ifNoneMatch; } + set { this.ifNoneMatch = value; } + } + + /// + /// Checks to see if IfNoneMatch is set. + /// + /// true, if IfNoneMatch property is set. + internal bool IsSetIfNoneMatch() + { + return !String.IsNullOrEmpty(this.ifNoneMatch); + } + + /// + /// A set of response headers that should be returned with the object. + /// + public ResponseHeaderOverrides ResponseHeaderOverrides + { + get + { + if (this.responseHeaders == null) + { + this.responseHeaders = new ResponseHeaderOverrides(); + } + return this.responseHeaders; + } + set + { + this.responseHeaders = value; + } + } /// /// The event for DownloadedDirectoryProgressEvent notifications. All diff --git a/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadDirectoryRequest.cs b/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadDirectoryRequest.cs index 5588d9ad9327..cf7be9f65437 100644 --- a/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadDirectoryRequest.cs +++ b/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadDirectoryRequest.cs @@ -38,35 +38,10 @@ namespace Amazon.S3.Transfer public class TransferUtilityUploadDirectoryRequest : BaseUploadRequest { string _directory; - string _bucketname; string _searchPattern = "*"; string _keyPrefix; - private string contentType; private bool _uploadFilesConcurrently = false; SearchOption _searchOption = SearchOption.TopDirectoryOnly; - S3CannedACL _cannedACL; - S3StorageClass _storageClass; - MetadataCollection metadataCollection; - ServerSideEncryptionMethod encryption; - string serverSideEncryptionKeyManagementServiceKeyId; - private ServerSideEncryptionCustomerMethod serverSideCustomerEncryption; - private string serverSideEncryptionCustomerProvidedKey; - private string serverSideEncryptionCustomerProvidedKeyMD5; - private List tagset; - private ObjectLockLegalHoldStatus objectLockLegalHoldStatus; - private ObjectLockMode objectLockMode; - private bool disablePayloadSigning; - private DateTime? objectLockRetainUntilDate; - private ChecksumAlgorithm checksumAlgorithm; - - /// - /// Gets or sets whether the payload should be signed or not - /// - public bool DisablePayloadSigning - { - get { return this.disablePayloadSigning; } - set { this.disablePayloadSigning = value; } - } /// /// Gets or sets the directory where files are uploaded from. @@ -152,226 +127,6 @@ public SearchOption SearchOption set { this._searchOption = value; } } - - /// - /// Gets or sets the name of the bucket. - /// - /// - /// The name of the bucket. - /// - public string BucketName - { - get { return this._bucketname; } - set { this._bucketname = value; } - } - - /// - /// Checks if BucketName property is set. - /// - /// true if BucketName property is set. - internal bool IsSetBucketName() - { - return !System.String.IsNullOrEmpty(this._bucketname); - } - - - /// - /// Gets or sets the canned access control list (ACL) - /// for the uploaded objects. - /// Please refer to - /// for - /// information on Amazon S3 canned ACLs. - /// - /// - /// The canned access control list (ACL) - /// for the uploaded objects. - /// - public S3CannedACL CannedACL - { - get { return this._cannedACL; } - set { this._cannedACL = value; } - } - - - /// - /// Checks if the CannedACL property is set. - /// - /// true if there is the CannedACL property is set. - internal bool IsSetCannedACL() - { - return (_cannedACL != null &&_cannedACL != S3CannedACL.NoACL); - } - - /// - /// Gets or sets the content type for the uploaded Amazon S3 objects. - /// The default behavior when this field is not set is to use the file - /// extension to set the content type. If this field is set to a value it - /// will be applied to all uploaded files in the directory, overriding - /// file extension inspection. - /// - /// - /// The content type for all the uploaded Amazon S3 objects. - /// - public string ContentType - { - get { return this.contentType; } - set { this.contentType = value; } - } - - - /// - /// Gets or sets the storage class for the uploaded Amazon S3 objects. - /// Please refer to - /// for - /// information on S3 Storage Classes. - /// - /// - /// The storage class for the uploaded Amazon S3 objects. - /// - public S3StorageClass StorageClass - { - get { return this._storageClass; } - set - { - this._storageClass = value; - } - } - - - /// - /// The collection of meta data for the request. - /// - public MetadataCollection Metadata - { - get - { - if (this.metadataCollection == null) - this.metadataCollection = new MetadataCollection(); - return this.metadataCollection; - } - internal set { this.metadataCollection = value; } - } - - #region ServerSideEncryption - - /// - /// Gets or sets the ServerSideEncryptionMethod property. - /// Specifies the encryption used on the server to - /// store the content. - /// - public ServerSideEncryptionMethod ServerSideEncryptionMethod - { - get { return this.encryption; } - set { this.encryption = value; } - } - - /// - /// The id of the AWS Key Management Service key that Amazon S3 should use to encrypt and decrypt the object. - /// If a key id is not specified, the default key will be used for encryption and decryption. - /// - [AWSProperty(Sensitive=true)] - public string ServerSideEncryptionKeyManagementServiceKeyId - { - get { return this.serverSideEncryptionKeyManagementServiceKeyId; } - set { this.serverSideEncryptionKeyManagementServiceKeyId = value; } - } - - /// - /// The Server-side encryption algorithm to be used with the customer provided key. - /// - public ServerSideEncryptionCustomerMethod ServerSideEncryptionCustomerMethod - { - get { return this.serverSideCustomerEncryption; } - set { this.serverSideCustomerEncryption = value; } - } - - /// - /// Checks if ServerSideEncryptionKeyManagementServiceKeyId property is set. - /// - /// true if ServerSideEncryptionKeyManagementServiceKeyId property is set. - internal bool IsSetServerSideEncryptionKeyManagementServiceKeyId() - { - return !System.String.IsNullOrEmpty(this.serverSideEncryptionKeyManagementServiceKeyId); - } - - /// - /// The base64-encoded encryption key for Amazon S3 to use to encrypt the object - /// - /// Using the encryption key you provide as part of your request Amazon S3 manages both the encryption, as it writes - /// to disks, and decryption, when you access your objects. Therefore, you don't need to maintain any data encryption code. The only - /// thing you do is manage the encryption keys you provide. - /// - /// - /// When you retrieve an object, you must provide the same encryption key as part of your request. Amazon S3 first verifies - /// the encryption key you provided matches, and then decrypts the object before returning the object data to you. - /// - /// - /// Important: Amazon S3 does not store the encryption key you provide. - /// - /// - [AWSProperty(Sensitive=true)] - public string ServerSideEncryptionCustomerProvidedKey - { - get { return this.serverSideEncryptionCustomerProvidedKey; } - set { this.serverSideEncryptionCustomerProvidedKey = value; } - } - - /// - /// The MD5 of the customer encryption key specified in the ServerSideEncryptionCustomerProvidedKey property. The MD5 is - /// base 64 encoded. This field is optional, the SDK will calculate the MD5 if this is not set. - /// - public string ServerSideEncryptionCustomerProvidedKeyMD5 - { - get { return this.serverSideEncryptionCustomerProvidedKeyMD5; } - set { this.serverSideEncryptionCustomerProvidedKeyMD5 = value; } - } - - #endregion - - /// - /// Gets and sets the property ObjectLockLegalHoldStatus. - /// - /// Specifies whether a legal hold will be applied to this object. For more information - /// about S3 Object Lock, see Object - /// Lock. - /// - /// - public ObjectLockLegalHoldStatus ObjectLockLegalHoldStatus - { - get { return this.objectLockLegalHoldStatus; } - set { this.objectLockLegalHoldStatus = value; } - } - - /// - /// Gets and sets the property ObjectLockMode. - /// - /// The Object Lock mode that you want to apply to this object. - /// - /// - public ObjectLockMode ObjectLockMode - { - get { return this.objectLockMode; } - set { this.objectLockMode = value; } - } - - /// - /// Gets and sets the property ObjectLockRetainUntilDate. - /// - /// The date and time when you want this object's Object Lock to expire. - /// - /// - public DateTime ObjectLockRetainUntilDate - { - get { return this.objectLockRetainUntilDate.GetValueOrDefault(); } - set { this.objectLockRetainUntilDate = value; } - } - - // Check to see if ObjectLockRetainUntilDate property is set - internal bool IsSetObjectLockRetainUntilDate() - { - return this.objectLockRetainUntilDate.HasValue; - } - /// /// Gets or sets the UploadFilesConcurrently property. /// Specifies if multiple files will be uploaded concurrently. @@ -444,41 +199,6 @@ internal void RaiseUploadDirectoryFileRequestEvent(TransferUtilityUploadRequest targetEvent(this, args); } } - - /// - /// Tags that will be applied to all objects in the diretory. - /// - public List TagSet - { - get { return this.tagset; } - set { this.tagset = value; } - } - - /// - /// WARNING: Setting DisableDefaultChecksumValidation to true disables the default data - /// integrity check on upload requests. - /// When true, checksum verification will not be used in upload requests. This may increase upload - /// performance under high CPU loads. The default value is false. - /// Checksums, SigV4 payload signing, and HTTPS each provide some data integrity - /// verification. If DisableDefaultChecksumValidation is true and DisablePayloadSigning is true, then the - /// possibility of data corruption is completely dependent on HTTPS being the only remaining - /// source of data integrity verification. - /// - public bool? DisableDefaultChecksumValidation { get; set; } - - /// - /// Gets and sets the property ChecksumAlgorithm. - /// - /// Indicates the algorithm used to create the checksum for each object in the provided directory. - /// For more information, see - /// Checking object integrity in the Amazon S3 User Guide. - /// - /// - public ChecksumAlgorithm ChecksumAlgorithm - { - get { return this.checksumAlgorithm; } - set { this.checksumAlgorithm = value; } - } } /// diff --git a/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadRequest.cs b/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadRequest.cs index 868fcf697dd8..b21ab2ae7602 100644 --- a/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadRequest.cs +++ b/sdk/src/Services/S3/Custom/Transfer/TransferUtilityUploadRequest.cs @@ -38,20 +38,10 @@ namespace Amazon.S3.Transfer /// public partial class TransferUtilityUploadRequest : BaseUploadRequest { - private string bucketName; private string key; - private S3CannedACL cannedACL; - private string contentType; - private S3StorageClass storageClass; private long? partSize; private bool autoCloseStream = true; private bool autoResetStreamPosition = true; - private ServerSideEncryptionMethod encryption; - private ServerSideEncryptionCustomerMethod serverSideCustomerEncryption; - private string serverSideEncryptionCustomerProvidedKey; - private string serverSideEncryptionCustomerProvidedKeyMD5; - private string serverSideEncryptionKeyManagementServiceKeyId; - private ChecksumAlgorithm checksumAlgorithm; private string _checksumCRC32; private string _checksumCRC32C; private string _checksumCRC64NVME; @@ -61,41 +51,7 @@ public partial class TransferUtilityUploadRequest : BaseUploadRequest private string _ifMatch; private long? _mpuObjectSize; - private HeadersCollection headersCollection = new HeadersCollection(); - private MetadataCollection metadataCollection = new MetadataCollection(); - - private List tagset; - private Stream inputStream; - private ObjectLockLegalHoldStatus objectLockLegalHoldStatus; - private ObjectLockMode objectLockMode; - private DateTime? objectLockRetainUntilDate; - - #region BucketName - - /// - /// Gets or sets the name of the bucket. - /// - /// - /// The name of the bucket. - /// - public string BucketName - { - get { return this.bucketName; } - set { this.bucketName = value; } - } - - - /// - /// Checks if BucketName property is set. - /// - /// true if BucketName property is set. - internal bool IsSetBucketName() - { - return !System.String.IsNullOrEmpty(this.bucketName); - } - - #endregion #region Key /// @@ -122,166 +78,6 @@ internal bool IsSetKey() #endregion - #region CannedACL - - /// - /// Gets or sets the canned access control list (ACL) - /// for the uploaded object. - /// Please refer to - /// for - /// information on Amazon S3 canned ACLs. - /// - /// - /// The canned access control list (ACL) - /// for the uploaded object. - /// - public S3CannedACL CannedACL - { - get { return this.cannedACL; } - set { this.cannedACL = value; } - } - - /// - /// Checks if the CannedACL property is set. - /// - /// true if there is the CannedACL property is set. - internal bool IsSetCannedACL() - { - return (cannedACL != null); - } - - /// - /// Removes the cannned access control list (ACL) - /// for the uploaded object. - /// - public void RemoveCannedACL() - { - this.cannedACL = null; - } - - #endregion - - #region ContentType - /// - /// Gets or sets the content type of the uploaded Amazon S3 object. - /// - /// - /// The content type of the uploaded Amazon S3 object. - /// - public string ContentType - { - get { return this.contentType; } - set { this.contentType = value; } - } - - - /// - /// Checks if ContentType property is set. - /// - /// true if ContentType property is set. - internal bool IsSetContentType() - { - return !System.String.IsNullOrEmpty(this.contentType); - } - - #endregion - - #region StorageClass - - /// - /// Gets or sets the storage class for the uploaded Amazon S3 object. - /// Please refer to - /// for - /// information on S3 Storage Classes. - /// - /// - /// The storage class for the uploaded Amazon S3 object. - /// - public S3StorageClass StorageClass - { - get { return this.storageClass; } - set { this.storageClass = value; } - } - - #endregion - - #region ServerSideEncryption - - /// - /// Gets and sets the ServerSideEncryptionMethod property. - /// Specifies the encryption used on the server to - /// store the content. - /// - public ServerSideEncryptionMethod ServerSideEncryptionMethod - { - get { return this.encryption; } - set { this.encryption = value; } - } - - /// - /// The Server-side encryption algorithm to be used with the customer provided key. - /// - /// - public ServerSideEncryptionCustomerMethod ServerSideEncryptionCustomerMethod - { - get { return this.serverSideCustomerEncryption; } - set { this.serverSideCustomerEncryption = value; } - } - - /// - /// The id of the AWS Key Management Service key that Amazon S3 should use to encrypt and decrypt the object. - /// If a key id is not specified, the default key will be used for encryption and decryption. - /// - [AWSProperty(Sensitive=true)] - public string ServerSideEncryptionKeyManagementServiceKeyId - { - get { return this.serverSideEncryptionKeyManagementServiceKeyId; } - set { this.serverSideEncryptionKeyManagementServiceKeyId = value; } - } - - /// - /// Checks if ServerSideEncryptionKeyManagementServiceKeyId property is set. - /// - /// true if ServerSideEncryptionKeyManagementServiceKeyId property is set. - internal bool IsSetServerSideEncryptionKeyManagementServiceKeyId() - { - return !System.String.IsNullOrEmpty(this.serverSideEncryptionKeyManagementServiceKeyId); - } - - /// - /// The Base64 encoded encryption key for Amazon S3 to use to encrypt the object - /// - /// Using the encryption key you provide as part of your request Amazon S3 manages both the encryption, as it writes - /// to disks, and decryption, when you access your objects. Therefore, you don't need to maintain any data encryption code. The only - /// thing you do is manage the encryption keys you provide. - /// - /// - /// When you retrieve an object, you must provide the same encryption key as part of your request. Amazon S3 first verifies - /// the encryption key you provided matches, and then decrypts the object before returning the object data to you. - /// - /// - /// Important: Amazon S3 does not store the encryption key you provide. - /// - /// - [AWSProperty(Sensitive=true)] - public string ServerSideEncryptionCustomerProvidedKey - { - get { return this.serverSideEncryptionCustomerProvidedKey; } - set { this.serverSideEncryptionCustomerProvidedKey = value; } - } - - /// - /// The MD5 of the customer encryption key specified in the ServerSideEncryptionCustomerProvidedKey property. The MD5 is - /// base 64 encoded. This field is optional, the SDK will calculate the MD5 if this is not set. - /// - public string ServerSideEncryptionCustomerProvidedKeyMD5 - { - get { return this.serverSideEncryptionCustomerProvidedKeyMD5; } - set { this.serverSideEncryptionCustomerProvidedKeyMD5 = value; } - } - - #endregion - /// /// Input stream for the request; content for the request will be read from the stream. /// @@ -345,43 +141,6 @@ internal bool IsSetPartSize() return this.partSize.HasValue; } - /// - /// The collection of headers for the request. - /// - public HeadersCollection Headers - { - get - { - if (this.headersCollection == null) - this.headersCollection = new HeadersCollection(); - return this.headersCollection; - } - internal set { this.headersCollection = value; } - } - - /// - /// The collection of meta data for the request. - /// - public MetadataCollection Metadata - { - get - { - if (this.metadataCollection == null) - this.metadataCollection = new MetadataCollection(); - return this.metadataCollection; - } - internal set { this.metadataCollection = value; } - } - - /// - /// The tag-set for the object. - /// - public List TagSet - { - get { return this.tagset; } - set { this.tagset = value; } - } - /// /// The event for UploadProgressEvent notifications. All /// subscribers will be notified when a new progress @@ -502,100 +261,6 @@ public TransferUtilityUploadRequest WithAutoCloseStream(bool autoCloseStream) } #endregion - /// - /// WARNING: Setting DisableDefaultChecksumValidation to true disables the default data - /// integrity check on upload requests. - /// When true, checksum verification will not be used in upload requests. This may increase upload - /// performance under high CPU loads. Setting DisableDefaultChecksumValidation sets the deprecated property - /// DisableMD5Stream to the same value. The default value is false. - /// Checksums, SigV4 payload signing, and HTTPS each provide some data integrity - /// verification. If DisableDefaultChecksumValidation is true and DisablePayloadSigning is true, then the - /// possibility of data corruption is completely dependent on HTTPS being the only remaining - /// source of data integrity verification. - /// - public bool? DisableDefaultChecksumValidation { get; set; } - - /// - /// WARNING: Setting DisablePayloadSigning to true disables the SigV4 payload signing - /// data integrity check on this request. - /// If using SigV4, the DisablePayloadSigning flag controls if the payload should be - /// signed on a request by request basis. By default this flag is null which will use the - /// default client behavior. The default client behavior is to sign the payload. When - /// DisablePayloadSigning is true, the request will be signed with an UNSIGNED-PAYLOAD value. - /// Setting DisablePayloadSigning to true requires that the request is sent over a HTTPS - /// connection. - /// Under certain circumstances, such as uploading to S3 while using MD5 hashing, it may - /// be desireable to use UNSIGNED-PAYLOAD to decrease signing CPU usage. This flag only applies - /// to Amazon S3 PutObject and UploadPart requests. - /// MD5Stream, SigV4 payload signing, and HTTPS each provide some data integrity - /// verification. If DisableMD5Stream is true and DisablePayloadSigning is true, then the - /// possibility of data corruption is completely dependant on HTTPS being the only remaining - /// source of data integrity verification. - /// - public bool? DisablePayloadSigning { get; set; } - - /// - /// Gets and sets the property ObjectLockLegalHoldStatus. - /// - /// Specifies whether a legal hold will be applied to this object. For more information - /// about S3 Object Lock, see Object - /// Lock. - /// - /// - public ObjectLockLegalHoldStatus ObjectLockLegalHoldStatus - { - get { return this.objectLockLegalHoldStatus; } - set { this.objectLockLegalHoldStatus = value; } - } - - /// - /// Gets and sets the property ObjectLockMode. - /// - /// The Object Lock mode that you want to apply to this object. - /// - /// - public ObjectLockMode ObjectLockMode - { - get { return this.objectLockMode; } - set { this.objectLockMode = value; } - } - - /// - /// Gets and sets the property ObjectLockRetainUntilDate. - /// - /// The date and time when you want this object's Object Lock to expire. - /// - /// - public DateTime ObjectLockRetainUntilDate - { - get { return this.objectLockRetainUntilDate.GetValueOrDefault(); } - set { this.objectLockRetainUntilDate = value; } - } - - // Check to see if ObjectLockRetainUntilDate property is set - internal bool IsSetObjectLockRetainUntilDate() - { - return this.objectLockRetainUntilDate.HasValue; - } - - /// - /// Gets and sets the property ChecksumAlgorithm. - /// - /// Indicates the algorithm used to create the checksum for the object. - /// For more information, see - /// Checking object integrity in the Amazon S3 User Guide. - /// - /// - /// - /// If you provide an individual checksum, Amazon S3 will ignore any provided ChecksumAlgorithm. - /// - /// - public ChecksumAlgorithm ChecksumAlgorithm - { - get { return this.checksumAlgorithm; } - set { this.checksumAlgorithm = value; } - } - /// /// Gets and sets the property ChecksumCRC32. /// diff --git a/sdk/test/Services/S3/UnitTests/Custom/EmbeddedResource/property-aliases.json b/sdk/test/Services/S3/UnitTests/Custom/EmbeddedResource/property-aliases.json index 245790b5fdbe..e9de0a44b4fe 100644 --- a/sdk/test/Services/S3/UnitTests/Custom/EmbeddedResource/property-aliases.json +++ b/sdk/test/Services/S3/UnitTests/Custom/EmbeddedResource/property-aliases.json @@ -1,10 +1,117 @@ { "PropertyAliases": { - "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", - "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5", - "SSEKMSKeyId": "ServerSideEncryptionKeyManagementServiceKeyId", - "ServerSideEncryption": "ServerSideEncryptionMethod", - "SSEKMSEncryptionContext": "ServerSideEncryptionKeyManagementServiceEncryptionContext", - "Restore": "RestoreExpiration" + "PutObjectResponse": { + "ServerSideEncryption": "ServerSideEncryptionMethod", + "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", + "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5", + "SSEKMSEncryptionContext": "ServerSideEncryptionKeyManagementServiceEncryptionContext", + "SSEKMSKeyId": "ServerSideEncryptionKeyManagementServiceKeyId" + }, + "TransferUtilityUploadResponse": { + "ServerSideEncryption": "ServerSideEncryptionMethod", + "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", + "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5", + "SSEKMSEncryptionContext": "ServerSideEncryptionKeyManagementServiceEncryptionContext", + "SSEKMSKeyId": "ServerSideEncryptionKeyManagementServiceKeyId" + }, + "PutObjectRequest": { + "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", + "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5", + "SSEKMSKeyId": "ServerSideEncryptionKeyManagementServiceKeyId", + "ServerSideEncryption": "ServerSideEncryptionMethod", + "SSEKMSEncryptionContext": "ServerSideEncryptionKeyManagementServiceEncryptionContext", + "ACL": "CannedACL", + "Bucket": "BucketName", + "SSECustomerKey": "ServerSideEncryptionCustomerProvidedKey", + "Tagging": "TagSet", + "GrantFullControl": "Grants", + "GrantRead": "Grants", + "GrantReadACP": "Grants", + "GrantWriteACP": "Grants" + }, + "GetObjectRequest": { + "Bucket": "BucketName", + "SSECustomerKey": "ServerSideEncryptionCustomerProvidedKey", + "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", + "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5", + "ResponseCacheControl": "CacheControl", + "ResponseContentDisposition": "ContentDisposition", + "ResponseContentEncoding": "ContentEncoding", + "ResponseContentLanguage": "ContentLanguage", + "ResponseContentType": "ContentType", + "IfMatch": "EtagToMatch", + "IfNoneMatch": "EtagToNotMatch", + "IfModifiedSince": "ModifiedSinceDate", + "IfUnmodifiedSince": "UnmodifiedSinceDate" + }, + "TransferUtilityDownloadRequest": { + "Bucket": "BucketName", + "IfModifiedSince": "ModifiedSinceDate", + "IfUnmodifiedSince": "UnmodifiedSinceDate", + "ResponseCacheControl": "CacheControl", + "ResponseContentDisposition": "ContentDisposition", + "ResponseContentEncoding": "ContentEncoding", + "ResponseContentLanguage": "ContentLanguage", + "ResponseContentType": "ContentType", + "ResponseExpires": "Expires", + "SSECustomerKey": "ServerSideEncryptionCustomerProvidedKey", + "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", + "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5" + }, + "TransferUtilityUploadRequest": { + "ACL": "CannedACL", + "Bucket": "BucketName", + "GrantFullControl": "Grants", + "GrantRead": "Grants", + "GrantReadACP": "Grants", + "GrantWriteACP": "Grants", + "ServerSideEncryption": "ServerSideEncryptionMethod", + "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", + "SSECustomerKey": "ServerSideEncryptionCustomerProvidedKey", + "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5", + "SSEKMSKeyId": "ServerSideEncryptionKeyManagementServiceKeyId", + "Tagging": "TagSet" + }, + "TransferUtilityUploadDirectoryRequest": { + "ACL": "CannedACL", + "Bucket": "BucketName", + "GrantFullControl": "Grants", + "GrantRead": "Grants", + "GrantReadACP": "Grants", + "GrantWriteACP": "Grants", + "ServerSideEncryption": "ServerSideEncryptionMethod", + "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", + "SSECustomerKey": "ServerSideEncryptionCustomerProvidedKey", + "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5", + "SSEKMSKeyId": "ServerSideEncryptionKeyManagementServiceKeyId", + "Tagging": "TagSet" + }, + "InitiateMultipartUploadRequest": { + "ACL": "CannedACL", + "Bucket": "BucketName", + "GrantFullControl": "Grants", + "GrantRead": "Grants", + "GrantReadACP": "Grants", + "GrantWriteACP": "Grants", + "ServerSideEncryption": "ServerSideEncryptionMethod", + "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", + "SSECustomerKey": "ServerSideEncryptionCustomerProvidedKey", + "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5", + "SSEKMSKeyId": "ServerSideEncryptionKeyManagementServiceKeyId", + "Tagging": "TagSet", + "SSEKMSEncryptionContext": "ServerSideEncryptionKeyManagementServiceEncryptionContext" + }, + "UploadPartRequest": { + "Bucket": "BucketName", + "SSECustomerAlgorithm": "ServerSideEncryptionCustomerMethod", + "SSECustomerKey": "ServerSideEncryptionCustomerProvidedKey", + "SSECustomerKeyMD5": "ServerSideEncryptionCustomerProvidedKeyMD5" + }, + "CompleteMultipartUploadRequest": { + "Bucket": "BucketName" + }, + "AbortMultipartUploadRequest": { + "Bucket": "BucketName" + } } -} +} \ No newline at end of file diff --git a/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs b/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs index ce4960e85985..bb828dc7a446 100644 --- a/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs +++ b/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs @@ -34,7 +34,8 @@ public class ResponseMapperTests { private static JsonDocument _mappingJson; private static JsonDocument _propertyAliasesJson; - private static Dictionary _propertyAliases; + private static Dictionary> _propertyAliases; + private static List _s3Grants; [ClassInitialize] public static void ClassInitialize(TestContext context) @@ -65,17 +66,22 @@ public static void ClassInitialize(TestContext context) _propertyAliasesJson = JsonDocument.Parse(aliasContent); // Convert to dictionary for fast lookup - _propertyAliases = new Dictionary(); - var aliasesElement = _propertyAliasesJson.RootElement.GetProperty("PropertyAliases"); - foreach (var alias in aliasesElement.EnumerateObject()) + _propertyAliases = new Dictionary>(); + var objectElement = _propertyAliasesJson.RootElement.GetProperty("PropertyAliases"); + foreach (var objectName in objectElement.EnumerateObject()) { - _propertyAliases[alias.Name] = alias.Value.GetString(); + var aliases = new Dictionary(); + foreach (var alias in objectName.Value.EnumerateObject()) + { + aliases[alias.Name] = alias.Value.GetString(); + } + _propertyAliases[objectName.Name] = aliases; } } } else { - _propertyAliases = new Dictionary(); + _propertyAliases = new Dictionary>(); } } } @@ -147,7 +153,7 @@ public void MapPutObjectResponse_AllMappedProperties_WorkCorrectly() foreach (var propertyName in putObjectMappings) { // Resolve alias to actual property name - var resolvedPropertyName = ResolvePropertyName(propertyName); + var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); var sourceProperty = sourceType.GetProperty(resolvedPropertyName); if (sourceProperty?.CanWrite == true) { @@ -172,19 +178,20 @@ public void MapPutObjectResponse_AllMappedProperties_WorkCorrectly() foreach (var propertyName in putObjectMappings) { // Resolve alias to actual property name for reflection lookups - var resolvedPropertyName = ResolvePropertyName(propertyName); - var sourceProperty = sourceType.GetProperty(resolvedPropertyName); - var targetProperty = targetType.GetProperty(resolvedPropertyName); + var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); + var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); + var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); if (sourceProperty == null) { - failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedPropertyName}) not found in PutObjectResponse"); + failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); continue; } if (targetProperty == null) { - failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedPropertyName}) not found in TransferUtilityUploadResponse"); + failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); continue; } @@ -209,6 +216,491 @@ public void MapPutObjectResponse_AllMappedProperties_WorkCorrectly() } } + [TestMethod] + [TestCategory("S3")] + public void MapUploadRequest_PutObjectRequest_AllMappedProperties_WorkCorrectly() + { + // Get the expected mappings from JSON + var putObjectMappings = _mappingJson.RootElement + .GetProperty("Conversion") + .GetProperty("UploadRequest") + .GetProperty("PutObjectRequest") + .EnumerateArray() + .Select(prop => prop.GetString()) + .ToList(); + + // Create source object with dynamically generated test data + var sourceRequest = new TransferUtilityUploadRequest(); + var sourceType = typeof(TransferUtilityUploadRequest); + var testDataValues = new Dictionary(); + + // Generate test data for each mapped property + foreach (var propertyName in putObjectMappings) + { + // Resolve alias to actual property name + var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var sourceProperty = sourceType.GetProperty(resolvedPropertyName); + if (sourceProperty?.CanWrite == true) + { + var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); + sourceProperty.SetValue(sourceRequest, testValue); + testDataValues[propertyName] = testValue; + } + } + + var simpleUploadCommand = new SimpleUploadCommand(null, null, sourceRequest); + + // Map the response + var mappedRequest = simpleUploadCommand.ConstructRequest(); + Assert.IsNotNull(mappedRequest, "Mapped request should not be null"); + + // Verify all mapped properties using reflection + var targetType = typeof(PutObjectRequest); + var failedAssertions = new List(); + + foreach (var propertyName in putObjectMappings) + { + // Resolve alias to actual property name for reflection lookups + var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); + var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); + var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); + + if (sourceProperty == null) + { + failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); + continue; + } + + if (targetProperty == null) + { + failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); + continue; + } + + var sourceValue = sourceProperty.GetValue(sourceRequest); + var targetValue = targetProperty.GetValue(mappedRequest); + + // Special handling for complex object comparisons + if (!AreValuesEqual(sourceValue, targetValue)) + { + failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); + } + } + + // Report any failures + if (failedAssertions.Any()) + { + Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); + } + } + + [TestMethod] + [TestCategory("S3")] + public void MapUploadRequest_CreateMultipartRequest_AllMappedProperties_WorkCorrectly() + { + // Get the expected mappings from JSON + var createMultipartRequestMappings = _mappingJson.RootElement + .GetProperty("Conversion") + .GetProperty("UploadRequest") + .GetProperty("CreateMultipartRequest") + .EnumerateArray() + .Select(prop => prop.GetString()) + .ToList(); + + // Create source object with dynamically generated test data + var sourceRequest = new TransferUtilityUploadRequest(); + var sourceType = typeof(TransferUtilityUploadRequest); + var testDataValues = new Dictionary(); + + // Generate test data for each mapped property + foreach (var propertyName in createMultipartRequestMappings) + { + // Resolve alias to actual property name + var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var sourceProperty = sourceType.GetProperty(resolvedPropertyName); + if (sourceProperty?.CanWrite == true) + { + var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); + sourceProperty.SetValue(sourceRequest, testValue); + testDataValues[propertyName] = testValue; + } + } + + // Add inherited properties for comprehensive testing + sourceRequest.InputStream = new MemoryStream(1024); + + var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); + + // Map the response + var mappedRequest = multipartUploadCommand.ConstructInitiateMultipartUploadRequest(); + Assert.IsNotNull(mappedRequest, "Mapped request should not be null"); + + // Verify all mapped properties using reflection + var targetType = typeof(InitiateMultipartUploadRequest); + var failedAssertions = new List(); + + foreach (var propertyName in createMultipartRequestMappings) + { + // Resolve alias to actual property name for reflection lookups + var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); + var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); + var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); + + object sourceValue = null; + + if (sourceProperty != null) + { + // Property found directly on source type + sourceValue = sourceProperty.GetValue(sourceRequest); + } + else + { + // Check if source type has a Headers property of type HeadersCollection + var sourceHeadersProperty = sourceType.GetProperty("Headers"); + if (sourceHeadersProperty != null && typeof(HeadersCollection).IsAssignableFrom(sourceHeadersProperty.PropertyType)) + { + var sourceHeadersCollection = sourceHeadersProperty.GetValue(sourceRequest) as HeadersCollection; + if (sourceHeadersCollection != null) + { + var sourceHeadersCollectionProperty = typeof(HeadersCollection).GetProperty(resolvedSourcePropertyName); + if (sourceHeadersCollectionProperty != null) + { + sourceValue = sourceHeadersCollectionProperty.GetValue(sourceHeadersCollection); + } + else + { + failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name} or HeadersCollection"); + continue; + } + } + else + { + failedAssertions.Add($"Source Headers collection is null in {sourceType.Name}"); + continue; + } + } + else + { + failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); + continue; + } + } + + object targetValue = null; + + if (targetProperty != null) + { + // Property found directly on target type + targetValue = targetProperty.GetValue(mappedRequest); + } + else + { + // Check if target type has a Headers property of type HeadersCollection + var headersProperty = targetType.GetProperty("Headers"); + if (headersProperty != null && typeof(HeadersCollection).IsAssignableFrom(headersProperty.PropertyType)) + { + var headersCollection = headersProperty.GetValue(mappedRequest) as HeadersCollection; + if (headersCollection != null) + { + var headersCollectionProperty = typeof(HeadersCollection).GetProperty(resolvedTargetPropertyName); + if (headersCollectionProperty != null) + { + targetValue = headersCollectionProperty.GetValue(headersCollection); + } + else + { + failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name} or HeadersCollection"); + continue; + } + } + else + { + failedAssertions.Add($"Headers collection is null in {targetType.Name}"); + continue; + } + } + else + { + failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); + continue; + } + } + + // Special handling for complex object comparisons + if (!AreValuesEqual(sourceValue, targetValue)) + { + failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); + } + } + + // Report any failures + if (failedAssertions.Any()) + { + Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); + } + } + + [TestMethod] + [TestCategory("S3")] + public void MapUploadRequest_UploadPartRequest_AllMappedProperties_WorkCorrectly() + { + // Get the expected mappings from JSON + var uploadPartRequestMappings = _mappingJson.RootElement + .GetProperty("Conversion") + .GetProperty("UploadRequest") + .GetProperty("UploadPartRequest") + .EnumerateArray() + .Select(prop => prop.GetString()) + .ToList(); + + // Create source object with dynamically generated test data + var sourceRequest = new TransferUtilityUploadRequest(); + var sourceType = typeof(TransferUtilityUploadRequest); + var testDataValues = new Dictionary(); + + // Generate test data for each mapped property + foreach (var propertyName in uploadPartRequestMappings) + { + // Resolve alias to actual property name + var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var sourceProperty = sourceType.GetProperty(resolvedPropertyName); + if (sourceProperty?.CanWrite == true) + { + var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); + sourceProperty.SetValue(sourceRequest, testValue); + testDataValues[propertyName] = testValue; + } + } + + // Add inherited properties for comprehensive testing + sourceRequest.InputStream = new MemoryStream(1024); + + var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); + + // Map the response + var initiateResponse = new InitiateMultipartUploadResponse + { + UploadId = "test-upload-id" + }; + var mappedRequest = multipartUploadCommand.ConstructUploadPartRequest(1, 1024, initiateResponse); + Assert.IsNotNull(mappedRequest, "Mapped request should not be null"); + + // Verify all mapped properties using reflection + var targetType = typeof(UploadPartRequest); + var failedAssertions = new List(); + + foreach (var propertyName in uploadPartRequestMappings) + { + // Resolve alias to actual property name for reflection lookups + var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); + var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); + var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); + + if (sourceProperty == null) + { + failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); + continue; + } + + if (targetProperty == null) + { + failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); + continue; + } + + var sourceValue = sourceProperty.GetValue(sourceRequest); + var targetValue = targetProperty.GetValue(mappedRequest); + + // Special handling for complex object comparisons + if (!AreValuesEqual(sourceValue, targetValue)) + { + failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); + } + } + + // Report any failures + if (failedAssertions.Any()) + { + Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); + } + } + + [TestMethod] + [TestCategory("S3")] + public void MapUploadRequest_CompleteMultipartRequest_AllMappedProperties_WorkCorrectly() + { + // Get the expected mappings from JSON + var completeMultipartRequestMappings = _mappingJson.RootElement + .GetProperty("Conversion") + .GetProperty("UploadRequest") + .GetProperty("CompleteMultipartRequest") + .EnumerateArray() + .Select(prop => prop.GetString()) + .ToList(); + + // Create source object with dynamically generated test data + var sourceRequest = new TransferUtilityUploadRequest(); + var sourceType = typeof(TransferUtilityUploadRequest); + var testDataValues = new Dictionary(); + + // Generate test data for each mapped property + foreach (var propertyName in completeMultipartRequestMappings) + { + // Resolve alias to actual property name + var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var sourceProperty = sourceType.GetProperty(resolvedPropertyName); + if (sourceProperty?.CanWrite == true) + { + var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); + sourceProperty.SetValue(sourceRequest, testValue); + testDataValues[propertyName] = testValue; + } + } + + // Add inherited properties for comprehensive testing + sourceRequest.InputStream = new MemoryStream(1024); + + sourceRequest.ServerSideEncryptionCustomerMethod = ServerSideEncryptionCustomerMethod.AES256; + + var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); + + // Map the response + var initiateResponse = new InitiateMultipartUploadResponse + { + UploadId = "test-upload-id", + ChecksumType = ChecksumType.FULL_OBJECT + }; + var mappedRequest = multipartUploadCommand.ConstructCompleteMultipartUploadRequest(initiateResponse); + Assert.IsNotNull(mappedRequest, "Mapped request should not be null"); + + // Verify all mapped properties using reflection + var targetType = typeof(CompleteMultipartUploadRequest); + var failedAssertions = new List(); + + foreach (var propertyName in completeMultipartRequestMappings) + { + // Resolve alias to actual property name for reflection lookups + var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); + var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); + var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); + + if (sourceProperty == null) + { + failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); + continue; + } + + if (targetProperty == null) + { + failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); + continue; + } + + var sourceValue = sourceProperty.GetValue(sourceRequest); + var targetValue = targetProperty.GetValue(mappedRequest); + + // Special handling for complex object comparisons + if (!AreValuesEqual(sourceValue, targetValue)) + { + failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); + } + } + + // Report any failures + if (failedAssertions.Any()) + { + Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); + } + } + + [TestMethod] + [TestCategory("S3")] + public void MapUploadRequest_AbortMultipartRequest_AllMappedProperties_WorkCorrectly() + { + // Get the expected mappings from JSON + var abortMultipartRequestMappings = _mappingJson.RootElement + .GetProperty("Conversion") + .GetProperty("UploadRequest") + .GetProperty("AbortMultipartRequest") + .EnumerateArray() + .Select(prop => prop.GetString()) + .ToList(); + + // Create source object with dynamically generated test data + var sourceRequest = new TransferUtilityUploadRequest(); + var sourceType = typeof(TransferUtilityUploadRequest); + var testDataValues = new Dictionary(); + + // Generate test data for each mapped property + foreach (var propertyName in abortMultipartRequestMappings) + { + // Resolve alias to actual property name + var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var sourceProperty = sourceType.GetProperty(resolvedPropertyName); + if (sourceProperty?.CanWrite == true) + { + var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); + sourceProperty.SetValue(sourceRequest, testValue); + testDataValues[propertyName] = testValue; + } + } + + // Add inherited properties for comprehensive testing + sourceRequest.InputStream = new MemoryStream(1024); + + sourceRequest.ServerSideEncryptionCustomerMethod = ServerSideEncryptionCustomerMethod.AES256; + + var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); + + // Map the response + var mappedRequest = multipartUploadCommand.ConstructAbortMultipartUploadRequest("test-upload-id"); + Assert.IsNotNull(mappedRequest, "Mapped request should not be null"); + + // Verify all mapped properties using reflection + var targetType = typeof(AbortMultipartUploadRequest); + var failedAssertions = new List(); + + foreach (var propertyName in abortMultipartRequestMappings) + { + // Resolve alias to actual property name for reflection lookups + var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); + var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); + var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); + var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); + + if (sourceProperty == null) + { + failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); + continue; + } + + if (targetProperty == null) + { + failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); + continue; + } + + var sourceValue = sourceProperty.GetValue(sourceRequest); + var targetValue = targetProperty.GetValue(mappedRequest); + + // Special handling for complex object comparisons + if (!AreValuesEqual(sourceValue, targetValue)) + { + failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); + } + } + + // Report any failures + if (failedAssertions.Any()) + { + Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); + } + } + [TestMethod] [TestCategory("S3")] public void MapPutObjectResponse_NullValues_HandledCorrectly() @@ -253,35 +745,74 @@ public void ValidateCompleteMultipartUploadResponseConversionCompleteness() new[] { "Conversion", "CompleteMultipartResponse", "UploadResponse" }, "TransferUtilityUploadResponse"); } - - // Uncomment for DOTNET-8277 - - // [TestMethod] - // [TestCategory("S3")] - // public void ValidatePutObjectRequestDefinitionCompleteness() - // { - // ValidateResponseDefinitionCompleteness( - // new[] { "Definition", "UploadRequest", "PutObjectRequest" }, - // "PutObjectRequest"); - // } - - // [TestMethod] - // [TestCategory("S3")] - // public void ValidateGetObjectRequestDefinitionCompleteness() - // { - // ValidateResponseDefinitionCompleteness( - // new[] { "Definition", "DownloadRequest", "GetObjectRequest" }, - // "GetObjectRequest"); - // } - - // [TestMethod] - // [TestCategory("S3")] - // public void ValidateGetObjectRequestDefinitionCompleteness() - // { - // ValidateResponseDefinitionCompleteness( - // new[] { "Definition", "DownloadRequest", "GetObjectRequest" }, - // "TransferUtilityDownloadRequest"); - // } + + [TestMethod] + [TestCategory("S3")] + public void ValidatePutObjectRequestDefinitionCompleteness() + { + ValidateResponseDefinitionCompleteness( + new[] { "Definition", "UploadRequest", "PutObjectRequest" }, + "PutObjectRequest", + () => + { + return typeof(HeadersCollection) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.CanRead) + .Select(p => p.Name) + .ToList(); + }); + } + + [TestMethod] + [TestCategory("S3")] + public void ValidateGetObjectRequestDefinitionCompleteness() + { + ValidateResponseDefinitionCompleteness( + new[] { "Definition", "DownloadRequest", "GetObjectRequest" }, + "GetObjectRequest", + () => + { + return typeof(ResponseHeaderOverrides) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.CanRead) + .Select(p => p.Name) + .ToList(); + }); + } + + [TestMethod] + [TestCategory("S3")] + public void ValidateTransferUtilityDownloadRequestDefinitionCompleteness() + { + ValidateResponseDefinitionCompleteness( + new[] { "Definition", "DownloadRequest", "GetObjectRequest" }, + "TransferUtilityDownloadRequest", + () => + { + return typeof(ResponseHeaderOverrides) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.CanRead) + .Select(p => p.Name) + .ToList(); + }); + } + + [TestMethod] + [TestCategory("S3")] + public void ValidateTransferUtilityUploadRequestDefinitionCompleteness() + { + ValidateResponseDefinitionCompleteness( + new[] { "Definition", "UploadRequest", "PutObjectRequest" }, + "TransferUtilityUploadRequest", + () => + { + return typeof(HeadersCollection) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.CanRead) + .Select(p => p.Name) + .ToList(); + }); + } /// /// Generates appropriate test data for a given property type @@ -344,6 +875,36 @@ private static object GenerateTestValue(Type propertyType, string propertyName) return 1024; } + if (propertyType == typeof(List)) + { + if (_s3Grants is null) + { + _s3Grants = new List { new S3Grant { Grantee = new S3Grantee { DisplayName = "test-s3grantee"} } }; + } + + return _s3Grants; + } + + if (propertyType == typeof(MetadataCollection)) + { + var metadataCollection = new MetadataCollection(); + metadataCollection.Add("x-amz-meta-testkey", "testvalue"); + return metadataCollection; + } + + if (propertyType == typeof(DateTime)) + { + return DateTime.UtcNow; + } + + if (propertyType == typeof(List)) + { + return new List + { + new Tag { Key = "test-key", Value = "test-value" } + }; + } + // For unknown types, throw an exception instead of returning null // If we've reached this point it means there is an unhandled scenario/missing mapping in our test code that we need to handle. throw new NotSupportedException( @@ -378,14 +939,17 @@ private static bool AreValuesEqual(object sourceValue, object targetValue) /// /// Resolves a property name to its actual class property name, checking aliases if needed /// - private static string ResolvePropertyName(string propertyName) + private static string ResolvePropertyName(string propertyName, string responseTypeName) { - // Check if there's an alias for this property name - if (_propertyAliases.TryGetValue(propertyName, out var aliasedName)) + if (_propertyAliases.TryGetValue(responseTypeName, out var objectAliases)) { - return aliasedName; + // Check if there's an alias for this property name + if (objectAliases.TryGetValue(propertyName, out var aliasedName)) + { + return aliasedName; + } } - + // Return the original name if no alias exists return propertyName; } @@ -434,7 +998,7 @@ private static void ValidateResponseDefinitionCompleteness( foreach (var definitionProperty in definitionProperties) { - var resolvedPropertyName = ResolvePropertyName(definitionProperty); + var resolvedPropertyName = ResolvePropertyName(definitionProperty, responseTypeName); // Check if the resolved property name exists in the actual class if (!actualProperties.Contains(resolvedPropertyName)) From 7b33bd66b72b4482e80c9c02842053526fed480b Mon Sep 17 00:00:00 2001 From: Phil Asmar Date: Tue, 21 Oct 2025 17:14:35 -0400 Subject: [PATCH 2/2] respond to feedback --- .../9d07dc1e-d82d-4f94-8700-c7b57f87205d.json | 2 +- .../Internal/MultipartUploadCommand.cs | 1 - .../UnitTests/Custom/ResponseMapperTests.cs | 589 +++++------------- 3 files changed, 156 insertions(+), 436 deletions(-) diff --git a/generator/.DevConfigs/9d07dc1e-d82d-4f94-8700-c7b57f87205d.json b/generator/.DevConfigs/9d07dc1e-d82d-4f94-8700-c7b57f87205d.json index 52e892ab03ab..9b7c13a4f5ab 100644 --- a/generator/.DevConfigs/9d07dc1e-d82d-4f94-8700-c7b57f87205d.json +++ b/generator/.DevConfigs/9d07dc1e-d82d-4f94-8700-c7b57f87205d.json @@ -4,7 +4,7 @@ "serviceName": "S3", "type": "patch", "changeLogMessages": [ - "Add missing fields to Transfer Utility request objects" + "Add missing fields to Transfer Utility request objects. ContentType on TransferUtilityUploadRequest and TransferUtilityUploadDirectoryRequest now directly updates the ContentType header, instead of being a separate field on those objects." ] } ] diff --git a/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartUploadCommand.cs b/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartUploadCommand.cs index de6ae0cbf813..644500df5bff 100644 --- a/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartUploadCommand.cs +++ b/sdk/src/Services/S3/Custom/Transfer/Internal/MultipartUploadCommand.cs @@ -299,7 +299,6 @@ internal InitiateMultipartUploadRequest ConstructInitiateMultipartUploadRequest( BucketName = this._fileTransporterRequest.BucketName, Key = this._fileTransporterRequest.Key, CannedACL = this._fileTransporterRequest.CannedACL, - Headers = this._fileTransporterRequest.Headers, ContentType = determineContentType(), StorageClass = this._fileTransporterRequest.StorageClass, ServerSideEncryptionMethod = this._fileTransporterRequest.ServerSideEncryptionMethod, diff --git a/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs b/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs index bb828dc7a446..b9a89d63ba70 100644 --- a/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs +++ b/sdk/test/Services/S3/UnitTests/Custom/ResponseMapperTests.cs @@ -135,186 +135,182 @@ public static void ClassCleanup() [TestCategory("S3")] public void MapPutObjectResponse_AllMappedProperties_WorkCorrectly() { - // Get the expected mappings from JSON - var putObjectMappings = _mappingJson.RootElement - .GetProperty("Conversion") - .GetProperty("PutObjectResponse") - .GetProperty("UploadResponse") - .EnumerateArray() - .Select(prop => prop.GetString()) - .ToList(); + ValidateMappingTransferUtilityAndSdkRequests( + new[] { "Conversion", "PutObjectResponse", "UploadResponse" }, + (sourceResponse) => + { + return ResponseMapper.MapPutObjectResponse(sourceResponse); + }, + usesHeadersCollection: false, + (sourceResponse) => + { + sourceResponse.HttpStatusCode = HttpStatusCode.OK; + sourceResponse.ContentLength = 1024; + }, + (sourceResponse, targetResponse) => + { + Assert.AreEqual(sourceResponse.HttpStatusCode, targetResponse.HttpStatusCode, "HttpStatusCode should match"); + Assert.AreEqual(sourceResponse.ContentLength, targetResponse.ContentLength, "ContentLength should match"); + }); + } - // Create source object with dynamically generated test data - var sourceResponse = new PutObjectResponse(); - var sourceType = typeof(PutObjectResponse); - var testDataValues = new Dictionary(); + [TestMethod] + [TestCategory("S3")] + public void MapUploadRequest_PutObjectRequest_AllMappedProperties_WorkCorrectly() + { + ValidateMappingTransferUtilityAndSdkRequests( + new[] { "Conversion", "UploadRequest", "PutObjectRequest" }, + (sourceRequest) => + { + var simpleUploadCommand = new SimpleUploadCommand(null, null, sourceRequest); + return simpleUploadCommand.ConstructRequest(); + }, + usesHeadersCollection: false); + } - // Generate test data for each mapped property - foreach (var propertyName in putObjectMappings) - { - // Resolve alias to actual property name - var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); - var sourceProperty = sourceType.GetProperty(resolvedPropertyName); - if (sourceProperty?.CanWrite == true) + [TestMethod] + [TestCategory("S3")] + public void MapUploadRequest_CreateMultipartRequest_AllMappedProperties_WorkCorrectly() + { + ValidateMappingTransferUtilityAndSdkRequests( + new[] { "Conversion", "UploadRequest", "CreateMultipartRequest" }, + (sourceRequest) => { - var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); - sourceProperty.SetValue(sourceResponse, testValue); - testDataValues[propertyName] = testValue; - } - } - - // Add inherited properties for comprehensive testing - sourceResponse.HttpStatusCode = HttpStatusCode.OK; - sourceResponse.ContentLength = 1024; - - // Map the response - var mappedResponse = ResponseMapper.MapPutObjectResponse(sourceResponse); - Assert.IsNotNull(mappedResponse, "Mapped response should not be null"); - - // Verify all mapped properties using reflection - var targetType = typeof(TransferUtilityUploadResponse); - var failedAssertions = new List(); - - foreach (var propertyName in putObjectMappings) - { - // Resolve alias to actual property name for reflection lookups - var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); - var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); - var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); - var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); - - if (sourceProperty == null) + var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); + return multipartUploadCommand.ConstructInitiateMultipartUploadRequest(); + }, + usesHeadersCollection: true, + (sourceRequest) => { - failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); - continue; - } + sourceRequest.InputStream = new MemoryStream(1024); + }); + } - if (targetProperty == null) + [TestMethod] + [TestCategory("S3")] + public void MapUploadRequest_UploadPartRequest_AllMappedProperties_WorkCorrectly() + { + ValidateMappingTransferUtilityAndSdkRequests( + new[] { "Conversion", "UploadRequest", "UploadPartRequest" }, + (sourceRequest) => { - failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); - continue; - } + var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); - var sourceValue = sourceProperty.GetValue(sourceResponse); - var targetValue = targetProperty.GetValue(mappedResponse); + var initiateResponse = new InitiateMultipartUploadResponse + { + UploadId = "test-upload-id" + }; - // Special handling for complex object comparisons - if (!AreValuesEqual(sourceValue, targetValue)) + return multipartUploadCommand.ConstructUploadPartRequest(1, 1024, initiateResponse); + }, + usesHeadersCollection: false, + (sourceRequest) => { - failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); - } - } - - // Test inherited properties - Assert.AreEqual(sourceResponse.HttpStatusCode, mappedResponse.HttpStatusCode, "HttpStatusCode should match"); - Assert.AreEqual(sourceResponse.ContentLength, mappedResponse.ContentLength, "ContentLength should match"); - - // Report any failures - if (failedAssertions.Any()) - { - Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); - } + sourceRequest.InputStream = new MemoryStream(1024); + }); } [TestMethod] [TestCategory("S3")] - public void MapUploadRequest_PutObjectRequest_AllMappedProperties_WorkCorrectly() + public void MapUploadRequest_CompleteMultipartRequest_AllMappedProperties_WorkCorrectly() { - // Get the expected mappings from JSON - var putObjectMappings = _mappingJson.RootElement - .GetProperty("Conversion") - .GetProperty("UploadRequest") - .GetProperty("PutObjectRequest") - .EnumerateArray() - .Select(prop => prop.GetString()) - .ToList(); - - // Create source object with dynamically generated test data - var sourceRequest = new TransferUtilityUploadRequest(); - var sourceType = typeof(TransferUtilityUploadRequest); - var testDataValues = new Dictionary(); - - // Generate test data for each mapped property - foreach (var propertyName in putObjectMappings) - { - // Resolve alias to actual property name - var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); - var sourceProperty = sourceType.GetProperty(resolvedPropertyName); - if (sourceProperty?.CanWrite == true) + ValidateMappingTransferUtilityAndSdkRequests( + new[] { "Conversion", "UploadRequest", "CompleteMultipartRequest" }, + (sourceRequest) => { - var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); - sourceProperty.SetValue(sourceRequest, testValue); - testDataValues[propertyName] = testValue; - } - } - - var simpleUploadCommand = new SimpleUploadCommand(null, null, sourceRequest); - - // Map the response - var mappedRequest = simpleUploadCommand.ConstructRequest(); - Assert.IsNotNull(mappedRequest, "Mapped request should not be null"); + var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); - // Verify all mapped properties using reflection - var targetType = typeof(PutObjectRequest); - var failedAssertions = new List(); + var initiateResponse = new InitiateMultipartUploadResponse + { + UploadId = "test-upload-id", + ChecksumType = ChecksumType.FULL_OBJECT + }; - foreach (var propertyName in putObjectMappings) - { - // Resolve alias to actual property name for reflection lookups - var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); - var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); - var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); - var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); + return multipartUploadCommand.ConstructCompleteMultipartUploadRequest(initiateResponse); + }, + usesHeadersCollection: false, + (sourceRequest) => + { + sourceRequest.InputStream = new MemoryStream(1024); + sourceRequest.ServerSideEncryptionCustomerMethod = ServerSideEncryptionCustomerMethod.AES256; + }); + } - if (sourceProperty == null) + [TestMethod] + [TestCategory("S3")] + public void MapUploadRequest_AbortMultipartRequest_AllMappedProperties_WorkCorrectly() + { + ValidateMappingTransferUtilityAndSdkRequests( + new[] { "Conversion", "UploadRequest", "AbortMultipartRequest" }, + (sourceRequest) => { - failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); - continue; - } + var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); - if (targetProperty == null) + return multipartUploadCommand.ConstructAbortMultipartUploadRequest("test-upload-id"); + }, + usesHeadersCollection: false, + (sourceRequest) => { - failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); - continue; - } + sourceRequest.InputStream = new MemoryStream(1024); + sourceRequest.ServerSideEncryptionCustomerMethod = ServerSideEncryptionCustomerMethod.AES256; + }); + } - var sourceValue = sourceProperty.GetValue(sourceRequest); - var targetValue = targetProperty.GetValue(mappedRequest); + [TestMethod] + [TestCategory("S3")] + public void MapPutObjectResponse_NullValues_HandledCorrectly() + { + // Test null handling scenarios + var testCases = new[] + { + // Test null Expiration + new PutObjectResponse { Expiration = null }, + + // Test null enum conversions + new PutObjectResponse { ChecksumType = null, RequestCharged = null, ServerSideEncryptionMethod = null } + }; - // Special handling for complex object comparisons - if (!AreValuesEqual(sourceValue, targetValue)) + foreach (var testCase in testCases) + { + var mapped = ResponseMapper.MapPutObjectResponse(testCase); + Assert.IsNotNull(mapped, "Response should always be mappable"); + + // Test null handling + if (testCase.Expiration == null) { - failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); + Assert.IsNull(mapped.Expiration, "Null Expiration should map to null"); } } + } - // Report any failures - if (failedAssertions.Any()) + private void ValidateMappingTransferUtilityAndSdkRequests( + string[] mappingPath, + Func fetchTargetRequest, + bool usesHeadersCollection = false, + Action requestHook = null, + Action additionalValidations = null) + { + // Get the expected mappings from JSON + JsonElement mappingElement = _mappingJson.RootElement; + + foreach (var path in mappingPath) { - Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); + mappingElement = mappingElement.GetProperty(path); } - } - [TestMethod] - [TestCategory("S3")] - public void MapUploadRequest_CreateMultipartRequest_AllMappedProperties_WorkCorrectly() - { // Get the expected mappings from JSON - var createMultipartRequestMappings = _mappingJson.RootElement - .GetProperty("Conversion") - .GetProperty("UploadRequest") - .GetProperty("CreateMultipartRequest") + var requestMappings = mappingElement .EnumerateArray() .Select(prop => prop.GetString()) .ToList(); // Create source object with dynamically generated test data - var sourceRequest = new TransferUtilityUploadRequest(); - var sourceType = typeof(TransferUtilityUploadRequest); + var sourceRequest = Activator.CreateInstance(); + var sourceType = typeof(TSourceRequest); var testDataValues = new Dictionary(); // Generate test data for each mapped property - foreach (var propertyName in createMultipartRequestMappings) + foreach (var propertyName in requestMappings) { // Resolve alias to actual property name var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); @@ -327,20 +323,17 @@ public void MapUploadRequest_CreateMultipartRequest_AllMappedProperties_WorkCorr } } - // Add inherited properties for comprehensive testing - sourceRequest.InputStream = new MemoryStream(1024); - - var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); + requestHook?.Invoke(sourceRequest); // Map the response - var mappedRequest = multipartUploadCommand.ConstructInitiateMultipartUploadRequest(); + var mappedRequest = fetchTargetRequest(sourceRequest); Assert.IsNotNull(mappedRequest, "Mapped request should not be null"); - + // Verify all mapped properties using reflection - var targetType = typeof(InitiateMultipartUploadRequest); + var targetType = typeof(TTargetRequest); var failedAssertions = new List(); - foreach (var propertyName in createMultipartRequestMappings) + foreach (var propertyName in requestMappings) { // Resolve alias to actual property name for reflection lookups var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); @@ -357,6 +350,12 @@ public void MapUploadRequest_CreateMultipartRequest_AllMappedProperties_WorkCorr } else { + if (!usesHeadersCollection) + { + failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); + continue; + } + // Check if source type has a Headers property of type HeadersCollection var sourceHeadersProperty = sourceType.GetProperty("Headers"); if (sourceHeadersProperty != null && typeof(HeadersCollection).IsAssignableFrom(sourceHeadersProperty.PropertyType)) @@ -397,6 +396,12 @@ public void MapUploadRequest_CreateMultipartRequest_AllMappedProperties_WorkCorr } else { + if (!usesHeadersCollection) + { + failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); + continue; + } + // Check if target type has a Headers property of type HeadersCollection var headersProperty = targetType.GetProperty("Headers"); if (headersProperty != null && typeof(HeadersCollection).IsAssignableFrom(headersProperty.PropertyType)) @@ -435,264 +440,7 @@ public void MapUploadRequest_CreateMultipartRequest_AllMappedProperties_WorkCorr } } - // Report any failures - if (failedAssertions.Any()) - { - Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); - } - } - - [TestMethod] - [TestCategory("S3")] - public void MapUploadRequest_UploadPartRequest_AllMappedProperties_WorkCorrectly() - { - // Get the expected mappings from JSON - var uploadPartRequestMappings = _mappingJson.RootElement - .GetProperty("Conversion") - .GetProperty("UploadRequest") - .GetProperty("UploadPartRequest") - .EnumerateArray() - .Select(prop => prop.GetString()) - .ToList(); - - // Create source object with dynamically generated test data - var sourceRequest = new TransferUtilityUploadRequest(); - var sourceType = typeof(TransferUtilityUploadRequest); - var testDataValues = new Dictionary(); - - // Generate test data for each mapped property - foreach (var propertyName in uploadPartRequestMappings) - { - // Resolve alias to actual property name - var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); - var sourceProperty = sourceType.GetProperty(resolvedPropertyName); - if (sourceProperty?.CanWrite == true) - { - var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); - sourceProperty.SetValue(sourceRequest, testValue); - testDataValues[propertyName] = testValue; - } - } - - // Add inherited properties for comprehensive testing - sourceRequest.InputStream = new MemoryStream(1024); - - var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); - - // Map the response - var initiateResponse = new InitiateMultipartUploadResponse - { - UploadId = "test-upload-id" - }; - var mappedRequest = multipartUploadCommand.ConstructUploadPartRequest(1, 1024, initiateResponse); - Assert.IsNotNull(mappedRequest, "Mapped request should not be null"); - - // Verify all mapped properties using reflection - var targetType = typeof(UploadPartRequest); - var failedAssertions = new List(); - - foreach (var propertyName in uploadPartRequestMappings) - { - // Resolve alias to actual property name for reflection lookups - var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); - var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); - var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); - var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); - - if (sourceProperty == null) - { - failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); - continue; - } - - if (targetProperty == null) - { - failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); - continue; - } - - var sourceValue = sourceProperty.GetValue(sourceRequest); - var targetValue = targetProperty.GetValue(mappedRequest); - - // Special handling for complex object comparisons - if (!AreValuesEqual(sourceValue, targetValue)) - { - failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); - } - } - - // Report any failures - if (failedAssertions.Any()) - { - Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); - } - } - - [TestMethod] - [TestCategory("S3")] - public void MapUploadRequest_CompleteMultipartRequest_AllMappedProperties_WorkCorrectly() - { - // Get the expected mappings from JSON - var completeMultipartRequestMappings = _mappingJson.RootElement - .GetProperty("Conversion") - .GetProperty("UploadRequest") - .GetProperty("CompleteMultipartRequest") - .EnumerateArray() - .Select(prop => prop.GetString()) - .ToList(); - - // Create source object with dynamically generated test data - var sourceRequest = new TransferUtilityUploadRequest(); - var sourceType = typeof(TransferUtilityUploadRequest); - var testDataValues = new Dictionary(); - - // Generate test data for each mapped property - foreach (var propertyName in completeMultipartRequestMappings) - { - // Resolve alias to actual property name - var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); - var sourceProperty = sourceType.GetProperty(resolvedPropertyName); - if (sourceProperty?.CanWrite == true) - { - var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); - sourceProperty.SetValue(sourceRequest, testValue); - testDataValues[propertyName] = testValue; - } - } - - // Add inherited properties for comprehensive testing - sourceRequest.InputStream = new MemoryStream(1024); - - sourceRequest.ServerSideEncryptionCustomerMethod = ServerSideEncryptionCustomerMethod.AES256; - - var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); - - // Map the response - var initiateResponse = new InitiateMultipartUploadResponse - { - UploadId = "test-upload-id", - ChecksumType = ChecksumType.FULL_OBJECT - }; - var mappedRequest = multipartUploadCommand.ConstructCompleteMultipartUploadRequest(initiateResponse); - Assert.IsNotNull(mappedRequest, "Mapped request should not be null"); - - // Verify all mapped properties using reflection - var targetType = typeof(CompleteMultipartUploadRequest); - var failedAssertions = new List(); - - foreach (var propertyName in completeMultipartRequestMappings) - { - // Resolve alias to actual property name for reflection lookups - var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); - var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); - var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); - var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); - - if (sourceProperty == null) - { - failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); - continue; - } - - if (targetProperty == null) - { - failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); - continue; - } - - var sourceValue = sourceProperty.GetValue(sourceRequest); - var targetValue = targetProperty.GetValue(mappedRequest); - - // Special handling for complex object comparisons - if (!AreValuesEqual(sourceValue, targetValue)) - { - failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); - } - } - - // Report any failures - if (failedAssertions.Any()) - { - Assert.Fail($"Property mapping failures:\n{string.Join("\n", failedAssertions)}"); - } - } - - [TestMethod] - [TestCategory("S3")] - public void MapUploadRequest_AbortMultipartRequest_AllMappedProperties_WorkCorrectly() - { - // Get the expected mappings from JSON - var abortMultipartRequestMappings = _mappingJson.RootElement - .GetProperty("Conversion") - .GetProperty("UploadRequest") - .GetProperty("AbortMultipartRequest") - .EnumerateArray() - .Select(prop => prop.GetString()) - .ToList(); - - // Create source object with dynamically generated test data - var sourceRequest = new TransferUtilityUploadRequest(); - var sourceType = typeof(TransferUtilityUploadRequest); - var testDataValues = new Dictionary(); - - // Generate test data for each mapped property - foreach (var propertyName in abortMultipartRequestMappings) - { - // Resolve alias to actual property name - var resolvedPropertyName = ResolvePropertyName(propertyName, sourceType.Name); - var sourceProperty = sourceType.GetProperty(resolvedPropertyName); - if (sourceProperty?.CanWrite == true) - { - var testValue = GenerateTestValue(sourceProperty.PropertyType, propertyName); - sourceProperty.SetValue(sourceRequest, testValue); - testDataValues[propertyName] = testValue; - } - } - - // Add inherited properties for comprehensive testing - sourceRequest.InputStream = new MemoryStream(1024); - - sourceRequest.ServerSideEncryptionCustomerMethod = ServerSideEncryptionCustomerMethod.AES256; - - var multipartUploadCommand = new MultipartUploadCommand(null, null, sourceRequest); - - // Map the response - var mappedRequest = multipartUploadCommand.ConstructAbortMultipartUploadRequest("test-upload-id"); - Assert.IsNotNull(mappedRequest, "Mapped request should not be null"); - - // Verify all mapped properties using reflection - var targetType = typeof(AbortMultipartUploadRequest); - var failedAssertions = new List(); - - foreach (var propertyName in abortMultipartRequestMappings) - { - // Resolve alias to actual property name for reflection lookups - var resolvedSourcePropertyName = ResolvePropertyName(propertyName, sourceType.Name); - var resolvedTargetPropertyName = ResolvePropertyName(propertyName, targetType.Name); - var sourceProperty = sourceType.GetProperty(resolvedSourcePropertyName); - var targetProperty = targetType.GetProperty(resolvedTargetPropertyName); - - if (sourceProperty == null) - { - failedAssertions.Add($"Source property '{propertyName}' (resolved to: {resolvedSourcePropertyName}) not found in {sourceType.Name}"); - continue; - } - - if (targetProperty == null) - { - failedAssertions.Add($"Target property '{propertyName}' (resolved to: {resolvedTargetPropertyName}) not found in {targetType.Name}"); - continue; - } - - var sourceValue = sourceProperty.GetValue(sourceRequest); - var targetValue = targetProperty.GetValue(mappedRequest); - - // Special handling for complex object comparisons - if (!AreValuesEqual(sourceValue, targetValue)) - { - failedAssertions.Add($"{propertyName}: Expected '{sourceValue ?? "null"}', got '{targetValue ?? "null"}'"); - } - } + additionalValidations?.Invoke(sourceRequest, mappedRequest); // Report any failures if (failedAssertions.Any()) @@ -701,33 +449,6 @@ public void MapUploadRequest_AbortMultipartRequest_AllMappedProperties_WorkCorre } } - [TestMethod] - [TestCategory("S3")] - public void MapPutObjectResponse_NullValues_HandledCorrectly() - { - // Test null handling scenarios - var testCases = new[] - { - // Test null Expiration - new PutObjectResponse { Expiration = null }, - - // Test null enum conversions - new PutObjectResponse { ChecksumType = null, RequestCharged = null, ServerSideEncryptionMethod = null } - }; - - foreach (var testCase in testCases) - { - var mapped = ResponseMapper.MapPutObjectResponse(testCase); - Assert.IsNotNull(mapped, "Response should always be mappable"); - - // Test null handling - if (testCase.Expiration == null) - { - Assert.IsNull(mapped.Expiration, "Null Expiration should map to null"); - } - } - } - [TestMethod] [TestCategory("S3")] public void ValidateTransferUtilityUploadResponseDefinitionCompleteness()