Browse Source

Fix: Use typed exceptions for minio client (#398)

Fixes #396, #395
pull/403/head
ig-sinicyn 5 years ago
committed by GitHub
parent
commit
22cc896398
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      Minio.Functional.Tests/FunctionalTest.cs
  2. 73
      Minio.Tests/NegativeTest.cs
  3. 9
      Minio.Tests/UtilsTest.cs
  4. 2
      Minio/DataModel/Select/SelectResponseStream.cs
  5. 7
      Minio/Exceptions/AccessDeniedException.cs
  6. 7
      Minio/Exceptions/BucketNotFoundException.cs
  7. 7
      Minio/Exceptions/ConnectionException.cs
  8. 23
      Minio/Exceptions/ErrorResponseException.cs
  9. 7
      Minio/Exceptions/InternalClientException.cs
  10. 43
      Minio/Exceptions/MinioException.cs
  11. 29
      Minio/Exceptions/SelectObjectContentException.cs
  12. 29
      Minio/Exceptions/UnexpectedMinioException.cs
  13. 190
      Minio/MinioClient.cs

4
Minio.Functional.Tests/FunctionalTest.cs

@ -1185,7 +1185,7 @@ namespace Minio.Functional.Tests
}
catch (MinioException ex)
{
if (ex.message.Equals("A header you provided implies functionality that is not implemented"))
if (ex.ServerMessage.Equals("A header you provided implies functionality that is not implemented"))
{
new MintLogger("CopyObject_Test5", copyObjectSignature, "Tests whether CopyObject multi-part copy upload for large files works", TestStatus.NA, (DateTime.Now - startTime), args:args).Log();
}
@ -1664,7 +1664,7 @@ namespace Minio.Functional.Tests
}
catch (ObjectNotFoundException ex)
{
Assert.AreEqual(ex.message, "Not found.");
Assert.AreEqual(ex.ServerMessage, "Not found.");
}
await TearDown(minio, bucketName);

73
Minio.Tests/NegativeTest.cs

@ -0,0 +1,73 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Minio.Exceptions;
namespace Minio.Tests
{
[TestClass]
public class NegativeTest
{
[TestMethod]
public async Task TestNoConnectionError()
{
// invalid uri
var minio = new MinioClient("localhost:12121");
var ex = await Assert.ThrowsExceptionAsync<ConnectionException>(() => minio.BucketExistsAsync("test"));
Assert.IsNotNull(ex.ServerResponse);
}
[TestMethod]
public async Task TestInvalidBucketNameError()
{
var badName = new string('A', 260);
var minio = new MinioClient("play.min.io", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG");
await Assert.ThrowsExceptionAsync<InvalidBucketNameException>(() => minio.BucketExistsAsync(badName));
}
[TestMethod]
public async Task TestInvalidObjectNameError()
{
var badName = new string('A', 260);
var bucketName = Guid.NewGuid().ToString("N");
var minio = new MinioClient("play.min.io", "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG");
try
{
await minio.MakeBucketAsync(bucketName);
var ex = await Assert.ThrowsExceptionAsync<InvalidObjectNameException>(
() => minio.StatObjectAsync(bucketName, badName));
Assert.AreEqual(ex.Response.Code, "InvalidObjectName");
ex = await Assert.ThrowsExceptionAsync<InvalidObjectNameException>(
() => minio.GetObjectAsync(bucketName, badName, s => { }));
Assert.AreEqual(ex.Response.Code, "InvalidObjectName");
}
finally
{
await minio.RemoveBucketAsync(bucketName);
}
}
}
}

9
Minio.Tests/UtilsTest.cs

@ -1,5 +1,6 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc.
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -73,7 +74,7 @@ namespace Minio.Tests
}
catch (InvalidObjectNameException ex)
{
Assert.AreEqual(ex.message, "Object name cannot be empty.");
Assert.AreEqual(ex.ServerMessage, "Object name cannot be empty.");
}
}
@ -87,7 +88,7 @@ namespace Minio.Tests
}
catch (InvalidObjectNameException ex)
{
Assert.AreEqual(ex.message, "Object name cannot be greater than 1024 characters.");
Assert.AreEqual(ex.ServerMessage, "Object name cannot be greater than 1024 characters.");
}
}
@ -120,7 +121,7 @@ namespace Minio.Tests
}
catch (EntityTooLargeException ex)
{
Assert.AreEqual(ex.message, "Your proposed upload size 5000000000000000000 exceeds the maximum allowed object size " + Constants.MaxMultipartPutObjectSize);
Assert.AreEqual(ex.ServerMessage, "Your proposed upload size 5000000000000000000 exceeds the maximum allowed object size " + Constants.MaxMultipartPutObjectSize);
}
}

