From 9772a373b92d81808118edbdbd653d301f4502ac Mon Sep 17 00:00:00 2001 From: ebozduman Date: Tue, 23 Jan 2024 21:32:37 -0800 Subject: [PATCH] fixes BucketExists' return value and StatObject's returned exceptions (#987) * fixes error messages for BucketExists and StatObject * lint changes * adds required format changes * for debugging workfloww only build failure * possible fix for build failure * another try for the timing issue * debug trial for the build timing issue * yet another try for the timing issue * more lint changes --------- Co-authored-by: Ersan Bozduman --- Minio.Functional.Tests/FunctionalTest.cs | 69 +++++++++------ .../Minio.Functional.Tests.csproj | 6 +- Minio/ApiEndpoints/BucketOperations.cs | 15 ++-- Minio/ApiEndpoints/ObjectOperations.cs | 14 ++- Minio/DataModel/Args/PutObjectArgs.cs | 2 +- .../DataModel/Response/CopyObjectResponse.cs | 7 +- Minio/Exceptions/MinioException.cs | 9 +- .../Exceptions/PreconditionFailedException.cs | 27 ++++++ Minio/Helper/Utils.cs | 32 ++++++- Minio/MinioClient.cs | 20 +++-- Minio/RequestExtensions.cs | 88 +++++++++++-------- 11 files changed, 181 insertions(+), 108 deletions(-) create mode 100644 Minio/Exceptions/PreconditionFailedException.cs diff --git a/Minio.Functional.Tests/FunctionalTest.cs b/Minio.Functional.Tests/FunctionalTest.cs index 6706180f0..91a697fae 100644 --- a/Minio.Functional.Tests/FunctionalTest.cs +++ b/Minio.Functional.Tests/FunctionalTest.cs @@ -364,8 +364,11 @@ internal static async Task BucketExists_Test(IMinioClient minio) try { - await minio.MakeBucketAsync(mbArgs).ConfigureAwait(false); + await minio.RemoveBucketAsync(rbArgs).ConfigureAwait(false); var found = await minio.BucketExistsAsync(beArgs).ConfigureAwait(false); + Assert.IsFalse(found); + await minio.MakeBucketAsync(mbArgs).ConfigureAwait(false); + found = await minio.BucketExistsAsync(beArgs).ConfigureAwait(false); Assert.IsTrue(found); new MintLogger(nameof(BucketExists_Test), bucketExistsSignature, "Tests whether BucketExists passes", TestStatus.PASS, DateTime.Now - startTime, args: args).Log(); @@ -1007,7 +1010,7 @@ internal static async Task PutObject_Tester(IMinioClient minio, filestream = bs.AsStream(); } - using (filestream) + await using (filestream.ConfigureAwait(false)) { var file_write_size = filestream.Length; var tempFileName = "tempfile-" + GetRandomName(); @@ -1020,7 +1023,7 @@ internal static async Task PutObject_Tester(IMinioClient minio, .WithProgress(progress) .WithContentType(contentType) .WithHeaders(metaData); - await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); + var statPutObj = await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); var statObjectArgs = new StatObjectArgs() .WithBucket(bucketName) @@ -1036,11 +1039,6 @@ internal static async Task PutObject_Tester(IMinioClient minio, Assert.IsNotNull(statObject.ContentType); Assert.IsTrue(statObject.ContentType.Equals(contentType, StringComparison.OrdinalIgnoreCase)); } - - var rmArgs = new RemoveObjectArgs() - .WithBucket(bucketName) - .WithObject(objectName); - await minio.RemoveObjectAsync(rmArgs).ConfigureAwait(false); } return statObject; @@ -3504,8 +3502,11 @@ internal static async Task PutObject_Test9(IMinioClient minio) totalBytesTransferred = progressReport.TotalBytesTransferred; // Console.WriteLine( // $"PutObject_Test9 - Percentage: {progressReport.Percentage}% TotalBytesTransferred: {progressReport.TotalBytesTransferred} bytes"); - // if (progressReport.Percentage != 100) - // Console.SetCursorPosition(0, Console.CursorTop - 1); + if (progressReport.Percentage != 100) + { + var topPosition = Console.CursorTop > 0 ? Console.CursorTop - 1 : Console.CursorTop; + Console.SetCursorPosition(0, topPosition); + } // else Console.WriteLine(); }); var args = new Dictionary @@ -3519,8 +3520,10 @@ internal static async Task PutObject_Test9(IMinioClient minio) try { await Setup_Test(minio, bucketName).ConfigureAwait(false); - _ = await PutObject_Tester(minio, bucketName, objectName, null, contentType, 0, null, - rsg.GenerateStreamFromSeed(objSize), progress).ConfigureAwait(false); + + var stream = rsg.GenerateStreamFromSeed(objSize); + var statObj = await PutObject_Tester(minio, bucketName, objectName, null, contentType, 0, null, + stream, progress).ConfigureAwait(false); Assert.IsTrue(percentage == 100); Assert.IsTrue(totalBytesTransferred == objSize); new MintLogger(nameof(PutObject_Test9), putObjectSignature, @@ -3556,8 +3559,11 @@ internal static async Task PutObject_Test10(IMinioClient minio) totalBytesTransferred = progressReport.TotalBytesTransferred; // Console.WriteLine( // $"PutObject_Test10 - Percentage: {progressReport.Percentage}% TotalBytesTransferred: {progressReport.TotalBytesTransferred} bytes"); - // if (progressReport.Percentage != 100) - // Console.SetCursorPosition(0, Console.CursorTop - 1); + if (progressReport.Percentage != 100) + { + var topPosition = Console.CursorTop > 0 ? Console.CursorTop - 1 : Console.CursorTop; + Console.SetCursorPosition(0, topPosition); + } // else Console.WriteLine(); }); var args = new Dictionary @@ -3673,6 +3679,9 @@ internal static async Task CopyObject_Test2(IMinioClient minio) var startTime = DateTime.Now; var bucketName = GetRandomName(15); var objectName = GetRandomObjectName(10); + var objectSize = 1 * KB; + var objectSizeStr = objectSize.ToString(CultureInfo.InvariantCulture) + .Replace(" * ", "", StringComparison.Ordinal); var destBucketName = GetRandomName(15); var destObjectName = GetRandomName(10); var args = new Dictionary @@ -3682,8 +3691,8 @@ internal static async Task CopyObject_Test2(IMinioClient minio) { "objectName", objectName }, { "destBucketName", destBucketName }, { "destObjectName", destObjectName }, - { "data", "1KB" }, - { "size", "1KB" } + { "data", objectSizeStr }, + { "size", objectSizeStr } }; try { @@ -3691,13 +3700,12 @@ internal static async Task CopyObject_Test2(IMinioClient minio) await Setup_Test(minio, bucketName).ConfigureAwait(false); await Setup_Test(minio, destBucketName).ConfigureAwait(false); - using var filestream = rsg.GenerateStreamFromSeed(1 * KB); + using var filestream = rsg.GenerateStreamFromSeed(objectSize); var putObjectArgs = new PutObjectArgs() .WithBucket(bucketName) .WithObject(objectName) .WithStreamData(filestream) - .WithObjectSize(filestream.Length) - .WithHeaders(null); + .WithObjectSize(filestream.Length); _ = await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); } catch (Exception ex) @@ -3705,7 +3713,8 @@ internal static async Task CopyObject_Test2(IMinioClient minio) await TearDown(minio, bucketName).ConfigureAwait(false); await TearDown(minio, destBucketName).ConfigureAwait(false); new MintLogger("CopyObject_Test2", copyObjectSignature, - "Tests whether CopyObject with Etag mismatch passes", TestStatus.FAIL, DateTime.Now - startTime, + "Tests whether CopyObject_Test2 with Etag mismatch (initial part) passes", TestStatus.FAIL, + DateTime.Now - startTime, ex.Message, ex.ToString(), args: args).Log(); throw; } @@ -3724,6 +3733,10 @@ internal static async Task CopyObject_Test2(IMinioClient minio) .WithObject(destObjectName); await minio.CopyObjectAsync(copyObjectArgs).ConfigureAwait(false); + + new MintLogger("CopyObject_Test2", copyObjectSignature, + "Tests whether CopyObject_Test2 with Etag mismatch passes", TestStatus.PASS, DateTime.Now - startTime, + args: args).Log(); } catch (MinioException ex) { @@ -3732,13 +3745,15 @@ internal static async Task CopyObject_Test2(IMinioClient minio) StringComparison.OrdinalIgnoreCase)) { new MintLogger(nameof(CopyObject_Test2), copyObjectSignature, - "Tests whether CopyObject with Etag mismatch passes", TestStatus.PASS, DateTime.Now - startTime, + "Tests whether CopyObject_Test2 with Etag mismatch passes", TestStatus.PASS, + DateTime.Now - startTime, args: args).Log(); } else { new MintLogger(nameof(CopyObject_Test2), copyObjectSignature, - "Tests whether CopyObject with Etag mismatch passes", TestStatus.FAIL, DateTime.Now - startTime, + "Tests whether CopyObject_Test2 with Etag mismatch passes", TestStatus.FAIL, + DateTime.Now - startTime, ex.Message, ex.ToString(), args: args).Log(); throw; } @@ -3746,7 +3761,7 @@ internal static async Task CopyObject_Test2(IMinioClient minio) catch (Exception ex) { new MintLogger(nameof(CopyObject_Test2), copyObjectSignature, - "Tests whether CopyObject with Etag mismatch passes", TestStatus.FAIL, DateTime.Now - startTime, + "Tests whether CopyObject_Test2 with Etag mismatch passes", TestStatus.FAIL, DateTime.Now - startTime, ex.Message, ex.ToString(), args: args).Log(); throw; } @@ -4074,6 +4089,7 @@ internal static async Task CopyObject_Test7(IMinioClient minio) var objectName = GetRandomObjectName(10); var destBucketName = GetRandomName(15); var destObjectName = GetRandomName(10); + var objectSize = 1 * KB; var args = new Dictionary (StringComparer.Ordinal) { @@ -4089,13 +4105,14 @@ internal static async Task CopyObject_Test7(IMinioClient minio) // Test CopyConditions where matching ETag is found await Setup_Test(minio, bucketName).ConfigureAwait(false); await Setup_Test(minio, destBucketName).ConfigureAwait(false); - using (var filestream = rsg.GenerateStreamFromSeed(1 * KB)) + Stream stream = null; + await using ((stream = rsg.GenerateStreamFromSeed(objectSize)).ConfigureAwait(false)) { var putObjectArgs = new PutObjectArgs() .WithBucket(bucketName) .WithObject(objectName) - .WithStreamData(filestream) - .WithObjectSize(filestream.Length); + .WithStreamData(stream) + .WithObjectSize(stream.Length); _ = await minio.PutObjectAsync(putObjectArgs).ConfigureAwait(false); } diff --git a/Minio.Functional.Tests/Minio.Functional.Tests.csproj b/Minio.Functional.Tests/Minio.Functional.Tests.csproj index 66ce48597..9e121f5c4 100644 --- a/Minio.Functional.Tests/Minio.Functional.Tests.csproj +++ b/Minio.Functional.Tests/Minio.Functional.Tests.csproj @@ -11,8 +11,8 @@ - - - + + + \ No newline at end of file diff --git a/Minio/ApiEndpoints/BucketOperations.cs b/Minio/ApiEndpoints/BucketOperations.cs index 8756a5968..fea10c210 100644 --- a/Minio/ApiEndpoints/BucketOperations.cs +++ b/Minio/ApiEndpoints/BucketOperations.cs @@ -77,24 +77,21 @@ public async Task BucketExistsAsync(BucketExistsArgs args, CancellationTok using var response = await this.ExecuteTaskAsync(ResponseErrorHandlers, requestMessageBuilder, cancellationToken: cancellationToken).ConfigureAwait(false); - if (response.Exception is not null && - response.Exception.GetType() == typeof(BucketNotFoundException)) - return false; + return response is not null && + (response.Exception is null || + response.Exception.GetType() != typeof(BucketNotFoundException)); } catch (InternalClientException ice) { - if ((ice.ServerResponse is not null && - HttpStatusCode.NotFound.Equals(ice.ServerResponse.StatusCode)) || - ice.ServerResponse is null) - return false; + return (ice.ServerResponse is null || + !HttpStatusCode.NotFound.Equals(ice.ServerResponse.StatusCode)) && + ice.ServerResponse is not null; } catch (Exception ex) { if (ex.GetType() == typeof(BucketNotFoundException)) return false; throw; } - - return true; } /// diff --git a/Minio/ApiEndpoints/ObjectOperations.cs b/Minio/ApiEndpoints/ObjectOperations.cs index ebcb9cc5a..b424bae0f 100644 --- a/Minio/ApiEndpoints/ObjectOperations.cs +++ b/Minio/ApiEndpoints/ObjectOperations.cs @@ -698,14 +698,12 @@ public async Task CopyObjectAsync(CopyObjectArgs args, CancellationToken cancell (srcByteRangeSize > 0 && args.SourceObject.CopyOperationConditions.byteRangeEnd >= args.SourceObjectInfo.Size)) - throw new InvalidDataException( - $"Specified byte range ({ - args.SourceObject.CopyOperationConditions - .byteRangeStart.ToString(CultureInfo.InvariantCulture)}-{ - args.SourceObject.CopyOperationConditions.byteRangeEnd - .ToString(CultureInfo.InvariantCulture) - }) does not fit within source object (size={ - args.SourceObjectInfo.Size.ToString(CultureInfo.InvariantCulture)})"); + throw new InvalidDataException($"Specified byte range ({args.SourceObject + .CopyOperationConditions + .byteRangeStart.ToString(CultureInfo.InvariantCulture)}-{args.SourceObject + .CopyOperationConditions.byteRangeEnd.ToString(CultureInfo.InvariantCulture) + }) does not fit within source object (size={args.SourceObjectInfo.Size + .ToString(CultureInfo.InvariantCulture)})"); if (copySize > Constants.MaxSingleCopyObjectSize || (srcByteRangeSize > 0 && diff --git a/Minio/DataModel/Args/PutObjectArgs.cs b/Minio/DataModel/Args/PutObjectArgs.cs index e029b063b..5139bb2e2 100644 --- a/Minio/DataModel/Args/PutObjectArgs.cs +++ b/Minio/DataModel/Args/PutObjectArgs.cs @@ -151,7 +151,7 @@ public override PutObjectArgs WithHeaders(IDictionary headers) } if (string.IsNullOrWhiteSpace(ContentType)) ContentType = "application/octet-stream"; - if (!Headers.ContainsKey("Content-Type")) Headers["Content-Type"] = ContentType; + Headers["Content-Type"] = ContentType; return this; } diff --git a/Minio/DataModel/Response/CopyObjectResponse.cs b/Minio/DataModel/Response/CopyObjectResponse.cs index ff9b66ace..d5b6f88bd 100644 --- a/Minio/DataModel/Response/CopyObjectResponse.cs +++ b/Minio/DataModel/Response/CopyObjectResponse.cs @@ -15,8 +15,6 @@ */ using System.Net; -using System.Text; -using CommunityToolkit.HighPerformance; using Minio.DataModel.Result; using Minio.Helper; @@ -27,11 +25,10 @@ internal class CopyObjectResponse : GenericResponse public CopyObjectResponse(HttpStatusCode statusCode, string responseContent, Type reqType) : base(statusCode, responseContent) { - using var stream = Encoding.UTF8.GetBytes(responseContent).AsMemory().AsStream(); if (reqType == typeof(CopyObjectResult)) - CopyObjectRequestResult = Utils.DeserializeXml(stream); + CopyObjectRequestResult = Utils.DeserializeXml(responseContent); else - CopyPartRequestResult = Utils.DeserializeXml(stream); + CopyPartRequestResult = Utils.DeserializeXml(responseContent); } internal CopyObjectResult CopyObjectRequestResult { get; set; } diff --git a/Minio/Exceptions/MinioException.cs b/Minio/Exceptions/MinioException.cs index d3ad3103a..db7bcc6ea 100644 --- a/Minio/Exceptions/MinioException.cs +++ b/Minio/Exceptions/MinioException.cs @@ -69,11 +69,8 @@ private static string GetMessage(string message, ResponseResult serverResponse) var contentString = serverResponse.Content; - if (message is null) - return - $"MinIO API responded with status code={serverResponse.StatusCode}, response={serverResponse.ErrorMessage}, content={contentString}"; - - return - $"MinIO API responded with message={message}. Status code={serverResponse.StatusCode}, response={serverResponse.ErrorMessage}, content={contentString}"; + return message is null + ? $"MinIO API responded with status code={serverResponse.StatusCode}, response={serverResponse.ErrorMessage}, content={contentString}" + : $"MinIO API responded with message={message}. Status code={serverResponse.StatusCode}, response={serverResponse.ErrorMessage}, content={contentString}"; } } diff --git a/Minio/Exceptions/PreconditionFailedException.cs b/Minio/Exceptions/PreconditionFailedException.cs new file mode 100644 index 000000000..6a6c095b2 --- /dev/null +++ b/Minio/Exceptions/PreconditionFailedException.cs @@ -0,0 +1,27 @@ +using Minio.DataModel.Result; + +namespace Minio.Exceptions; + +[Serializable] +public class PreconditionFailedException : MinioException +{ + public PreconditionFailedException() + { + } + + public PreconditionFailedException(string message, Exception innerException) : base(message, innerException) + { + } + + public PreconditionFailedException(ResponseResult serverResponse) : base(serverResponse) + { + } + + public PreconditionFailedException(string message) : base(message) + { + } + + public PreconditionFailedException(string message, ResponseResult serverResponse) : base(message, serverResponse) + { + } +} diff --git a/Minio/Helper/Utils.cs b/Minio/Helper/Utils.cs index 21527477d..4076342f1 100644 --- a/Minio/Helper/Utils.cs +++ b/Minio/Helper/Utils.cs @@ -996,7 +996,10 @@ public static void Print(object obj) .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { var value = prop.GetValue(obj, Array.Empty()); - Console.WriteLine("DEBUG >> {0} = {1}", prop.Name, value); + if (string.Equals(prop.Name, "Headers", StringComparison.Ordinal)) + PrintDict((Dictionary)value); + else + Console.WriteLine("DEBUG >> {0} = {1}", prop.Name, value); } Console.WriteLine("DEBUG >> Print is DONE!\n\n"); @@ -1006,9 +1009,9 @@ public static void PrintDict(IDictionary d) { if (d is not null) foreach (var kv in d) - Console.WriteLine("DEBUG >> {0} = {1}", kv.Key, kv.Value); + Console.WriteLine("DEBUG >> Dictionary({0} => {1})", kv.Key, kv.Value); - Console.WriteLine("DEBUG >> Done printing\n"); + Console.WriteLine("DEBUG >> Dictionary: Done printing\n"); } public static string DetermineNamespace(XDocument document) @@ -1062,10 +1065,23 @@ public static string SerializeToXml(T anyobject) where T : class XmlResolver = null }; + var xRoot = (XmlRootAttribute)typeof(T).GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault(); + + var serializer = new XmlSerializer(typeof(T), xRoot); + using var stringReader = new StringReader(xml); using var xmlReader = XmlReader.Create(stringReader, settings); + if (xml.Contains("", StringComparison.Ordinal)) + { + // Skip the first line + xml = xml[(xml.IndexOf('\n', StringComparison.Ordinal) + 1)..]; + stringReader.Dispose(); + using var stringReader1 = new StringReader(xml); + xRoot = new XmlRootAttribute { ElementName = "Error", IsNullable = true }; + serializer = new XmlSerializer(typeof(T), xRoot); + return (T)serializer.Deserialize(new NamespaceIgnorantXmlTextReader(stringReader1)); + } - var serializer = new XmlSerializer(typeof(T)); return (T)serializer.Deserialize(xmlReader); } @@ -1076,4 +1092,12 @@ private static string GetNamespace() ? xmlRootAttribute.Namespace : null; } + + // Class to ignore namespaces when de-serializing + public class NamespaceIgnorantXmlTextReader : XmlTextReader + { + public NamespaceIgnorantXmlTextReader(TextReader reader) : base(reader) { } + + public override string NamespaceURI => string.Empty; + } } diff --git a/Minio/MinioClient.cs b/Minio/MinioClient.cs index bb15e4e75..dd5cddad5 100644 --- a/Minio/MinioClient.cs +++ b/Minio/MinioClient.cs @@ -16,8 +16,6 @@ */ using System.Net; -using System.Text; -using CommunityToolkit.HighPerformance; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Minio.DataModel.Result; @@ -234,8 +232,7 @@ private static void ParseErrorFromContent(ResponseResult response) throw new BucketNotFoundException(bucketName, "Not found."); } - using var stream = Encoding.UTF8.GetBytes(response.Content).AsMemory().AsStream(); - var errResponse = Utils.DeserializeXml(stream); + var errResponse = Utils.DeserializeXml(response.Content); if (response.StatusCode.Equals(HttpStatusCode.Forbidden) && (errResponse.Code.Equals("SignatureDoesNotMatch", StringComparison.OrdinalIgnoreCase) || @@ -260,14 +257,16 @@ private static void ParseErrorFromContent(ResponseResult response) if (response.StatusCode.Equals(HttpStatusCode.NotImplemented) && errResponse.Code.Equals("NotImplemented", StringComparison.OrdinalIgnoreCase)) + { #pragma warning disable MA0025 // Implement the functionality instead of throwing NotImplementedException throw new NotImplementedException(errResponse.Message); + } #pragma warning restore MA0025 // Implement the functionality instead of throwing NotImplementedException if (response.StatusCode.Equals(HttpStatusCode.BadRequest) && errResponse.Code.Equals("InvalidRequest", StringComparison.OrdinalIgnoreCase)) { - var legalHold = new Dictionary(StringComparer.Ordinal) { { "legal-hold", "" } }; + _ = new Dictionary(StringComparer.Ordinal) { { "legal-hold", "" } }; if (response.Request.RequestUri.Query.Contains("legalHold", StringComparison.OrdinalIgnoreCase)) throw new MissingObjectLockConfigurationException(errResponse.BucketName, errResponse.Message); } @@ -285,6 +284,12 @@ private static void ParseErrorFromContent(ResponseResult response) throw new ArgumentException("Bucket already owned by you: " + errResponse.BucketName, nameof(response)); + if (response.StatusCode.Equals(HttpStatusCode.PreconditionFailed) + && errResponse.Code.Equals("PreconditionFailed", StringComparison.OrdinalIgnoreCase)) + throw new PreconditionFailedException("At least one of the pre-conditions you " + + "specified did not hold for object: \"" + errResponse.Resource + + "\""); + throw new UnexpectedMinioException(errResponse.Message) { Response = errResponse, XmlError = response.Content }; } @@ -292,9 +297,8 @@ protected virtual void Dispose(bool disposing) { if (!disposedValue) { - if (disposing) - if (Config.DisposeHttpClient) - Config.HttpClient?.Dispose(); + if (disposing && Config.DisposeHttpClient) + Config.HttpClient?.Dispose(); disposedValue = true; } } diff --git a/Minio/RequestExtensions.cs b/Minio/RequestExtensions.cs index 827d0d44b..e55eef215 100644 --- a/Minio/RequestExtensions.cs +++ b/Minio/RequestExtensions.cs @@ -16,9 +16,9 @@ public static class RequestExtensions Justification = "This is done in the interface. String is provided here for convenience")] public static Task WrapperGetAsync(this IMinioClient minioClient, string url) { - if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); - - return minioClient.WrapperGetAsync(new Uri(url)); + return minioClient is null + ? throw new ArgumentNullException(nameof(minioClient)) + : minioClient.WrapperGetAsync(new Uri(url)); } /// @@ -28,9 +28,9 @@ public static Task WrapperGetAsync(this IMinioClient minioC Justification = "This is done in the interface. String is provided here for convenience")] public static Task WrapperPutAsync(this IMinioClient minioClient, string url, StreamContent strm) { - if (minioClient is null) throw new ArgumentNullException(nameof(minioClient)); - - return minioClient.WrapperPutAsync(new Uri(url), strm); + return minioClient is null + ? throw new ArgumentNullException(nameof(minioClient)) + : minioClient.WrapperPutAsync(new Uri(url), strm); } /// @@ -90,7 +90,7 @@ private static async Task ExecuteTaskCoreAsync(this IMinioClient var request = requestMessageBuilder.Request; - ResponseResult responseResult = null; + var responseResult = new ResponseResult(request, response: null); try { var response = await minioClient.Config.HttpClient.SendAsync(request, @@ -102,37 +102,47 @@ private static async Task ExecuteTaskCoreAsync(this IMinioClient if (requestMessageBuilder.FunctionResponseWriter is not null) await requestMessageBuilder.FunctionResponseWriter(responseResult.ContentStream, cancellationToken) .ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception e) - { - responseResult?.Dispose(); - responseResult = new ResponseResult(request, e); - } - if (responseResult.StatusCode == HttpStatusCode.NotFound) - { - if (request.Method == HttpMethod.Head) + var path = request.RequestUri.LocalPath.TrimStart('/').TrimEnd('/').Split('/'); + if (responseResult.Response.StatusCode == HttpStatusCode.NotFound) { - Exception ex = new BucketNotFoundException(); - responseResult.Exception = ex; - return responseResult; + if (request.Method == HttpMethod.Head) + { + if (responseResult.Exception?.GetType().Equals(typeof(BucketNotFoundException)) == true || + path?.ToList().Count == 1) + responseResult.Exception = new BucketNotFoundException(); + + if (path?.ToList().Count > 1) + { + var found = await minioClient + .BucketExistsAsync(new BucketExistsArgs().WithBucket(path.ToList()[0]), cancellationToken) + .ConfigureAwait(false); + responseResult.Exception = !found + ? new Exception("ThrowBucketNotFoundException") + : new ObjectNotFoundException(); + throw responseResult.Exception; + } + } + + if (request.RequestUri.ToString().Contains("lock", StringComparison.OrdinalIgnoreCase) && + request.Method == HttpMethod.Get) + responseResult.Exception = new MissingObjectLockConfigurationException(); } - if (request.RequestUri.ToString().Contains("lock", StringComparison.OrdinalIgnoreCase) && - request.Method == HttpMethod.Get) - { - Exception ex = new MissingObjectLockConfigurationException(); - responseResult.Exception = ex; - return responseResult; - } + return responseResult; } + catch (Exception ex) when (ex is not (OperationCanceledException or + ObjectNotFoundException)) + { + if (ex.Message.Equals("ThrowBucketNotFoundException", StringComparison.Ordinal)) + throw new BucketNotFoundException(); - minioClient.HandleIfErrorResponse(responseResult, errorHandlers, startTime); - return responseResult; + if (responseResult is not null) + responseResult.Exception = ex; + else + responseResult = new ResponseResult(request, ex); + return responseResult; + } } private static Task ExecuteWithRetry(this IMinioClient minioClient, @@ -361,12 +371,14 @@ private static void HandleIfErrorResponse(this IMinioClient minioClient, Respons minioClient.LogRequest(response.Request, response, (now - startTime).TotalMilliseconds); } - if (handlers is null) throw new ArgumentNullException(nameof(handlers)); + if (response.Exception is null) + throw response.Exception; - // Run through handlers passed to take up error handling - foreach (var handler in handlers) handler.Handle(response); - - // Fall back default error handler - minioClient.DefaultErrorHandler.Handle(response); + if (handlers.Any()) + // Run through handlers passed to take up error handling + foreach (var handler in handlers) + handler.Handle(response); + else + minioClient.DefaultErrorHandler.Handle(response); } }