Skip to content

Commit db48d1a

Browse files
authored
impl(GCS+gRPC): InsertObjectRequest for AsyncClient (googleapis#12503)
We need a class to represent requests that insert objects via the `AsyncClient`. We cannot use the class from the synchronous client because that uses non-owning types for the data payload. And we also want the class in the public API (eventually) as it is needed for mocking.
1 parent 689c06f commit db48d1a

12 files changed

+248
-76
lines changed

google/cloud/storage/async_object_requests.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,54 @@ class WritePayload {
6262
absl::Cord impl_;
6363
};
6464

65+
/**
66+
* A request to insert object sans the data payload.
67+
*
68+
* This class can hold all the mandatory and optional parameters to insert an
69+
* object **except** for the data payload. The ideal representation for the data
70+
* payload depends on the type of request. For asynchronous requests the data
71+
* must be in an owning type, such as `WritePayload`. For blocking request, a
72+
* non-owning type (such as `absl::string_view`) can reduce data copying.
73+
*
74+
* This class is the public API for the library because it is required for
75+
* mocking.
76+
*/
77+
class InsertObjectRequest {
78+
public:
79+
InsertObjectRequest() = default;
80+
InsertObjectRequest(std::string bucket_name, std::string object_name)
81+
: impl_(std::move(bucket_name), std::move(object_name)) {}
82+
83+
std::string const& bucket_name() const { return impl_.bucket_name(); }
84+
std::string const& object_name() const { return impl_.object_name(); }
85+
86+
template <typename... O>
87+
InsertObjectRequest& set_multiple_options(O&&... o) & {
88+
impl_.set_multiple_options(std::forward<O>(o)...);
89+
return *this;
90+
}
91+
template <typename... O>
92+
InsertObjectRequest&& set_multiple_options(O&&... o) && {
93+
return std::move(set_multiple_options(std::forward<O>(o)...));
94+
}
95+
96+
template <typename O>
97+
bool HasOption() const {
98+
return impl_.HasOption<O>();
99+
}
100+
template <typename O>
101+
O GetOption() const {
102+
return impl_.GetOption<O>();
103+
}
104+
105+
protected:
106+
struct Impl : public storage::internal::InsertObjectRequestImpl<Impl> {
107+
using storage::internal::InsertObjectRequestImpl<
108+
Impl>::InsertObjectRequestImpl;
109+
};
110+
Impl impl_;
111+
};
112+
65113
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
66114
} // namespace storage_experimental
67115
} // namespace cloud

google/cloud/storage/internal/grpc/configure_client_context.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ void ApplyRoutingHeaders(
4343
request.bucket_name()));
4444
}
4545

