diff --git a/backend/internal/apierr/parse.go b/backend/internal/apierr/parse.go index 04aa7d6..06cc21b 100644 --- a/backend/internal/apierr/parse.go +++ b/backend/internal/apierr/parse.go @@ -2,9 +2,11 @@ package apierr import ( "encoding/json" + "errors" "io" "github.com/Noooste/azuretls-client" + "github.com/minio/minio-go/v7" ) // garageErrBody mirrors Garage's JSON error envelope. @@ -51,3 +53,40 @@ func ParseGarage(resp *azuretls.Response) error { } return ue } + +// FromMinio converts a MinIO/S3 error into an *UpstreamError. Returns nil when +// err is nil. If err is not a minio.ErrorResponse (e.g. a network error), a +// generic 500 *UpstreamError is returned with the raw error string as Message. +func FromMinio(err error) *UpstreamError { + if err == nil { + return nil + } + + var mer minio.ErrorResponse + if errors.As(err, &mer) { + details := map[string]string{} + if mer.BucketName != "" { + details["bucket"] = mer.BucketName + } + if mer.Key != "" { + details["key"] = mer.Key + } + status := mer.StatusCode + if status == 0 { + status = 500 + } + return &UpstreamError{ + HTTPStatus: status, + Code: mer.Code, + Message: mer.Message, + Source: "s3", + Details: details, + } + } + + return &UpstreamError{ + HTTPStatus: 500, + Source: "s3", + Message: err.Error(), + } +} diff --git a/backend/internal/apierr/parse_test.go b/backend/internal/apierr/parse_test.go index c24f75a..f96b5c7 100644 --- a/backend/internal/apierr/parse_test.go +++ b/backend/internal/apierr/parse_test.go @@ -2,11 +2,13 @@ package apierr import ( "bytes" + "errors" "io" "strings" "testing" "github.com/Noooste/azuretls-client" + "github.com/minio/minio-go/v7" ) func fakeResp(status int, body string) *azuretls.Response { @@ -77,3 +79,56 @@ func TestParseGarage_EmptyBody(t *testing.T) { t.Errorf("HTTPStatus = %d, want 502", ue.HTTPStatus) } } + +func TestFromMinio_NilInput(t *testing.T) { + if got := FromMinio(nil); got != nil { + t.Fatalf("FromMinio(nil) = %v, want nil", got) + } +} + +func TestFromMinio_MinioErrorResponse(t *testing.T) { + in := minio.ErrorResponse{ + StatusCode: 404, + Code: "NoSuchBucket", + Message: "The specified bucket does not exist", + BucketName: "missing", + } + got := FromMinio(in) + if got == nil { + t.Fatal("FromMinio returned nil") + } + if got.HTTPStatus != 404 { + t.Errorf("HTTPStatus = %d, want 404", got.HTTPStatus) + } + if got.Code != "NoSuchBucket" { + t.Errorf("Code = %q, want NoSuchBucket", got.Code) + } + if got.Message != "The specified bucket does not exist" { + t.Errorf("Message = %q", got.Message) + } + if got.Source != "s3" { + t.Errorf("Source = %q, want s3", got.Source) + } + if got.Details["bucket"] != "missing" { + t.Errorf("Details[bucket] = %q, want missing", got.Details["bucket"]) + } +} + +func TestFromMinio_NonMinioError(t *testing.T) { + got := FromMinio(errors.New("connection refused")) + if got == nil { + t.Fatal("FromMinio returned nil") + } + if got.HTTPStatus != 500 { + t.Errorf("HTTPStatus = %d, want 500", got.HTTPStatus) + } + if got.Code != "" { + t.Errorf("Code = %q, want empty", got.Code) + } + if !strings.Contains(got.Message, "connection refused") { + t.Errorf("Message = %q", got.Message) + } + if got.Source != "s3" { + t.Errorf("Source = %q, want s3", got.Source) + } +}