diff --git a/packages/storage_client/lib/src/storage_file_api.dart b/packages/storage_client/lib/src/storage_file_api.dart index ea57554b0..3573339d0 100644 --- a/packages/storage_client/lib/src/storage_file_api.dart +++ b/packages/storage_client/lib/src/storage_file_api.dart @@ -366,7 +366,11 @@ class StorageFileApi { }, options: options, ); - final signedUrlPath = (response as Map)['signedURL']; + final signedUrlPath = + (response as Map)['signedURL'] as String?; + if (signedUrlPath == null) { + throw StorageException('No signed URL returned by API'); + } final signedUrl = '$url$signedUrlPath'; return signedUrl; } @@ -395,11 +399,11 @@ class StorageFileApi { options: options, ); final List urls = (response as List).map((e) { + final signedUrlPath = e['signedURL'] as String?; return SignedUrl( - // Prevents exceptions being thrown when null value is returned - // https://github.com/supabase/storage-api/issues/353 path: e['path'] ?? '', - signedUrl: '$url${e['signedURL']}', + signedUrl: signedUrlPath != null ? '$url$signedUrlPath' : null, + error: e['error'] as String?, ); }).toList(); return urls; diff --git a/packages/storage_client/lib/src/types.dart b/packages/storage_client/lib/src/types.dart index 41d71b6f5..46ed4136e 100644 --- a/packages/storage_client/lib/src/types.dart +++ b/packages/storage_client/lib/src/types.dart @@ -246,16 +246,22 @@ class SignedUrl { /// The file path, including the current file name. For example `folder/image.png`. final String path; - /// Full signed URL of the files. - final String signedUrl; + /// Full signed URL of the file. Null when the path does not exist or the + /// server returned an error for this item; check [error] for details. + final String? signedUrl; + + /// Per-item error message returned by the server when [signedUrl] is null. + final String? error; const SignedUrl({ required this.path, required this.signedUrl, + this.error, }); @override - String toString() => 'SignedUrl(path: $path, signedUrl: $signedUrl)'; + String toString() => + 'SignedUrl(path: $path, signedUrl: $signedUrl, error: $error)'; @override bool operator ==(Object other) { @@ -263,19 +269,22 @@ class SignedUrl { return other is SignedUrl && other.path == path && - other.signedUrl == signedUrl; + other.signedUrl == signedUrl && + other.error == error; } @override - int get hashCode => path.hashCode ^ signedUrl.hashCode; + int get hashCode => path.hashCode ^ signedUrl.hashCode ^ error.hashCode; SignedUrl copyWith({ String? path, String? signedUrl, + String? error, }) { return SignedUrl( path: path ?? this.path, signedUrl: signedUrl ?? this.signedUrl, + error: error ?? this.error, ); } } diff --git a/packages/storage_client/test/basic_test.dart b/packages/storage_client/test/basic_test.dart index 168606ede..b233983e3 100644 --- a/packages/storage_client/test/basic_test.dart +++ b/packages/storage_client/test/basic_test.dart @@ -129,10 +129,51 @@ void main() { }); test('should createSignedUrl file', () async { - customHttpClient.response = {'signedURL': 'url'}; + customHttpClient.response = {'signedURL': '/signed/url'}; final response = await client.from('public').createSignedUrl('b.txt', 60); expect(response, isA()); + expect(response, endsWith('/signed/url')); + }); + + test('createSignedUrl throws StorageException when signedURL is null', + () async { + customHttpClient.response = {'signedURL': null}; + + expect( + () => client.from('public').createSignedUrl('missing.txt', 60), + throwsA(isA()), + ); + }); + + test( + 'createSignedUrls returns null signedUrl and error for missing path', + () async { + customHttpClient.response = [ + { + 'path': 'exists.txt', + 'signedURL': + '/storage/v1/object/sign/public/exists.txt?token=abc', + }, + { + 'path': 'missing.txt', + 'signedURL': null, + 'error': 'not_found', + }, + ]; + + final urls = await client + .from('public') + .createSignedUrls(['exists.txt', 'missing.txt'], 60); + + expect(urls.length, 2); + expect(urls[0].signedUrl, isNotNull); + expect( + urls[0].signedUrl, + '$supabaseUrl/storage/v1/storage/v1/object/sign/public/exists.txt?token=abc', + ); + expect(urls[1].signedUrl, isNull); + expect(urls[1].error, 'not_found'); }); test('should list files', () async {