46+
void ApplyRoutingHeaders(
47+
grpc::ClientContext& context,
48+
storage_experimental::InsertObjectRequest const& request) {
49+
context.AddMetadata(
50+
"x-goog-request-params",
51+
"bucket=" + google::cloud::internal::UrlEncode("projects/_/buckets/" +
52+
request.bucket_name()));
53+
}
54+
4655
void ApplyRoutingHeaders(grpc::ClientContext& context,
4756
storage::internal::UploadChunkRequest const& request) {
4857
static auto* slash_format =

google/cloud/storage/internal/grpc/configure_client_context.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_GRPC_CONFIGURE_CLIENT_CONTEXT_H
1616
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_GRPC_CONFIGURE_CLIENT_CONTEXT_H
1717

18+
#include "google/cloud/storage/async_object_requests.h"
1819
#include "google/cloud/storage/internal/generic_request.h"
1920
#include "google/cloud/storage/internal/object_requests.h"
2021
#include "google/cloud/storage/version.h"
@@ -76,6 +77,11 @@ void ApplyRoutingHeaders(
7677
grpc::ClientContext& context,
7778
storage::internal::InsertObjectMediaRequest const& request);
7879

80+
/// @copydoc ApplyRoutingHeaders(grpc::ClientContext&,)
81+
void ApplyRoutingHeaders(
82+
grpc::ClientContext& context,
83+
storage_experimental::InsertObjectRequest const& request);
84+
7985
/**
8086
* The generated `StorageMetadata` stub can not handle dynamic routing headers
8187
* for client side streaming. So we manually match and extract the headers in

google/cloud/storage/internal/grpc/configure_client_context_test.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,17 @@ TEST_F(GrpcConfigureClientContext, ApplyRoutingHeadersInsertObjectMedia) {
145145
"bucket=projects%2F_%2Fbuckets%2Ftest-bucket")));
146146
}
147147

148+
TEST_F(GrpcConfigureClientContext, ApplyRoutingHeadersInsertObject) {
149+
storage_experimental::InsertObjectRequest req("test-bucket", "test-object");
150+
151+
grpc::ClientContext context;
152+
ApplyRoutingHeaders(context, req);
153+
auto metadata = GetMetadata(context);
154+
EXPECT_THAT(metadata,
155+
Contains(Pair("x-goog-request-params",
156+
"bucket=projects%2F_%2Fbuckets%2Ftest-bucket")));
157+
}
158+
148159
TEST_F(GrpcConfigureClientContext, ApplyRoutingHeadersUploadChunkMatchSlash) {
149160
storage::internal::UploadChunkRequest req(
150161
"projects/_/buckets/test-bucket/blah/blah", 0, {},

google/cloud/storage/internal/grpc/object_request_parser.cc

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,29 @@ Status PatchTemporaryHold(Object& o, nlohmann::json const& p) {
209209
return Status{};
210210
}
211211

212+
template <typename Request>
213+
StatusOr<google::storage::v2::WriteObjectRequest> ToProtoImpl(
214+
Request const& request) {
215+
google::storage::v2::WriteObjectRequest r;
216+
auto& object_spec = *r.mutable_write_object_spec();
217+
auto& resource = *object_spec.mutable_resource();
218+
SetResourceOptions(resource, request);
219+
auto status = SetObjectMetadata(resource, request);
220+
if (!status.ok()) return status;
221+
SetStorageClass(resource, request);
222+
SetPredefinedAcl(object_spec, request);
223+
SetGenerationConditions(object_spec, request);
224+
SetMetagenerationConditions(object_spec, request);
225+
status = SetCommonObjectParameters(r, request);
226+
if (!status.ok()) return status;
227+
228+
resource.set_bucket(GrpcBucketIdToName(request.bucket_name()));
229+
resource.set_name(request.object_name());
230+
r.set_write_offset(0);
231+
232+
return r;
233+
}
234+
212235
} // namespace
213236

214237
StatusOr<google::storage::v2::ComposeObjectRequest> ToProto(
@@ -455,25 +478,13 @@ StatusOr<google::storage::v2::UpdateObjectRequest> ToProto(
455478
}
456479

457480
StatusOr<google::storage::v2::WriteObjectRequest> ToProto(
458-
storage::internal::InsertObjectMediaRequest const& request) {
459-
google::storage::v2::WriteObjectRequest r;
460-
auto& object_spec = *r.mutable_write_object_spec();
461-
auto& resource = *object_spec.mutable_resource();
462-
SetResourceOptions(resource, request);
463-
auto status = SetObjectMetadata(resource, request);
464-
if (!status.ok()) return status;
465-
SetStorageClass(resource, request);
466-
SetPredefinedAcl(object_spec, request);
467-
SetGenerationConditions(object_spec, request);
468-
SetMetagenerationConditions(object_spec, request);
469-
status = SetCommonObjectParameters(r, request);
470-
if (!status.ok()) return status;
471-
472-
resource.set_bucket(GrpcBucketIdToName(request.bucket_name()));
473-
resource.set_name(request.object_name());
474-
r.set_write_offset(0);
481+
storage_experimental::InsertObjectRequest const& request) {
482+
return ToProtoImpl(request);
483+
}
475484

476-
return r;
485+
StatusOr<google::storage::v2::WriteObjectRequest> ToProto(
486+
storage::internal::InsertObjectMediaRequest const& request) {
487+
return ToProtoImpl(request);
477488
}
478489

479490
storage::internal::QueryResumableUploadResponse FromProto(

google/cloud/storage/internal/grpc/object_request_parser.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_GRPC_OBJECT_REQUEST_PARSER_H
1616
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_INTERNAL_GRPC_OBJECT_REQUEST_PARSER_H
1717

18+
#include "google/cloud/storage/async_object_requests.h"
1819
#include "google/cloud/storage/internal/storage_connection.h"
1920
#include "google/cloud/storage/version.h"
2021
#include "google/cloud/internal/grpc_request_metadata.h"
@@ -42,6 +43,8 @@ StatusOr<google::storage::v2::UpdateObjectRequest> ToProto(
4243
StatusOr<google::storage::v2::UpdateObjectRequest> ToProto(
4344
storage::internal::UpdateObjectRequest const& request);
4445

46+
StatusOr<google::storage::v2::WriteObjectRequest> ToProto(
47+
storage_experimental::InsertObjectRequest const& request);
4548
StatusOr<google::storage::v2::WriteObjectRequest> ToProto(
4649
storage::internal::InsertObjectMediaRequest const& request);
4750
storage::internal::QueryResumableUploadResponse FromProto(

google/cloud/storage/internal/grpc/object_request_parser_test.cc

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,88 @@ TEST(GrpcObjectRequestParser, InsertObjectMediaRequestWithObjectMetadata) {
729729
EXPECT_THAT(actual, IsProtoEqual(expected));
730730
}
731731

732+
TEST(GrpcObjectRequestParser, InsertObjectRequestSimple) {
733+
storage_proto::WriteObjectRequest expected;
734+
EXPECT_TRUE(TextFormat::ParseFromString(
735+
R"pb(
736+
write_object_spec: {
737+
resource: {
738+
bucket: "projects/_/buckets/test-bucket-name"
739+
name: "test-object-name"
740+
}
741+
}
742+
)pb",
743+
&expected));
744+
745+
storage_experimental::InsertObjectRequest request("test-bucket-name",
746+
"test-object-name");
747+
auto actual = ToProto(request).value();
748+
EXPECT_THAT(actual, IsProtoEqual(expected));
749+
}
750+
751+
TEST(GrpcObjectRequestParser, InsertObjectRequestAllOptions) {
752+
auto constexpr kTextProto = R"pb(
753+
write_object_spec {
754+
resource: {
755+
bucket: "projects/_/buckets/test-bucket-name"
756+
name: "test-object-name"
757+
content_type: "test-content-type"
758+
content_encoding: "test-content-encoding"
759+
# Should not be set, the proto file says these values should
760+
# not be included in the upload
761+
# crc32c:
762+
# md5_hash:
763+
kms_key: "test-kms-key-name"
764+
}
765+
predefined_acl: "private"
766+
if_generation_match: 0
767+
if_generation_not_match: 7
768+
if_metageneration_match: 42
769+
if_metageneration_not_match: 84
770+
})pb";
771+
storage_proto::WriteObjectRequest expected;
772+
EXPECT_TRUE(TextFormat::ParseFromString(kTextProto, &expected));
773+
*expected.mutable_common_object_request_params() =
774+
ExpectedCommonObjectRequestParams();
775+
776+
auto constexpr kContents = "The quick brown fox jumps over the lazy dog";
777+
778+
storage_experimental::InsertObjectRequest request("test-bucket-name",
779+
"test-object-name");
780+
request.set_multiple_options(
781+
storage::ContentType("test-content-type"),
782+
storage::ContentEncoding("test-content-encoding"),
783+
storage::Crc32cChecksumValue(storage::ComputeCrc32cChecksum(kContents)),
784+
storage::MD5HashValue(storage::ComputeMD5Hash(kContents)),
785+
storage::PredefinedAcl("private"), storage::IfGenerationMatch(0),
786+
storage::IfGenerationNotMatch(7), storage::IfMetagenerationMatch(42),
787+
storage::IfMetagenerationNotMatch(84), storage::Projection::Full(),
788+
storage::UserProject("test-user-project"),
789+
storage::QuotaUser("test-quota-user"), storage::UserIp("test-user-ip"),
790+
storage::EncryptionKey::FromBinaryKey("01234567"),
791+
storage::KmsKeyName("test-kms-key-name"));
792+
793+
auto actual = ToProto(request).value();
794+
EXPECT_THAT(actual, IsProtoEqual(expected));
795+
}
796+
797+
TEST(GrpcObjectRequestParser, InsertObjectRequestWithObjectMetadata) {
798+
storage_proto::WriteObjectRequest expected;
799+
auto& resource = *expected.mutable_write_object_spec()->mutable_resource();
800+
resource = ExpectedFullObjectMetadata();
801+
resource.set_bucket("projects/_/buckets/test-bucket-name");
802+
resource.set_name("test-object-name");
803+
resource.set_storage_class("STANDARD");
804+
805+
storage_experimental::InsertObjectRequest request("test-bucket-name",
806+
"test-object-name");
807+
request.set_multiple_options(storage::WithObjectMetadata(
808+
FullObjectMetadata().set_storage_class("STANDARD")));
809+
810+
auto actual = ToProto(request).value();
811+
EXPECT_THAT(actual, IsProtoEqual(expected));
812+
}
813+
732814
TEST(GrpcObjectRequestParser, WriteObjectResponseSimple) {
733815
google::storage::v2::WriteObjectResponse input;
734816
ASSERT_TRUE(TextFormat::ParseFromString(

google/cloud/storage/internal/hash_function.cc

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,38 +21,26 @@ namespace cloud {
2121
namespace storage {
2222
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2323
namespace internal {
24-
namespace {
25-
std::unique_ptr<HashFunction> CreateHashFunction(bool disable_crc32c,
26-
bool disable_md5) {
27-
if (disable_md5 && disable_crc32c) {
28-
return std::make_unique<NullHashFunction>();
29-
}
30-
if (disable_md5) return std::make_unique<Crc32cHashFunction>();
31-
if (disable_crc32c) return std::make_unique<MD5HashFunction>();
32-
return std::make_unique<CompositeFunction>(
33-
std::make_unique<Crc32cHashFunction>(),
34-
std::make_unique<MD5HashFunction>());
35-
}
3624

37-
template <typename Request>
38-
std::unique_ptr<HashFunction> CreateUploadHashFunction(Request const& request) {
25+
std::unique_ptr<HashFunction> CreateHashFunction(
26+
Crc32cChecksumValue const& crc32c_value,
27+
DisableCrc32cChecksum const& crc32c_disabled, MD5HashValue const& md5_value,
28+
DisableMD5Hash const& md5_disabled) {
3929
auto crc32c = std::unique_ptr<HashFunction>();
40-
auto crc32c_value =
41-
request.template GetOption<Crc32cChecksumValue>().value_or("");
42-
if (!crc32c_value.empty()) {
30+
auto crc32c_v = crc32c_value.value_or("");
31+
if (!crc32c_v.empty()) {
4332
crc32c = std::make_unique<PrecomputedHashFunction>(
44-
HashValues{/*.crc32c=*/std::move(crc32c_value), /*md5=*/{}});
45-
} else if (!request.template GetOption<DisableCrc32cChecksum>().value_or(
46-
false)) {
33+
HashValues{/*.crc32c=*/std::move(crc32c_v), /*md5=*/{}});
34+
} else if (!crc32c_disabled.value_or(false)) {
4735
crc32c = std::make_unique<Crc32cHashFunction>();
4836
}
4937

