diff --git a/Frends.AmazonS3.ListObjectVersions/Frends.AmazonS3.ListObjectVersions.Tests/ErrorHandlerTest.cs b/Frends.AmazonS3.ListObjectVersions/Frends.AmazonS3.ListObjectVersions.Tests/ErrorHandlerTest.cs index 83bade2..0b68d08 100644 --- a/Frends.AmazonS3.ListObjectVersions/Frends.AmazonS3.ListObjectVersions.Tests/ErrorHandlerTest.cs +++ b/Frends.AmazonS3.ListObjectVersions/Frends.AmazonS3.ListObjectVersions.Tests/ErrorHandlerTest.cs @@ -13,8 +13,8 @@ public class ErrorHandlerTest : TestBase [Test] public void Should_Throw_Error_When_ThrowErrorOnFailure_Is_True() { - var ex = Assert.ThrowsAsync(() => - AmazonS3.ListObjectVersions(DefaultInput(), DefaultConnection(), DefaultOptions(), CancellationToken.None)); + var ex = Assert.ThrowsAsync((Func)(() => + AmazonS3.ListObjectVersions(DefaultInput(), DefaultConnection(), DefaultOptions(), CancellationToken.None))); Assert.That(ex, Is.Not.Null); } @@ -33,8 +33,8 @@ public void Should_Use_Custom_ErrorMessageOnFailure() { var options = DefaultOptions(); options.ErrorMessageOnFailure = CustomErrorMessage; - var ex = Assert.ThrowsAsync(() => - AmazonS3.ListObjectVersions(DefaultInput(), DefaultConnection(), options, CancellationToken.None)); + var ex = Assert.ThrowsAsync((Func)(() => + AmazonS3.ListObjectVersions(DefaultInput(), DefaultConnection(), options, CancellationToken.None))); Assert.That(ex, Is.Not.Null); Assert.That(ex.Message, Contains.Substring(CustomErrorMessage)); } diff --git a/Frends.AmazonS3.ListObjects/CHANGELOG.md b/Frends.AmazonS3.ListObjects/CHANGELOG.md index 0d6a71a..96af826 100644 --- a/Frends.AmazonS3.ListObjects/CHANGELOG.md +++ b/Frends.AmazonS3.ListObjects/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [2.2.0] - 2026-06-16 +### Changed +- Updated AWSSDK.Core nuget package from 4.0.3.5 to 4.0.8 +- Updated AWSSDK.S3 nuget package from 4.0.14.2 to 4.0.24.1 +- Introduce more null checks to avoid unexpected exceptions + ## [2.1.0] - 2025-12-29 ### Changed - Updated AWS SDK version. diff --git a/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects.Test/UnitTests.cs b/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects.Test/UnitTests.cs index 59ea646..07c5c10 100644 --- a/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects.Test/UnitTests.cs +++ b/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects.Test/UnitTests.cs @@ -33,7 +33,7 @@ public class ListObjectsTest Connection? _connection = null; Input? _input = null; - Options? _options = null; + Options? _options; public ListObjectsTest() { diff --git a/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/Definitions/BucketObject.cs b/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/Definitions/BucketObject.cs index 3e8982b..e4288e6 100644 --- a/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/Definitions/BucketObject.cs +++ b/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/Definitions/BucketObject.cs @@ -35,7 +35,7 @@ public class BucketObject /// The date and time the object was last modified. /// /// 2022-04-22T00:16:40+02:00 - public DateTime LastModified { get; set; } + public DateTime? LastModified { get; set; } } } diff --git a/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects.csproj b/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects.csproj index d85600e..e4c6396 100644 --- a/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects.csproj +++ b/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects.csproj @@ -2,7 +2,7 @@ net8.0 - 2.1.0 + 2.2.0 Frends Frends Frends @@ -20,12 +20,12 @@ - - + + - \ No newline at end of file + diff --git a/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/ListObjects.cs b/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/ListObjects.cs index fd69e99..c891a7d 100644 --- a/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/ListObjects.cs +++ b/Frends.AmazonS3.ListObjects/Frends.AmazonS3.ListObjects/ListObjects.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Amazon; @@ -14,7 +15,7 @@ namespace Frends.AmazonS3.ListObjects /// /// Amazon S3 task. /// - public class AmazonS3 + public static class AmazonS3 { /// /// Lists objects from specified AWS S3 Bucket. @@ -25,18 +26,26 @@ public class AmazonS3 /// Options for the task /// Token to stop ListObjects. This is generated by Frends. /// Result { List (BucketObject) Objects, bool Success, Error Error } where BucketObject contains { string BucketName, string Key, string Etag, long Size, DateTime LastModified } - public static async Task ListObjects([PropertyTab] Connection connection, [PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken) + public static async Task ListObjects([PropertyTab] Connection connection, [PropertyTab] Input input, + [PropertyTab] Options options, CancellationToken cancellationToken) { try { - if (string.IsNullOrWhiteSpace(connection.AwsSecretAccessKey) || string.IsNullOrWhiteSpace(connection.AwsAccessKeyId)) + if (string.IsNullOrWhiteSpace(connection.AwsSecretAccessKey) || + string.IsNullOrWhiteSpace(connection.AwsAccessKeyId)) { return ErrorHandler.HandleCredentialsError("AWS credentials missing.", options); } + if (string.IsNullOrWhiteSpace(input.BucketName)) + { + return ErrorHandler.HandleCredentialsError("Bucket name is missing.", options); + } + var region = RegionSelection(connection.Region); var client = new AmazonS3Client(connection.AwsAccessKeyId, connection.AwsSecretAccessKey, region); var response = await ListBucketContentsAsync(client, input, options, cancellationToken); + return new Result(response); } catch (Exception ex) @@ -53,7 +62,8 @@ public static async Task ListObjects([PropertyTab] Connection connection /// Options for filtering and limiting results /// Token to cancel the operation /// A list of BucketObject instances representing the objects in the bucket - private static async Task> ListBucketContentsAsync(AmazonS3Client client, Input input, Options options, CancellationToken cancellationToken) + private static async Task> ListBucketContentsAsync(AmazonS3Client client, Input input, + Options options, CancellationToken cancellationToken) { var data = new List(); var request = GetListObjectsV2Request(input, options); @@ -62,6 +72,19 @@ private static async Task> ListBucketContentsAsync(AmazonS3Cl { var response = await client.ListObjectsV2Async(request, cancellationToken); + if (response is null) + throw new Exception("Received null response from S3 ListObjectsV2Async call."); + + if (response.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + throw new Exception($"Error listing objects: {response.HttpStatusCode}"); + } + + if (response.S3Objects is null) + { + throw new Exception("S3Objects property was null in S3 ListObjectsV2Async response."); + } + foreach (var item in response.S3Objects) { cancellationToken.ThrowIfCancellationRequested(); @@ -69,18 +92,20 @@ private static async Task> ListBucketContentsAsync(AmazonS3Cl { BucketName = item.BucketName, Key = item.Key, - Size = (long)item.Size, + Size = item.Size ?? 0, Etag = item.ETag, - LastModified = (DateTime)item.LastModified + LastModified = item.LastModified, }); } + if (response.IsTruncated is null) + throw new Exception("IsTruncated flag was not set in S3 ListObjectsV2Async response."); + if ((bool)response.IsTruncated) request.ContinuationToken = response.NextContinuationToken; else break; - } - while (true); + } while (true); return data; } @@ -112,6 +137,7 @@ private static ListObjectsV2Request GetListObjectsV2Request(Input input, Options /// /// The region enum value to convert /// The corresponding AWS RegionEndpoint instance + [ExcludeFromCodeCoverage] private static RegionEndpoint RegionSelection(Region region) { switch (region)