2
Minio/DataModel/Select/SelectResponseStream.cs

@ -180,7 +180,7 @@ namespace Minio.DataModel
string errorMessage = null;
headerMap.TryGetValue(":error-code", out errorCode);
headerMap.TryGetValue(":error-message", out errorMessage);
throw new MinioException(errorCode + ":" + errorMessage);
throw new SelectObjectContentException(errorCode + ":" + errorMessage);
}
}
if (headerMap.TryGetValue(":event-type", out value))

7
Minio/Exceptions/AccessDeniedException.cs

@ -1,5 +1,6 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc.
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,10 +22,6 @@ namespace Minio.Exceptions
[Serializable]
public class AccessDeniedException : MinioException
{
public AccessDeniedException()
{
}
public AccessDeniedException(string message) : base(message)
{
}

7
Minio/Exceptions/BucketNotFoundException.cs

@ -1,5 +1,6 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc.
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,10 +24,6 @@ namespace Minio.Exceptions
{
private readonly string bucketName;
public BucketNotFoundException()
{
}
public BucketNotFoundException(string bucketName, string message) : base(message)
{
this.bucketName = bucketName;

7
Minio/Exceptions/ConnectionException.cs

@ -1,5 +1,6 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc.
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,11 +15,13 @@
* limitations under the License.
*/
using RestSharp;
namespace Minio.Exceptions
{
public class ConnectionException : MinioException
{
public ConnectionException(string message) : base(message)
public ConnectionException(string message, IRestResponse response) : base(message, response)
{
}
}

23
Minio/Exceptions/ErrorResponseException.cs

@ -1,5 +1,6 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc.
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,24 +21,10 @@ namespace Minio.Exceptions
{
public class ErrorResponseException : MinioException
{
private readonly string ErrorCode;
public ErrorResponseException(IRestResponse response)
: base($"MinIO API responded with status code={response.StatusCode}, response={response.ErrorMessage}, content={response.Content}")
{
this.response = response;
}
public ErrorResponseException()
public ErrorResponseException(ErrorResponse errorResponse, IRestResponse serverResponse) :
base(serverResponse)
{
Response = errorResponse;
}
public ErrorResponseException(string message, string errorcode) : base($"MinIO API responded with message={message}")
{
this.message = message;
this.ErrorCode = errorcode;
}
public override string ToString() => $"{this.message}: {base.ToString()}";
}
}

7
Minio/Exceptions/InternalClientException.cs

@ -1,5 +1,6 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc.
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,11 +15,13 @@
* limitations under the License.
*/
using RestSharp;
namespace Minio.Exceptions
{
public class InternalClientException : MinioException
{
public InternalClientException(string message) : base(message)
public InternalClientException(string message, IRestResponse response) : base(message, response)
{
}
}

43
Minio/Exceptions/MinioException.cs

@ -1,5 +1,6 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc.
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,6 +16,7 @@
*/
using System;
using RestSharp;
namespace Minio.Exceptions
@ -22,27 +24,44 @@ namespace Minio.Exceptions
[Serializable]
public class MinioException : Exception
{
public string message { get; set; }
public IRestResponse response { get; set; }
private static string GetMessage(string message, IRestResponse serverResponse)
{
if (serverResponse == null && string.IsNullOrEmpty(message))
throw new ArgumentNullException(nameof(message));
if (serverResponse == null)
return $"MinIO API responded with message={message}";
public MinioException(IRestResponse response)
: base($"MinIO API responded with status code={response.StatusCode}, response={response.ErrorMessage}, content={response.Content}")
if (message == null)
return $"MinIO API responded with status code={serverResponse.StatusCode}, response={serverResponse.ErrorMessage}, content={serverResponse.Content}";
return $"MinIO API responded with message={message}. Status code={serverResponse.StatusCode}, response={serverResponse.ErrorMessage}, content={serverResponse.Content}";
}
public MinioException(IRestResponse serverResponse)
: this(null, serverResponse)
{
this.response = response;
}
public MinioException()
public MinioException(string message)
: this(message, null)
{
}
public MinioException(string message) : base($"MinIO API responded with message={message}")
public MinioException(string message, IRestResponse serverResponse)
: base(GetMessage(message, serverResponse))
{
this.message = message;
this.ServerMessage = message;
this.ServerResponse = serverResponse;
}
public ErrorResponse Response { get; set; }
public string XmlError { get; set; }
public string ServerMessage { get; }
public IRestResponse ServerResponse { get; }
public ErrorResponse Response { get; internal set; }
public override string ToString() => $"{this.message}: {base.ToString()}";
public string XmlError { get; internal set; }
}
}

29
Minio/Exceptions/SelectObjectContentException.cs

@ -0,0 +1,29 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
namespace Minio.Exceptions
{
[Serializable]
public class SelectObjectContentException : MinioException
{
public SelectObjectContentException(string message) : base(message)
{
}
}
}

29
Minio/Exceptions/UnexpectedMinioException.cs

@ -0,0 +1,29 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
namespace Minio.Exceptions
{
[Serializable]
public class UnexpectedMinioException : MinioException
{
public UnexpectedMinioException(string message) : base(message)
{
}
}
}

190
Minio/MinioClient.cs

@ -1,5 +1,6 @@
/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017 MinIO, Inc.
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2017, 2018, 2019, 2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -412,7 +413,7 @@ namespace Minio
{
if (response == null)
{
throw new ConnectionException("Response is nil. Please report this issue https://github.com/minio/minio-dotnet/issues");
throw new ConnectionException("Response is nil. Please report this issue https://github.com/minio/minio-dotnet/issues", response);
}
if (HttpStatusCode.Redirect.Equals(response.StatusCode) || HttpStatusCode.TemporaryRedirect.Equals(response.StatusCode) || HttpStatusCode.MovedPermanently.Equals(response.StatusCode))
@ -422,85 +423,120 @@ namespace Minio
if (string.IsNullOrWhiteSpace(response.Content))
{
ErrorResponse errorResponse = new ErrorResponse();
ParseErrorNoContent(response);
return;
}
ParseErrorFromContent(response);
}
private static void ParseErrorNoContent(IRestResponse response)
{
if (HttpStatusCode.Forbidden.Equals(response.StatusCode)
|| HttpStatusCode.BadRequest.Equals(response.StatusCode)
|| HttpStatusCode.NotFound.Equals(response.StatusCode)
|| HttpStatusCode.MethodNotAllowed.Equals(response.StatusCode)
|| HttpStatusCode.NotImplemented.Equals(response.StatusCode))
{
ParseWellKnownErrorNoContent(response);
}
if (HttpStatusCode.Forbidden.Equals(response.StatusCode) || HttpStatusCode.NotFound.Equals(response.StatusCode) ||
HttpStatusCode.MethodNotAllowed.Equals(response.StatusCode) || HttpStatusCode.NotImplemented.Equals(response.StatusCode))
if (response.StatusCode == 0)
throw new ConnectionException("Connection error: " + response.ErrorMessage, response);
throw new InternalClientException("Unsuccessful response from server without XML error: " + response.ErrorMessage, response);
}
private static void ParseWellKnownErrorNoContent(IRestResponse response)
{
MinioException error = null;
ErrorResponse errorResponse = new ErrorResponse();
foreach (Parameter parameter in response.Headers)
{
if (parameter.Name.Equals("x-amz-id-2", StringComparison.CurrentCultureIgnoreCase))
{
MinioException e = null;
errorResponse.HostId = parameter.Value.ToString();
}
foreach (Parameter parameter in response.Headers)
{
if (parameter.Name.Equals("x-amz-id-2", StringComparison.CurrentCultureIgnoreCase))
{
errorResponse.HostId = parameter.Value.ToString();
}
if (parameter.Name.Equals("x-amz-request-id", StringComparison.CurrentCultureIgnoreCase))
{
errorResponse.RequestId = parameter.Value.ToString();
}
if (parameter.Name.Equals("x-amz-bucket-region", StringComparison.CurrentCultureIgnoreCase))
{
errorResponse.BucketRegion = parameter.Value.ToString();
}
}
if (parameter.Name.Equals("x-amz-request-id", StringComparison.CurrentCultureIgnoreCase))
{
errorResponse.RequestId = parameter.Value.ToString();
}
if (parameter.Name.Equals("x-amz-bucket-region", StringComparison.CurrentCultureIgnoreCase))
{
errorResponse.BucketRegion = parameter.Value.ToString();
}
}
errorResponse.Resource = response.Request.Resource;
var resourceSplits = response.Request.Resource.Split('/');
errorResponse.Resource = response.Request.Resource;
// zero, one or two segments
var resourceSplits = response.Request.Resource.Split(new[] { '/' }, 2, StringSplitOptions.RemoveEmptyEntries);
if (HttpStatusCode.NotFound.Equals(response.StatusCode))
{
int pathLength = resourceSplits.Length;
bool isAWS = response.ResponseUri.Host.EndsWith("s3.amazonaws.com");
bool isVirtual = isAWS && !response.ResponseUri.Host.StartsWith("s3.amazonaws.com");
if (HttpStatusCode.NotFound.Equals(response.StatusCode))
if (pathLength > 1)
{
var objectName = resourceSplits[1];
errorResponse.Code = "NoSuchKey";
error = new ObjectNotFoundException(objectName, "Not found.");
}
else if (pathLength == 1)
{
var resource = resourceSplits[0];
if (isAWS && isVirtual && response.Request.Resource != string.Empty)
{
int pathLength = resourceSplits.Count();
bool isAWS = response.ResponseUri.Host.EndsWith("s3.amazonaws.com");
bool isVirtual = isAWS && !response.ResponseUri.Host.StartsWith("s3.amazonaws.com");
if (pathLength > 1)
{
errorResponse.Code = "NoSuchKey";
var bucketName = resourceSplits[0];
var objectName = String.Join("/", resourceSplits.Skip(1));
if (objectName == string.Empty)
{
e = new BucketNotFoundException(bucketName, "Not found.");
}
else
{
e = new ObjectNotFoundException(objectName, "Not found.");
}
}
else if (pathLength == 1)
{
var resource = resourceSplits[0];
if (isAWS && isVirtual && response.Request.Resource != string.Empty)
{
errorResponse.Code = "NoSuchKey";
e = new ObjectNotFoundException(resource, "Not found.");
}
else
{
errorResponse.Code = "NoSuchBucket";
BucketRegionCache.Instance.Remove(resource);
e = new BucketNotFoundException(resource, "Not found.");
}
}
else
{
e = new InternalClientException("404 without body resulted in path with less than two components");
}
errorResponse.Code = "NoSuchKey";
error = new ObjectNotFoundException(resource, "Not found.");
}
else if (HttpStatusCode.Forbidden.Equals(response.StatusCode))
else
{
errorResponse.Code = "Forbidden";
e = new AccessDeniedException("Access denied on the resource: " + response.Request.Resource);
errorResponse.Code = "NoSuchBucket";
BucketRegionCache.Instance.Remove(resource);
error = new BucketNotFoundException(resource, "Not found.");
}
e.Response = errorResponse;
throw e;
}
throw new InternalClientException("Unsuccessful response from server without XML error: " + response.ErrorMessage);
else
{
error = new InternalClientException("404 without body resulted in path with less than two components", response);
}
}
else if (HttpStatusCode.BadRequest.Equals(response.StatusCode))
{
int pathLength = resourceSplits.Length;
if (pathLength > 1)
{
var objectName = resourceSplits[1];
errorResponse.Code = "InvalidObjectName";
error = new InvalidObjectNameException(objectName, "Invalid object name.");
}
else
{
error = new InternalClientException("400 without body resulted in path with less than two components", response);
}
}
else if (HttpStatusCode.Forbidden.Equals(response.StatusCode))
{
errorResponse.Code = "Forbidden";
error = new AccessDeniedException("Access denied on the resource: " + response.Request.Resource);
}
error.Response = errorResponse;
throw error;
}
if (response.StatusCode.Equals(HttpStatusCode.NotFound) && response.Request.Resource.EndsWith("?location")
private static void ParseErrorFromContent(IRestResponse response)
{
if (response.StatusCode.Equals(HttpStatusCode.NotFound)
&& response.Request.Resource.EndsWith("?location")
&& response.Request.Method.Equals(Method.GET))
{
var bucketName = response.Request.Resource.Split('?')[0];
@ -513,23 +549,25 @@ namespace Minio
ErrorResponse errResponse = (ErrorResponse)new XmlSerializer(typeof(ErrorResponse)).Deserialize(stream);
// Handle XML response for Bucket Policy not found case
if (response.StatusCode.Equals(HttpStatusCode.NotFound) && response.Request.Resource.EndsWith("?policy")
&& response.Request.Method.Equals(Method.GET) && errResponse.Code == "NoSuchBucketPolicy")
if (response.StatusCode.Equals(HttpStatusCode.NotFound)
&& response.Request.Resource.EndsWith("?policy")
&& response.Request.Method.Equals(Method.GET)
&& errResponse.Code == "NoSuchBucketPolicy")
{
throw new ErrorResponseException(errResponse.Message, errResponse.Code)
throw new ErrorResponseException(errResponse, response)
{
Response = errResponse,
XmlError = response.Content
};
}
if (response.StatusCode.Equals(HttpStatusCode.NotFound)
&& response.Request.Method.Equals(Method.GET) && errResponse.Code == "NoSuchBucket")
&& response.Request.Method.Equals(Method.GET)
&& errResponse.Code == "NoSuchBucket")
{
throw new BucketNotFoundException(errResponse.BucketName, "Not found.");
}
throw new MinioException(errResponse.Message)
throw new UnexpectedMinioException(errResponse.Message)
{
Response = errResponse,
XmlError = response.Content

Loading…
Cancel
Save