From 8d76e390488c21ba3f3002affe69961c74eaed98 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 12 Jun 2026 15:09:12 -0700 Subject: [PATCH 1/4] WiP --- alioss.go | 48 +++++++++++++++++++++++++++++++++++++++--------- azure.go | 31 +++++++++++++++++++++++-------- gcp.go | 51 ++++++++++++++++++++++++++++++++++++++++++--------- s3.go | 30 +++++++++++++++++++++++------- 4 files changed, 127 insertions(+), 33 deletions(-) diff --git a/alioss.go b/alioss.go index 914319c..e420edf 100644 --- a/alioss.go +++ b/alioss.go @@ -16,6 +16,7 @@ package storage import ( "bytes" + "errors" "io" "net/url" "os" @@ -25,6 +26,27 @@ import ( "github.com/aliyun/aliyun-oss-go-sdk/oss" ) +func wrapAliOSSError(err error) error { + if err == nil { + return nil + } + var svcErr oss.ServiceError + if errors.As(err, &svcErr) { + return &ErrorWithStatusCode{ + Err: err, + StatusCode: svcErr.StatusCode, + } + } + var unexpectedErr oss.UnexpectedStatusCodeError + if errors.As(err, &unexpectedErr) { + return &ErrorWithStatusCode{ + Err: err, + StatusCode: unexpectedErr.Got(), + } + } + return err +} + type aliOSSStorage struct { conf *AliOSSConfig bucket *oss.Bucket @@ -50,7 +72,7 @@ func NewAliOSS(conf *AliOSSConfig) (Storage, error) { func (s *aliOSSStorage) UploadData(data []byte, storagePath, contentType string) (string, int64, error) { reader := bytes.NewBuffer(data) if err := s.bucket.PutObject(storagePath, reader, oss.ContentType(contentType)); err != nil { - return "", 0, err + return "", 0, wrapAliOSSError(err) } return s.location(storagePath), int64(len(data)), nil @@ -63,7 +85,7 @@ func (s *aliOSSStorage) UploadFile(filepath, storagePath, contentType string) (s } if err = s.bucket.PutObjectFromFile(storagePath, filepath, oss.ContentType(contentType)); err != nil { - return "", 0, err + return "", 0, wrapAliOSSError(err) } return s.location(storagePath), info.Size(), nil @@ -82,7 +104,7 @@ func (s *aliOSSStorage) ListObjects(prefix string) ([]string, error) { for { lor, err := s.bucket.ListObjects(oss.Prefix(prefix), marker) if err != nil { - return nil, err + return nil, wrapAliOSSError(err) } for _, object := range lor.Objects { @@ -101,16 +123,20 @@ func (s *aliOSSStorage) ListObjects(prefix string) ([]string, error) { func (s *aliOSSStorage) DownloadData(storagePath string) ([]byte, error) { reader, err := s.bucket.GetObject(storagePath) if err != nil { - return nil, err + return nil, wrapAliOSSError(err) } defer reader.Close() - return io.ReadAll(reader) + data, err := io.ReadAll(reader) + if err != nil { + return nil, wrapAliOSSError(err) + } + return data, nil } func (s *aliOSSStorage) DownloadFile(filepath, storagePath string) (int64, error) { if err := s.bucket.GetObjectToFile(storagePath, filepath); err != nil { - return 0, err + return 0, wrapAliOSSError(err) } info, err := os.Stat(filepath) @@ -122,14 +148,18 @@ func (s *aliOSSStorage) DownloadFile(filepath, storagePath string) (int64, error } func (s *aliOSSStorage) GeneratePresignedUrl(storagePath string, expiration time.Duration) (string, error) { - return s.bucket.SignURL(storagePath, oss.HTTPGet, int64(expiration.Seconds())) + u, err := s.bucket.SignURL(storagePath, oss.HTTPGet, int64(expiration.Seconds())) + if err != nil { + return "", wrapAliOSSError(err) + } + return u, nil } func (s *aliOSSStorage) DeleteObject(storagePath string) error { - return s.bucket.DeleteObject(storagePath) + return wrapAliOSSError(s.bucket.DeleteObject(storagePath)) } func (s *aliOSSStorage) DeleteObjects(storagePaths []string) error { _, err := s.bucket.DeleteObjects(storagePaths) - return err + return wrapAliOSSError(err) } diff --git a/azure.go b/azure.go index 314a6c7..375ec8e 100644 --- a/azure.go +++ b/azure.go @@ -23,6 +23,7 @@ import ( "path" "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" @@ -30,6 +31,20 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" ) +func wrapAzureError(err error) error { + if err == nil { + return nil + } + var respErr *azcore.ResponseError + if errors.As(err, &respErr) { + return &ErrorWithStatusCode{ + Err: err, + StatusCode: respErr.StatusCode, + } + } + return err +} + const azureBlockSize = 4 * 1024 * 1024 const azureParallelism = 16 @@ -73,7 +88,7 @@ func (s *azureBLOBStorage) UploadData(data []byte, storagePath, contentType stri Concurrency: azureParallelism, }) if err != nil { - return "", 0, err + return "", 0, wrapAzureError(err) } return s.location(storagePath), int64(len(data)), nil } @@ -98,7 +113,7 @@ func (s *azureBLOBStorage) UploadFile(filepath, storagePath, contentType string) Concurrency: azureParallelism, }) if err != nil { - return "", 0, err + return "", 0, wrapAzureError(err) } return s.location(storagePath), stat.Size(), nil @@ -113,7 +128,7 @@ func (s *azureBLOBStorage) ListObjects(prefix string) ([]string, error) { for pager.More() { page, err := pager.NextPage(context.Background()) if err != nil { - return nil, err + return nil, wrapAzureError(err) } for _, item := range page.Segment.BlobItems { if item.Name != nil { @@ -131,7 +146,7 @@ func (s *azureBLOBStorage) DownloadData(storagePath string) ([]byte, error) { props, err := blobClient.GetProperties(ctx, nil) if err != nil { - return nil, err + return nil, wrapAzureError(err) } if props.ContentLength == nil { return nil, errors.New("azure: missing content length") @@ -143,7 +158,7 @@ func (s *azureBLOBStorage) DownloadData(storagePath string) ([]byte, error) { Concurrency: azureParallelism, }) if err != nil { - return nil, err + return nil, wrapAzureError(err) } return buf, nil } @@ -160,7 +175,7 @@ func (s *azureBLOBStorage) DownloadFile(filepath, storagePath string) (int64, er Concurrency: azureParallelism, }) if err != nil { - return 0, err + return 0, wrapAzureError(err) } stat, err := file.Stat() @@ -188,7 +203,7 @@ func (s *azureBLOBStorage) GeneratePresignedUrl(storagePath string, expiration t Expiry: to.Ptr(exp.Format(sas.TimeFormat)), }, nil) if err != nil { - return "", err + return "", wrapAzureError(err) } qp, err := sas.BlobSignatureValues{ @@ -214,7 +229,7 @@ func (s *azureBLOBStorage) GeneratePresignedUrl(storagePath string, expiration t func (s *azureBLOBStorage) DeleteObject(storagePath string) error { _, err := s.client.DeleteBlob(context.Background(), s.conf.ContainerName, storagePath, nil) - return err + return wrapAzureError(err) } func (s *azureBLOBStorage) DeleteObjects(storagePaths []string) error { diff --git a/gcp.go b/gcp.go index b417648..c68b32a 100644 --- a/gcp.go +++ b/gcp.go @@ -29,12 +29,33 @@ import ( "cloud.google.com/go/storage" "github.com/googleapis/gax-go/v2" "golang.org/x/oauth2/google" + "google.golang.org/api/googleapi" "google.golang.org/api/iterator" "google.golang.org/api/option" ) const storageScope = "https://www.googleapis.com/auth/devstorage.read_write" +func wrapGCPError(err error) error { + if err == nil { + return nil + } + if errors.Is(err, storage.ErrBucketNotExist) || errors.Is(err, storage.ErrObjectNotExist) { + return &ErrorWithStatusCode{ + Err: err, + StatusCode: http.StatusNotFound, + } + } + var apiErr *googleapi.Error + if errors.As(err, &apiErr) { + return &ErrorWithStatusCode{ + Err: err, + StatusCode: apiErr.Code, + } + } + return err +} + type gcpStorage struct { conf *GCPConfig client *storage.Client @@ -111,11 +132,11 @@ func (s *gcpStorage) upload(reader io.Reader, storagePath, contentType string) ( n, err := io.Copy(wc, reader) if err != nil { - return "", 0, err + return "", 0, wrapGCPError(err) } if err = wc.Close(); err != nil { - return "", 0, err + return "", 0, wrapGCPError(err) } return s.location(storagePath), n, nil @@ -141,7 +162,7 @@ func (s *gcpStorage) ListObjects(prefix string) ([]string, error) { if errors.Is(err, iterator.Done) { return objects, nil } - return nil, err + return nil, wrapGCPError(err) } objects = append(objects, attr.Name) } @@ -154,7 +175,11 @@ func (s *gcpStorage) DownloadData(storagePath string) ([]byte, error) { } defer rc.Close() - return io.ReadAll(rc) + data, err := io.ReadAll(rc) + if err != nil { + return nil, wrapGCPError(err) + } + return data, nil } func (s *gcpStorage) DownloadFile(filepath, storagePath string) (int64, error) { @@ -172,14 +197,14 @@ func (s *gcpStorage) DownloadFile(filepath, storagePath string) (int64, error) { _, err = io.Copy(file, rc) _ = rc.Close() if err != nil { - return 0, err + return 0, wrapGCPError(err) } return rc.Attrs.Size, nil } func (s *gcpStorage) download(storagePath string) (*storage.Reader, error) { - return s.client.Bucket(s.conf.Bucket).Object(storagePath).Retryer( + r, err := s.client.Bucket(s.conf.Bucket).Object(storagePath).Retryer( storage.WithBackoff( gax.Backoff{ Initial: time.Millisecond * 100, @@ -188,24 +213,32 @@ func (s *gcpStorage) download(storagePath string) (*storage.Reader, error) { }), storage.WithPolicy(storage.RetryAlways), ).NewReader(context.Background()) + if err != nil { + return nil, wrapGCPError(err) + } + return r, nil } func (s *gcpStorage) GeneratePresignedUrl(storagePath string, expiration time.Duration) (string, error) { - return s.client.Bucket(s.conf.Bucket).SignedURL(storagePath, &storage.SignedURLOptions{ + u, err := s.client.Bucket(s.conf.Bucket).SignedURL(storagePath, &storage.SignedURLOptions{ Method: "GET", Expires: time.Now().Add(expiration), }) + if err != nil { + return "", wrapGCPError(err) + } + return u, nil } func (s *gcpStorage) DeleteObject(storagePath string) error { - return s.client.Bucket(s.conf.Bucket).Object(storagePath).Delete(context.Background()) + return wrapGCPError(s.client.Bucket(s.conf.Bucket).Object(storagePath).Delete(context.Background())) } func (s *gcpStorage) DeleteObjects(storagePaths []string) error { bucket := s.client.Bucket(s.conf.Bucket) for _, path := range storagePaths { if err := bucket.Object(path).Delete(context.Background()); err != nil { - return err + return wrapGCPError(err) } } return nil diff --git a/s3.go b/s3.go index b5caf30..ecba17d 100644 --- a/s3.go +++ b/s3.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "encoding/base64" + "errors" "fmt" "io" "net/http" @@ -40,6 +41,20 @@ import ( "github.com/aws/smithy-go/middleware" ) +func wrapS3Error(err error) error { + if err == nil { + return nil + } + var sc interface{ HTTPStatusCode() int } + if errors.As(err, &sc) { + return &ErrorWithStatusCode{ + Err: err, + StatusCode: sc.HTTPStatusCode(), + } + } + return err +} + const defaultBucketLocation = "us-east-1" type s3Storage struct { @@ -150,7 +165,7 @@ func updateRegion(awsConf *aws.Config, bucket string) error { resp, err := s3.NewFromConfig(*awsConf).GetBucketLocation(context.Background(), req) if err != nil { - return err + return wrapS3Error(err) } if resp.LocationConstraint != "" { @@ -257,7 +272,7 @@ func (s *s3Storage) upload(reader io.Reader, storagePath, contentType string) (s if _, err := uploader.Upload(context.Background(), input); err != nil { l.WriteLogs() - return "", err + return "", wrapS3Error(err) } return s.location(storagePath), nil @@ -294,7 +309,7 @@ func (s *s3Storage) ListObjects(prefix string) ([]string, error) { for paginator.HasMorePages() { page, err := paginator.NextPage(context.Background()) if err != nil { - return nil, err + return nil, wrapS3Error(err) } for _, obj := range page.Contents { @@ -328,7 +343,7 @@ func (s *s3Storage) DownloadFile(filepath, storagePath string) (int64, error) { func (s *s3Storage) download(w io.WriterAt, storagePath string) (int64, error) { client := s.getClient(nil) - return manager.NewDownloader(client).Download( + n, err := manager.NewDownloader(client).Download( context.Background(), w, &s3.GetObjectInput{ @@ -336,6 +351,7 @@ func (s *s3Storage) download(w io.WriterAt, storagePath string) (int64, error) { Key: aws.String(storagePath), }, ) + return n, wrapS3Error(err) } func (s *s3Storage) GeneratePresignedUrl(storagePath string, expiration time.Duration) (string, error) { @@ -346,7 +362,7 @@ func (s *s3Storage) GeneratePresignedUrl(storagePath string, expiration time.Dur Key: aws.String(storagePath), }, s3.WithPresignExpires(expiration)) if err != nil { - return "", err + return "", wrapS3Error(err) } return res.URL, nil @@ -359,7 +375,7 @@ func (s *s3Storage) DeleteObject(storagePath string) error { Bucket: aws.String(s.conf.Bucket), Key: aws.String(storagePath), }) - return err + return wrapS3Error(err) } func (s *s3Storage) DeleteObjects(storagePaths []string) error { @@ -390,7 +406,7 @@ func (s *s3Storage) DeleteObjects(storagePaths []string) error { _, err := client.DeleteObjects(context.Background(), deleteInput) if err != nil { - return err + return wrapS3Error(err) } } From 37633bc35fa702cd72e71c0867fdffa49c1ffeb0 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 12 Jun 2026 15:22:16 -0700 Subject: [PATCH 2/4] Missing files? --- error.go | 18 ++++++ error_test.go | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 error.go create mode 100644 error_test.go diff --git a/error.go b/error.go new file mode 100644 index 0000000..7b792cb --- /dev/null +++ b/error.go @@ -0,0 +1,18 @@ +package storage + +import "fmt" + +var _ error = (*ErrorWithStatusCode)(nil) + +type ErrorWithStatusCode struct { + Err error + StatusCode int +} + +func (e *ErrorWithStatusCode) Error() string { + return fmt.Sprintf("Err: %s, Status Code: %d", e.Err, e.StatusCode) +} + +func (e *ErrorWithStatusCode) UnWrap() error { + return e.Err +} diff --git a/error_test.go b/error_test.go new file mode 100644 index 0000000..de1cf7f --- /dev/null +++ b/error_test.go @@ -0,0 +1,172 @@ +// Copyright 2026 LiveKit, 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. + +package storage + +import ( + "errors" + "fmt" + "net/http" + "testing" + + "cloud.google.com/go/storage" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/stretchr/testify/require" + "google.golang.org/api/googleapi" +) + +func smithyRespErr(code int) *smithyhttp.ResponseError { + return &smithyhttp.ResponseError{ + Response: &smithyhttp.Response{Response: &http.Response{StatusCode: code}}, + Err: errors.New("inner"), + } +} + +func requireStatus(t *testing.T, err error, wantStatus int, wantInner error) { + t.Helper() + var sce *ErrorWithStatusCode + require.ErrorAs(t, err, &sce) + require.Equal(t, wantStatus, sce.StatusCode) + require.ErrorIs(t, sce.Err, wantInner) +} + +func TestWrapS3Error(t *testing.T) { + t.Run("nil", func(t *testing.T) { + require.NoError(t, wrapS3Error(nil)) + }) + + t.Run("smithy ResponseError", func(t *testing.T) { + inner := smithyRespErr(404) + requireStatus(t, wrapS3Error(inner), 404, inner) + }) + + t.Run("aws ResponseError", func(t *testing.T) { + inner := &awshttp.ResponseError{ResponseError: smithyRespErr(503), RequestID: "req-1"} + requireStatus(t, wrapS3Error(inner), 503, inner) + }) + + t.Run("wrapped via fmt.Errorf", func(t *testing.T) { + inner := smithyRespErr(403) + wrapped := fmt.Errorf("operation failed: %w", inner) + requireStatus(t, wrapS3Error(wrapped), 403, inner) + }) + + t.Run("plain error passes through", func(t *testing.T) { + plain := errors.New("network down") + got := wrapS3Error(plain) + require.Same(t, plain, got) + var sce *ErrorWithStatusCode + require.False(t, errors.As(got, &sce)) + }) +} + +func TestWrapGCPError(t *testing.T) { + t.Run("nil", func(t *testing.T) { + require.NoError(t, wrapGCPError(nil)) + }) + + t.Run("ErrBucketNotExist", func(t *testing.T) { + requireStatus(t, wrapGCPError(storage.ErrBucketNotExist), http.StatusNotFound, storage.ErrBucketNotExist) + }) + + t.Run("ErrObjectNotExist", func(t *testing.T) { + requireStatus(t, wrapGCPError(storage.ErrObjectNotExist), http.StatusNotFound, storage.ErrObjectNotExist) + }) + + t.Run("wrapped ErrObjectNotExist", func(t *testing.T) { + wrapped := fmt.Errorf("read object: %w", storage.ErrObjectNotExist) + requireStatus(t, wrapGCPError(wrapped), http.StatusNotFound, storage.ErrObjectNotExist) + }) + + t.Run("googleapi.Error", func(t *testing.T) { + inner := &googleapi.Error{Code: 500, Message: "boom"} + requireStatus(t, wrapGCPError(inner), 500, inner) + }) + + t.Run("wrapped googleapi.Error", func(t *testing.T) { + inner := &googleapi.Error{Code: 403, Message: "forbidden"} + wrapped := fmt.Errorf("delete failed: %w", inner) + requireStatus(t, wrapGCPError(wrapped), 403, inner) + }) + + t.Run("plain error passes through", func(t *testing.T) { + plain := errors.New("dial timeout") + got := wrapGCPError(plain) + require.Same(t, plain, got) + var sce *ErrorWithStatusCode + require.False(t, errors.As(got, &sce)) + }) +} + +func TestWrapAzureError(t *testing.T) { + t.Run("nil", func(t *testing.T) { + require.NoError(t, wrapAzureError(nil)) + }) + + t.Run("azcore ResponseError", func(t *testing.T) { + inner := &azcore.ResponseError{StatusCode: 409, ErrorCode: "BlobAlreadyExists"} + requireStatus(t, wrapAzureError(inner), 409, inner) + }) + + t.Run("wrapped azcore ResponseError", func(t *testing.T) { + inner := &azcore.ResponseError{StatusCode: 404, ErrorCode: "BlobNotFound"} + wrapped := fmt.Errorf("download: %w", inner) + requireStatus(t, wrapAzureError(wrapped), 404, inner) + }) + + t.Run("plain error passes through", func(t *testing.T) { + plain := errors.New("connection reset") + got := wrapAzureError(plain) + require.Same(t, plain, got) + var sce *ErrorWithStatusCode + require.False(t, errors.As(got, &sce)) + }) +} + +func TestWrapAliOSSError(t *testing.T) { + t.Run("nil", func(t *testing.T) { + require.NoError(t, wrapAliOSSError(nil)) + }) + + t.Run("ServiceError", func(t *testing.T) { + inner := oss.ServiceError{StatusCode: 403, Code: "AccessDenied", Message: "denied"} + requireStatus(t, wrapAliOSSError(inner), 403, inner) + }) + + t.Run("wrapped ServiceError", func(t *testing.T) { + inner := oss.ServiceError{StatusCode: 404, Code: "NoSuchKey"} + wrapped := fmt.Errorf("get: %w", inner) + requireStatus(t, wrapAliOSSError(wrapped), 404, inner) + }) + + t.Run("UnexpectedStatusCodeError", func(t *testing.T) { + // CheckRespCode returns an UnexpectedStatusCodeError when respCode is not in allowed. + inner := oss.CheckRespCode(500, []int{200}) + require.Error(t, inner) + var sce *ErrorWithStatusCode + require.ErrorAs(t, wrapAliOSSError(inner), &sce) + require.Equal(t, 500, sce.StatusCode) + }) + + t.Run("plain error passes through", func(t *testing.T) { + plain := errors.New("eof") + got := wrapAliOSSError(plain) + require.Same(t, plain, got) + var sce *ErrorWithStatusCode + require.False(t, errors.As(got, &sce)) + }) +} From 3761236cf608d82a5ca40d5430ed474a9caeed4c Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Wed, 17 Jun 2026 12:30:01 -0700 Subject: [PATCH 3/4] Fix unwrap --- error.go | 2 +- error_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/error.go b/error.go index 7b792cb..927d7c1 100644 --- a/error.go +++ b/error.go @@ -13,6 +13,6 @@ func (e *ErrorWithStatusCode) Error() string { return fmt.Sprintf("Err: %s, Status Code: %d", e.Err, e.StatusCode) } -func (e *ErrorWithStatusCode) UnWrap() error { +func (e *ErrorWithStatusCode) Unwrap() error { return e.Err } diff --git a/error_test.go b/error_test.go index de1cf7f..9649c94 100644 --- a/error_test.go +++ b/error_test.go @@ -44,6 +44,36 @@ func requireStatus(t *testing.T, err error, wantStatus int, wantInner error) { require.ErrorIs(t, sce.Err, wantInner) } +type customError struct{ msg string } + +func (c *customError) Error() string { return c.msg } + +func TestErrorWithStatusCodeUnwrap(t *testing.T) { + sentinel := errors.New("sentinel") + err := &ErrorWithStatusCode{Err: sentinel, StatusCode: 418} + + t.Run("errors.Is finds wrapped sentinel", func(t *testing.T) { + require.ErrorIs(t, err, sentinel) + }) + + t.Run("errors.Is through fmt.Errorf wrap", func(t *testing.T) { + wrapped := fmt.Errorf("outer: %w", err) + require.ErrorIs(t, wrapped, sentinel) + }) + + t.Run("errors.As extracts inner error type", func(t *testing.T) { + inner := &customError{msg: "boom"} + wrapped := &ErrorWithStatusCode{Err: inner, StatusCode: 500} + var ce *customError + require.ErrorAs(t, wrapped, &ce) + require.Same(t, inner, ce) + }) + + t.Run("Unwrap returns inner error", func(t *testing.T) { + require.Same(t, sentinel, errors.Unwrap(err)) + }) +} + func TestWrapS3Error(t *testing.T) { t.Run("nil", func(t *testing.T) { require.NoError(t, wrapS3Error(nil)) From 7543e1b207672c658b2d5ab985f0c1ef473223b6 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Wed, 17 Jun 2026 12:32:49 -0700 Subject: [PATCH 4/4] Address PR feedback: add license header, lowercase error string Co-Authored-By: Claude Opus 4.7 (1M context) --- error.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/error.go b/error.go index 927d7c1..99deaed 100644 --- a/error.go +++ b/error.go @@ -1,3 +1,17 @@ +// Copyright 2026 LiveKit, 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. + package storage import "fmt" @@ -10,7 +24,7 @@ type ErrorWithStatusCode struct { } func (e *ErrorWithStatusCode) Error() string { - return fmt.Sprintf("Err: %s, Status Code: %d", e.Err, e.StatusCode) + return fmt.Sprintf("err: %s, status code: %d", e.Err, e.StatusCode) } func (e *ErrorWithStatusCode) Unwrap() error {