This is an automated email from the ASF dual-hosted git repository.
laskoviymishka pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git
The following commit(s) were added to refs/heads/main by this push:
new 5079bc105 fix(io): wrap blob Open failures as path errors (#1577)
5079bc105 is described below
commit 5079bc105297667ad0e4fcdfe3d1d3748fda3df7
Author: Minh Vu <[email protected]>
AuthorDate: Tue Aug 4 12:32:22 2026 +0200
fix(io): wrap blob Open failures as path errors (#1577)
## What changed
Wrap blob reader failures from `BlobFileIO.Open` with the existing
filesystem error adapter and retain the caller-provided path for
preprocessing, validation, and backend failures.
## Why
Backend errors were returned directly, contrary to the `IO.Open`
contract. Missing objects did not consistently expose `fs.ErrNotExist`
through a `*fs.PathError`, and preprocessing could replace the public
path with an empty or internal value.
## Testing
- `go test ./io/gocloud -run
'TestBlobFileIOOpenMissingReturnsPathError|TestDefaultKeyExtractor'`
- `go vet ./io/gocloud`
---------
Signed-off-by: Minh Vu <[email protected]>
---
io/gocloud/blob.go | 7 ++++---
io/gocloud/blob_test.go | 34 ++++++++++++++++++++++++++++++++++
2 files changed, 38 insertions(+), 3 deletions(-)
diff --git a/io/gocloud/blob.go b/io/gocloud/blob.go
index fc38185fb..46a3083b6 100644
--- a/io/gocloud/blob.go
+++ b/io/gocloud/blob.go
@@ -249,20 +249,21 @@ func directoryName(key string) string {
}
func (bfs *BlobFileIO) Open(path string) (icebergio.File, error) {
+ originalPath := path
var err error
path, err = bfs.preprocess(path)
if err != nil {
- return nil, &fs.PathError{Op: "open", Path: path, Err: err}
+ return nil, &fs.PathError{Op: "open", Path: originalPath, Err:
err}
}
if !fs.ValidPath(path) {
- return nil, &fs.PathError{Op: "open", Path: path, Err:
fs.ErrInvalid}
+ return nil, &fs.PathError{Op: "open", Path: originalPath, Err:
fs.ErrInvalid}
}
key, name := path, pathpkg.Base(path)
r, err := bfs.NewReader(bfs.ctx, key, nil)
if err != nil {
- return nil, err
+ return nil, blobErrToFsErr("open", originalPath, err)
}
return &blobOpenFile{Reader: r, name: name, key: key, b: bfs, ctx:
bfs.ctx}, nil
diff --git a/io/gocloud/blob_test.go b/io/gocloud/blob_test.go
index ef9f16e98..ba6bf399d 100644
--- a/io/gocloud/blob_test.go
+++ b/io/gocloud/blob_test.go
@@ -162,6 +162,40 @@ func TestDefaultKeyExtractor(t *testing.T) {
}
}
+func TestBlobFileIOOpenMissingReturnsPathError(t *testing.T) {
+ t.Parallel()
+
+ bucket := memblob.OpenBucket(nil)
+ t.Cleanup(func() { require.NoError(t, bucket.Close()) })
+ fileIO := testBlobFileIO(context.Background(), "my-bucket", bucket)
+ name := "s3://my-bucket/missing.parquet"
+
+ _, err := fileIO.Open(name)
+ require.ErrorIs(t, err, fs.ErrNotExist)
+
+ var pathErr *fs.PathError
+ require.ErrorAs(t, err, &pathErr)
+ assert.Equal(t, "open", pathErr.Op)
+ assert.Equal(t, name, pathErr.Path)
+}
+
+func TestBlobFileIOOpenPreprocessErrorRetainsOriginalPath(t *testing.T) {
+ t.Parallel()
+
+ bucket := memblob.OpenBucket(nil)
+ t.Cleanup(func() { require.NoError(t, bucket.Close()) })
+ fileIO := testBlobFileIO(context.Background(), "my-bucket", bucket)
+ name := "s3://other-bucket/file.parquet"
+
+ _, err := fileIO.Open(name)
+ require.Error(t, err)
+
+ var pathErr *fs.PathError
+ require.ErrorAs(t, err, &pathErr)
+ assert.Equal(t, "open", pathErr.Op)
+ assert.Equal(t, name, pathErr.Path)
+}
+
func testBlobFileIO(ctx context.Context, bucketName string, bucket
*blob.Bucket, allowedSchemes ...string) *BlobFileIO {
if len(allowedSchemes) == 0 {
allowedSchemes = s3Schemes