Skip to content

Commit e728250

Browse files
stefanhahmannclaude
andcommitted
Route S3 store-access errors through a backend-agnostic exception
The original "Catch StoreException / N5Exception" commit caught both backend-specific exceptions directly in the opener. On main the Fiji opener (ZarrOpener, ome-zarr-fiji) was decoupled from the concrete backends: it depends on the ome-zarr-imglib2 API only, and the zarr-java backend is test-scoped, so dev.zarr.zarrjava.store.StoreException is no longer on its compile classpath. Introduce ome.zarr.imglib2.exceptions.StoreAccessException (mirroring the existing PyramidLevelAccessException) and wrap store-open failures into it inside each backend's load(): the zarr-java backend wraps StoreException, the N5 backend wraps N5Exception thrown while opening the reader / reading multiscale metadata. ZarrOpener now catches the backend-agnostic StoreAccessException, keeping the decoupling intact while preserving the original intent: report S3 auth failures, missing buckets and network errors as a clean user-facing message instead of an unhandled exception. The store-access test mocks the backend to throw StoreAccessException (what a real load() now surfaces to the opener). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent cc6d627 commit e728250

5 files changed

Lines changed: 87 additions & 10 deletions

File tree

ome-zarr-fiji-ui/src/test/java/ome/zarr/fijiui/open/ZarrOpenActionsTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@
9696
import bdv.viewer.ViewerFrame;
9797
import bdv.util.BdvStackSource;
9898
import ij.ImagePlus;
99-
import dev.zarr.zarrjava.store.StoreException;
10099
import ome.zarr.fijiui.settings.UserScriptSettings;
101100
import ome.zarr.fiji.Pyramidal;
102101
import ome.zarr.zarrjava.ZarrJavaPyramidBackend;
102+
import ome.zarr.imglib2.exceptions.StoreAccessException;
103103
import ome.zarr.imglib2.PyramidContents;
104104
import ome.zarr.fiji.PyramidalBdv;
105105
import ome.zarr.fiji.PyramidalDataset;
@@ -703,7 +703,8 @@ void storeAccessErrorIsReportedToErrorHandler( final ZarrReaderBackend backend )
703703
try ( MockedConstruction< ZarrJavaPyramidBackend > mock = mockConstruction(
704704
ZarrJavaPyramidBackend.class,
705705
( mockBackend, ctx ) -> when( mockBackend.load( any() ) )
706-
.thenThrow( new StoreException( "Access Denied (403)" ) ) ) )
706+
.thenThrow( new StoreAccessException( uri.toString(),
707+
new RuntimeException( "Access Denied (403)" ) ) ) ) )
707708
{
708709
final ZarrOpenActions actions = new ZarrOpenActions( uri, context, settings, capturedError::set );
709710
assertDoesNotThrow( () -> actions.openImage( dataset -> null, img -> null ) );

ome-zarr-fiji/src/main/java/ome/zarr/fiji/open/ZarrOpener.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@
6060
import ome.zarr.fiji.open.exceptions.NonExistingResolutionLevelException;
6161
import ome.zarr.fiji.open.exceptions.NotASingleScaleImageException;
6262
import ome.zarr.fiji.util.BdvUtils;
63-
import dev.zarr.zarrjava.store.StoreException;
64-
import org.janelia.saalfeldlab.n5.N5Exception;
63+
import ome.zarr.imglib2.exceptions.StoreAccessException;
6564

6665
/**
6766
* Backend-reader-agnostic opener for OME-Zarr datasets.
@@ -247,7 +246,7 @@ private Object openPyramidImage( final Supplier< Object > multiScaleOpener, fina
247246
showSingleScaleNotSupported();
248247
// TODO: openSingleScaleImage( singleScaleOpener ) when single-scale support is added
249248
}
250-
catch ( StoreException | N5Exception e )
249+
catch ( StoreAccessException e )
251250
{
252251
showStoreAccessError( e );
253252
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*-
2+
* #%L
3+
* OME-Zarr extras for Fiji
4+
* %%
5+
* Copyright (C) 2022 - 2026 SciJava developers
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
package ome.zarr.imglib2.exceptions;
30+
31+
/**
32+
* Thrown when a pyramid backend cannot access the OME-Zarr store itself —
33+
* e.g. an S3 authentication failure, a missing bucket, a network error, or any
34+
* other failure that prevents even opening the dataset root. Distinct from
35+
* {@link PyramidLevelAccessException}, which signals a failure to read an
36+
* individual resolution level after the store was reached.
37+
* <p>
38+
* This is a backend-agnostic wrapper so that the Fiji layer can report store
39+
* access failures without depending on backend-specific exception types (e.g.
40+
* zarr-java's {@code StoreException} or N5's {@code N5Exception}).
41+
*/
42+
public class StoreAccessException extends RuntimeException
43+
{
44+
45+
public StoreAccessException( final String path, final Throwable cause )
46+
{
47+
super( "Cannot access OME-Zarr store at: " + path, cause );
48+
}
49+
}

ome-zarr-n5/src/main/java/ome/zarr/n5/N5PyramidBackend.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import net.imglib2.util.Cast;
3636

3737
import org.janelia.saalfeldlab.n5.DataType;
38+
import org.janelia.saalfeldlab.n5.N5Exception;
3839
import org.janelia.saalfeldlab.n5.N5Reader;
3940
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
4041
import org.janelia.saalfeldlab.n5.universe.N5DatasetDiscoverer;
@@ -67,6 +68,7 @@
6768
import ome.zarr.imglib2.PyramidContents;
6869
import ome.zarr.imglib2.exceptions.MultiImageDatasetException;
6970
import ome.zarr.imglib2.exceptions.NotAMultiscaleImageException;
71+
import ome.zarr.imglib2.exceptions.StoreAccessException;
7072
import ome.zarr.imglib2.metadata.AxisCalibration;
7173
import ome.zarr.imglib2.metadata.Omero;
7274

@@ -96,11 +98,24 @@ public static < T extends NativeType< T > & RealType< T > > PyramidContents< T >
9698
@Override
9799
public < T extends NativeType< T > & RealType< T > > PyramidContents< T > load( final URI inputUri )
98100
{
99-
final N5Reader reader = new N5Factory()
100-
.s3Configuration( builder -> builder.region( Region.US_EAST_1 ) )
101-
.openReader( inputUri.toString() );
101+
final N5Reader reader;
102102
final N5TreeNode treeNode = new N5TreeNode( "" );
103-
final OmeNgffMetadata metadata = readMetadata( reader, treeNode, inputUri );
103+
final OmeNgffMetadata metadata;
104+
try
105+
{
106+
reader = new N5Factory()
107+
.s3Configuration( builder -> builder.region( Region.US_EAST_1 ) )
108+
.openReader( inputUri.toString() );
109+
metadata = readMetadata( reader, treeNode, inputUri );
110+
}
111+
catch ( N5Exception e )
112+
{
113+
// Store-level failure (e.g. S3 auth failure, missing bucket, network
114+
// error) before we could reach the dataset. Wrap in a backend-agnostic
115+
// exception so the Fiji layer can report it without depending on N5's
116+
// N5Exception type.
117+
throw new StoreAccessException( inputUri.toString(), e );
118+
}
104119
final Multiscale multiscale = buildMultiscale( metadata, 0 );
105120
final Omero omero = readOmeroMetadata( reader, treeNode );
106121

ome-zarr-zarrjava/src/main/java/ome/zarr/zarrjava/ZarrJavaPyramidBackend.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import dev.zarr.zarrjava.store.HttpStore;
5656
import dev.zarr.zarrjava.store.S3Store;
5757
import dev.zarr.zarrjava.store.Store;
58+
import dev.zarr.zarrjava.store.StoreException;
5859
import dev.zarr.zarrjava.store.StoreHandle;
5960

6061
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
@@ -87,6 +88,7 @@
8788
import ome.zarr.imglib2.exceptions.MultiImageDatasetException;
8889
import ome.zarr.imglib2.exceptions.NotAMultiscaleImageException;
8990
import ome.zarr.imglib2.exceptions.PyramidLevelAccessException;
91+
import ome.zarr.imglib2.exceptions.StoreAccessException;
9092
import ome.zarr.imglib2.PyramidBackend;
9193
import ome.zarr.imglib2.PyramidContents;
9294
import ome.zarr.imglib2.metadata.AxisCalibration;
@@ -253,7 +255,18 @@ else if ( "s3".equalsIgnoreCase( scheme ) )
253255
}
254256
else
255257
throw new IllegalArgumentException( "Unsupported URI scheme '" + scheme + "' for OME-Zarr location: " + inputUri );
256-
return openMultiscaleImageFromHandle( store.resolve() );
258+
try
259+
{
260+
return openMultiscaleImageFromHandle( store.resolve() );
261+
}
262+
catch ( StoreException e )
263+
{
264+
// Store-level failure (e.g. S3 auth failure, missing bucket, network
265+
// error) before we could reach the dataset. Wrap in a backend-agnostic
266+
// exception so the Fiji layer can report it without depending on
267+
// zarr-java's StoreException type.
268+
throw new StoreAccessException( inputUri.toString(), e );
269+
}
257270
}
258271

259272
private MultiscaleImage openMultiscaleImageFromHandle( final StoreHandle handle )

0 commit comments

Comments
 (0)