diff --git a/Storage/Exceptions/FailureHint.cs b/Storage/Exceptions/FailureHint.cs
index 4bfd3a8..9f98533 100644
--- a/Storage/Exceptions/FailureHint.cs
+++ b/Storage/Exceptions/FailureHint.cs
@@ -3,18 +3,60 @@
namespace Supabase.Storage.Exceptions
{
+ ///
+ /// Maps a failed storage response onto a coarse a caller can branch on.
+ ///
public static class FailureHint
{
+ ///
+ /// A coarse classification of why a storage request failed, derived from its status code and body.
+ ///
public enum Reason
{
+ ///
+ /// The failure could not be attributed to a known cause — the status and body matched no
+ /// recognised pattern, or the response carried no body to inspect.
+ ///
Unknown,
+
+ ///
+ /// The request was rejected for authentication or authorization reasons (HTTP 401, or a
+ /// 400/403 whose body names an auth cause such as a missing or malformed token).
+ ///
NotAuthorized,
+
+ ///
+ /// The storage service failed internally (HTTP 500).
+ ///
Internal,
+
+ ///
+ /// The requested object or bucket does not exist (HTTP 404).
+ ///
NotFound,
+
+ ///
+ /// The resource being created already exists (HTTP 409).
+ ///
AlreadyExists,
- InvalidInput
+
+ ///
+ /// The request was rejected as invalid (an HTTP 400 whose body indicates invalid input).
+ ///
+ InvalidInput,
+
+ ///
+ /// The upload exceeded the gateway's request-size limit (HTTP 413). Retry it through a
+ /// resumable upload (UploadOrResume) rather than a single request.
+ ///
+ EntityTooLarge
}
+ ///
+ /// Classifies a failed storage request from its status code and response body.
+ ///
+ /// The failure to classify.
+ /// The matching , or when nothing matches.
public static Reason DetectReason(SupabaseStorageException storageException)
{
if (storageException.Content == null)
@@ -31,6 +73,7 @@ 403 when storageException.Content.ToLower().Contains("invalid compact jws") => N
403 when storageException.Content.ToLower().Contains("signature verification failed") => NotAuthorized,
404 when storageException.Content.ToLower().Contains("not found") => NotFound,
409 when storageException.Content.ToLower().Contains("exists") => AlreadyExists,
+ 413 => EntityTooLarge,
500 => Internal,
_ => Unknown
};
diff --git a/Storage/StorageFileApi.cs b/Storage/StorageFileApi.cs
index 31d8fe3..77282fa 100644
--- a/Storage/StorageFileApi.cs
+++ b/Storage/StorageFileApi.cs
@@ -246,6 +246,12 @@ public async Task CreateSignedUrl(
///
/// Uploads a file to an existing bucket.
///
+ ///
+ /// This is a single-request upload and is subject to the gateway's request-size limit; a file
+ /// past that limit fails with a of
+ /// . For large files use the resumable
+ /// .
+ ///
/// File Source Path
/// The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload.
///
@@ -274,6 +280,12 @@ public async Task Upload(
///
/// Uploads a byte array to an existing bucket.
///
+ ///
+ /// This is a single-request upload and is subject to the gateway's request-size limit; data
+ /// past that limit fails with a of
+ /// . For large payloads use the resumable
+ /// .
+ ///
///
/// The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload.
///
diff --git a/StorageTests/Errors/FailureHintTests.cs b/StorageTests/Errors/FailureHintTests.cs
index 1749574..064a11b 100644
--- a/StorageTests/Errors/FailureHintTests.cs
+++ b/StorageTests/Errors/FailureHintTests.cs
@@ -63,6 +63,12 @@ public void DetectReason_ShouldReturnNotFound_Given404NotFound() =>
public void DetectReason_ShouldReturnAlreadyExists_Given409Exists() =>
FailureHint.DetectReason(Failure(409, "The resource already exists")).Should().Be(Reason.AlreadyExists);
+ [TestMethod]
+ public void DetectReason_ShouldReturnEntityTooLarge_Given413() =>
+ FailureHint.DetectReason(Failure(413, "413 Request Entity Too Large")).Should()
+ .Be(Reason.EntityTooLarge,
+ "a 413 is the oversized-upload signal that should steer callers to a resumable upload (issue #14)");
+
[TestMethod]
public void DetectReason_ShouldReturnInternal_Given500() =>
FailureHint.DetectReason(Failure(500, "boom")).Should().Be(Reason.Internal);
diff --git a/StorageTests/Files/StorageFileApiContractTests.cs b/StorageTests/Files/StorageFileApiContractTests.cs
index 7d7f9cb..2f39332 100644
--- a/StorageTests/Files/StorageFileApiContractTests.cs
+++ b/StorageTests/Files/StorageFileApiContractTests.cs
@@ -336,6 +336,23 @@ public async Task PurgeCache_ShouldDeleteTheCdnObjectPathWithNoBodyAndReturnTheM
}
}
+ [TestMethod]
+ public async Task Upload_ShouldSurfaceStorageException_GivenNonJsonError()
+ {
+ const string body = "413 Request Entity Too Large";
+ this.server.Given(Request.Create().WithPath($"/storage/v1/object/{Bucket}/big.bin").UsingPost())
+ .RespondWith(Response.Create().WithStatusCode(413).WithHeader("Content-Type", "text/html").WithBody(body));
+ var act = () => this.client.From(Bucket).Upload(new byte[] { 0x1 }, "big.bin");
+ var exception = (await act.Should().ThrowAsync(
+ "an oversized upload returns a non-JSON gateway error that must not crash JSON parsing (issue #14)")).Which;
+ using (new AssertionScope())
+ {
+ exception.StatusCode.Should().Be(413);
+ exception.Content.Should().Be(body);
+ exception.Reason.Should().Be(FailureHint.Reason.EntityTooLarge);
+ }
+ }
+
[TestMethod]
public async Task List_ShouldSurfaceStorageException_GivenNonJsonError()
{