Skip to content

Commit 23c8b2e

Browse files
refactor(Storage): Introduce RejectAndThrow upload validation mode and deprecate legacy modes
Introduces `UploadValidationMode.RejectAndThrow` to leverage server-side validation, ensuring invalid uploads are rejected automatically before object creation. Updates `UploadObjectOptions` to use this new mode as the default behavior. Updates `UploadObjectTest`. Deprecated the legacy `ThrowOnly` and `DeleteAndThrow` modes, as server-side validation effectively unifies their end-state behaviors. BREAKING CHANGE: The default upload validation mode has been switched to `RejectAndThrow`. Under this mode, objects with mismatched hashes will no longer be created in the bucket at all, changing the legacy `ThrowOnly` behavior which used to leave the invalid object intact.
1 parent 863ba52 commit 23c8b2e

3 files changed

Lines changed: 17 additions & 40 deletions

File tree

apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/UploadObjectTest.cs

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -300,35 +300,19 @@ public void UploadObject_InvalidHash_None()
300300
}
301301

302302
[Fact]
303-
public void UploadObject_InvalidHash_ThrowOnly()
303+
public void UploadObject_InvalidHash_RejectAndThrow()
304304
{
305305
var client = StorageClient.Create();
306306
var interceptor = new BreakUploadInterceptor();
307307
client.Service.HttpClient.MessageHandler.AddExecuteInterceptor(interceptor);
308308
var stream = GenerateData(50);
309309
var name = IdGenerator.FromGuid();
310310
var bucket = _fixture.MultiVersionBucket;
311-
var options = new UploadObjectOptions { UploadValidationMode = UploadValidationMode.ThrowOnly };
311+
var options = new UploadObjectOptions { UploadValidationMode = UploadValidationMode.RejectAndThrow };
312312
var exception = Assert.Throws<GoogleApiException>(() => client.UploadObject(bucket, name, null, stream, options));
313313
Assert.Equal(HttpStatusCode.BadRequest, exception.HttpStatusCode);
314314
}
315315

316-
[Fact]
317-
public void UploadObject_InvalidHash_DeleteAndThrow()
318-
{
319-
var client = StorageClient.Create();
320-
var interceptor = new BreakUploadInterceptor();
321-
client.Service.HttpClient.MessageHandler.AddExecuteInterceptor(interceptor);
322-
var stream = GenerateData(50);
323-
var name = IdGenerator.FromGuid();
324-
var bucket = _fixture.MultiVersionBucket;
325-
var options = new UploadObjectOptions { UploadValidationMode = UploadValidationMode.DeleteAndThrow };
326-
var exception = Assert.Throws<GoogleApiException>(() => client.UploadObject(bucket, name, null, stream, options));
327-
Assert.Equal(HttpStatusCode.BadRequest, exception.HttpStatusCode);
328-
var notFound = Assert.Throws<GoogleApiException>(() => _fixture.Client.GetObject(bucket, name));
329-
Assert.Equal(HttpStatusCode.NotFound, notFound.HttpStatusCode);
330-
}
331-
332316
[Fact]
333317
public async Task UploadObjectAsync_InvalidHash_None()
334318
{
@@ -346,34 +330,17 @@ public async Task UploadObjectAsync_InvalidHash_None()
346330
}
347331

348332
[Fact]
349-
public async Task UploadObjectAsync_InvalidHash_ThrowOnly()
333+
public async Task UploadObjectAsync_InvalidHash_RejectAndThrow()
350334
{
351335
var client = StorageClient.Create();
352336
var interceptor = new BreakUploadInterceptor();
353337
client.Service.HttpClient.MessageHandler.AddExecuteInterceptor(interceptor);
354338
var stream = GenerateData(50);
355339
var name = IdGenerator.FromGuid();
356340
var bucket = _fixture.MultiVersionBucket;
357-
var options = new UploadObjectOptions { UploadValidationMode = UploadValidationMode.ThrowOnly };
358-
var exception = await Assert.ThrowsAsync<GoogleApiException>(() => client.UploadObjectAsync(bucket, name, null, stream, options));
359-
Assert.Equal(HttpStatusCode.BadRequest, exception.HttpStatusCode);
360-
}
361-
362-
[Fact]
363-
public async Task UploadObjectAsync_InvalidHash_DeleteAndThrow()
364-
{
365-
var client = StorageClient.Create();
366-
var interceptor = new BreakUploadInterceptor();
367-
client.Service.HttpClient.MessageHandler.AddExecuteInterceptor(interceptor);
368-
369-
var stream = GenerateData(50);
370-
var name = IdGenerator.FromGuid();
371-
var bucket = _fixture.MultiVersionBucket;
372-
var options = new UploadObjectOptions { UploadValidationMode = UploadValidationMode.DeleteAndThrow };
341+
var options = new UploadObjectOptions { UploadValidationMode = UploadValidationMode.RejectAndThrow };
373342
var exception = await Assert.ThrowsAsync<GoogleApiException>(() => client.UploadObjectAsync(bucket, name, null, stream, options));
374343
Assert.Equal(HttpStatusCode.BadRequest, exception.HttpStatusCode);
375-
var notFound = await Assert.ThrowsAsync<GoogleApiException>(() => _fixture.Client.GetObjectAsync(bucket, name));
376-
Assert.Equal(HttpStatusCode.NotFound, notFound.HttpStatusCode);
377344
}
378345

379346
[Fact]

apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/UploadObjectOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public sealed class UploadObjectOptions
3232
/// </summary>
3333
public const int MinimumChunkSize = ResumableUpload<Object>.MinimumChunkSize;
3434

35-
internal static UploadValidationMode DefaultValidationMode { get; } = V1.UploadValidationMode.DeleteAndThrow;
35+
internal static UploadValidationMode DefaultValidationMode { get; } = V1.UploadValidationMode.RejectAndThrow;
3636

3737
/// <summary>
3838
/// Precondition for upload: the object is only uploaded if the existing object's

apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/UploadValidationMode.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2017 Google Inc. All Rights Reserved.
1+
// Copyright 2017 Google Inc. All Rights Reserved.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -12,6 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
using System;
16+
1517
namespace Google.Cloud.Storage.V1
1618
{
1719
/// <summary>
@@ -29,6 +31,7 @@ public enum UploadValidationMode
2931
/// if the resulting object has a different hash, an <see cref="UploadValidationException"/>
3032
/// is thrown, but the object remains present in Storage.
3133
/// </summary>
34+
[Obsolete("ThrowOnly is deprecated. Server-side validation will now automatically reject the upload, matching the behavior of RejectAndThrow.")]
3235
ThrowOnly = 1,
3336

3437
/// <summary>
@@ -38,6 +41,13 @@ public enum UploadValidationMode
3841
/// is thrown. If the deletion fails, that failure can be examined via
3942
/// <see cref="UploadValidationException.AdditionalFailures"/>
4043
/// </summary>
41-
DeleteAndThrow = 2
44+
[Obsolete("DeleteAndThrow is deprecated. Use RejectAndThrow instead, as the server now rejects the object before creation.")]
45+
DeleteAndThrow = 2,
46+
47+
/// <summary>
48+
/// The server validates the object hash during upload. If a hash mismatch is detected,
49+
/// the server rejects the upload entirely, preventing the object from being created.
50+
/// </summary>
51+
RejectAndThrow = 3
4252
}
4353
}

0 commit comments

Comments
 (0)