5038
auto md5 = std::unique_ptr<HashFunction>();
51-
auto md5_value = request.template GetOption<MD5HashValue>().value_or("");
52-
if (!md5_value.empty()) {
39+
auto md5_v = md5_value.value_or("");
40+
if (!md5_v.empty()) {
5341
md5 = std::make_unique<PrecomputedHashFunction>(
54-
HashValues{/*.crc32c=*/{}, /*.md5=*/std::move(md5_value)});
55-
} else if (!request.template GetOption<DisableMD5Hash>().value_or(false)) {
42+
HashValues{/*.crc32c=*/{}, /*.md5=*/std::move(md5_v)});
43+
} else if (!md5_disabled.value_or(false)) {
5644
md5 = std::make_unique<MD5HashFunction>();
5745
}
5846

@@ -63,18 +51,25 @@ std::unique_ptr<HashFunction> CreateUploadHashFunction(Request const& request) {
6351
return std::make_unique<CompositeFunction>(std::move(crc32c), std::move(md5));
6452
}
6553

66-
} // namespace
67-
6854
std::unique_ptr<HashFunction> CreateNullHashFunction() {
6955
return std::make_unique<NullHashFunction>();
7056
}
7157

7258
std::unique_ptr<HashFunction> CreateHashFunction(
7359
ReadObjectRangeRequest const& request) {
7460
if (request.RequiresRangeHeader()) return CreateNullHashFunction();
75-
return CreateHashFunction(
76-
request.GetOption<DisableCrc32cChecksum>().value_or(false),
77-
request.GetOption<DisableMD5Hash>().value_or(false));
61+
62+
auto const disable_crc32c =
63+
request.GetOption<DisableCrc32cChecksum>().value_or(false);
64+
auto const disable_md5 = request.GetOption<DisableMD5Hash>().value_or(false);
65+
if (disable_md5 && disable_crc32c) {
66+
return std::make_unique<NullHashFunction>();
67+
}
68+
if (disable_md5) return std::make_unique<Crc32cHashFunction>();
69+
if (disable_crc32c) return std::make_unique<MD5HashFunction>();
70+
return std::make_unique<CompositeFunction>(
71+
std::make_unique<Crc32cHashFunction>(),
72+
std::make_unique<MD5HashFunction>());
7873
}
7974

8075
std::unique_ptr<HashFunction> CreateHashFunction(
@@ -84,12 +79,10 @@ std::unique_ptr<HashFunction> CreateHashFunction(
8479
// for previous values is lost.
8580
return CreateNullHashFunction();
8681
}
87-
return CreateUploadHashFunction(request);
88-
}
89-
90-
std::unique_ptr<HashFunction> CreateHashFunction(
91-
InsertObjectMediaRequest const& request) {
92-
return CreateUploadHashFunction(request);
82+
return CreateHashFunction(request.GetOption<Crc32cChecksumValue>(),
83+
request.GetOption<DisableCrc32cChecksum>(),
84+
request.GetOption<MD5HashValue>(),
85+
request.GetOption<DisableMD5Hash>());
9386
}
9487

9588
} // namespace internal

0 commit comments

Comments
 (0)