Skip to content

Commit 6b8853b

Browse files
authored
Implement request for PatchObjetAcl. (#949)
This is part of the work for #843, it implements the request object. * Rename AddModifier(WellKnownHeader) to AddOption. Missed that one after all the merges, thanks to @houglum for spotting it.
1 parent f553c49 commit 6b8853b

File tree

8 files changed

+274
-3
lines changed

8 files changed

+274
-3
lines changed

google/cloud/storage/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,13 @@ add_library(storage_client
180180
object_metadata.cc
181181
object_stream.h
182182
object_stream.cc
183-
well_known_parameters.h
184183
retry_policy.h
185184
status.h
186185
storage_class.h
187186
version.h
188-
version.cc)
187+
version.cc
188+
well_known_headers.h
189+
well_known_parameters.h)
189190
target_link_libraries(storage_client
190191
PUBLIC google_cloud_cpp_common
191192
nlohmann_json

google/cloud/storage/internal/curl_request_builder.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "google/cloud/storage/internal/curl_download_request.h"
1919
#include "google/cloud/storage/internal/curl_request.h"
2020
#include "google/cloud/storage/internal/curl_upload_request.h"
21+
#include "google/cloud/storage/well_known_headers.h"
2122

2223
namespace google {
2324
namespace cloud {
@@ -76,6 +77,18 @@ class CurlRequestBuilder {
7677
return *this;
7778
}
7879

80+
/// Add one of the well-known headers to the request.
81+
template <typename P>
82+
CurlRequestBuilder& AddOption(WellKnownHeader<P, std::string> const& p) {
83+
if (p.has_value()) {
84+
std::string header = p.header_name();
85+
header += ": ";
86+
header += p.value();
87+
AddHeader(header);
88+
}
89+
return *this;
90+
}
91+
7992
/// Add a prefix to the user-agent string.
8093
CurlRequestBuilder& AddUserAgentPrefix(std::string const& prefix);
8194

google/cloud/storage/internal/object_acl_requests.cc

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "google/cloud/storage/internal/object_acl_requests.h"
1616
#include "google/cloud/storage/internal/nljson.h"
17+
#include "google/cloud/storage/internal/patch_builder.h"
1718
#include <iostream>
1819

1920
namespace google {
@@ -44,6 +45,66 @@ std::ostream& operator<<(std::ostream& os, ObjectAclRequest const& r) {
4445
return os << "}";
4546
}
4647

48+
PatchObjectAclRequest::PatchObjectAclRequest(
49+
std::string bucket, std::string object, std::string entity,
50+
ObjectAccessControl const& original, ObjectAccessControl const& new_acl)
51+
: GenericObjectRequest(std::move(bucket), std::move(object)),
52+
entity_(std::move(entity)) {
53+
PatchBuilder build_patch;
54+
build_patch.AddStringField("bucket", original.bucket(), new_acl.bucket());
55+
build_patch.AddStringField("domain", original.domain(), new_acl.domain());
56+
build_patch.AddStringField("email", original.email(), new_acl.email());
57+
build_patch.AddStringField("entity", original.entity(), new_acl.entity());
58+
build_patch.AddStringField("entityId", original.entity_id(),
59+
new_acl.entity_id());
60+
build_patch.AddStringField("etag", original.etag(), new_acl.etag());
61+
build_patch.AddIntField("generation", original.generation(),
62+
new_acl.generation());
63+
build_patch.AddStringField("id", original.id(), new_acl.id());
64+
build_patch.AddStringField("kind", original.kind(), new_acl.kind());
65+
build_patch.AddStringField("object", original.object(), new_acl.object());
66+
67+
if (original.project_team() != new_acl.project_team()) {
68+
auto empty = [](ProjectTeam const& p) {
69+
return p.project_number.empty() and p.team.empty();
70+
};
71+
if (empty(new_acl.project_team())) {
72+
if (not empty(original.project_team())) {
73+
build_patch.RemoveField("projectTeam");
74+
}
75+
} else {
76+
PatchBuilder project_team_patch;
77+
project_team_patch
78+
.AddStringField("project_number",
79+
original.project_team().project_number,
80+
new_acl.project_team().project_number)
81+
.AddStringField("team", original.project_team().team,
82+
new_acl.project_team().team);
83+
build_patch.AddSubPatch("projectTeam", project_team_patch);
84+
}
85+
}
86+
87+
build_patch.AddStringField("role", original.role(), new_acl.role());
88+
build_patch.AddStringField("selfLink", original.self_link(),
89+
new_acl.self_link());
90+
payload_ = build_patch.ToString();
91+
}
92+
93+
PatchObjectAclRequest::PatchObjectAclRequest(
94+
std::string bucket, std::string object, std::string entity,
95+
ObjectAccessControlPatchBuilder const& patch)
96+
: GenericObjectRequest(std::move(bucket), std::move(object)),
97+
entity_(std::move(entity)),
98+
payload_(patch.BuildPatch()) {}
99+
100+
std::ostream& operator<<(std::ostream& os, PatchObjectAclRequest const& r) {
101+
os << "ObjectAclRequest={bucket_name=" << r.bucket_name()
102+
<< ", object_name=" << r.object_name() << ", entity=" << r.entity();
103+
r.DumpOptions(os, ", ");
104+
os << ", payload=" << r.payload();
105+
return os << "}";
106+
}
107+
47108
} // namespace internal
48109
} // namespace STORAGE_CLIENT_NS
49110
} // namespace storage

google/cloud/storage/internal/object_acl_requests.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,30 @@ class ObjectAclRequest
101101

102102
std::ostream& operator<<(std::ostream& os, ObjectAclRequest const& r);
103103

104+
/**
105+
* Patch an ObjectAccessControl entry.
106+
*/
107+
class PatchObjectAclRequest
108+
: public GenericObjectRequest<PatchObjectAclRequest, Generation,
109+
UserProject> {
110+
public:
111+
PatchObjectAclRequest(std::string bucket, std::string object,
112+
std::string entity, ObjectAccessControl const& original,
113+
ObjectAccessControl const& new_acl);
114+
PatchObjectAclRequest(std::string bucket, std::string object,
115+
std::string entity,
116+
ObjectAccessControlPatchBuilder const& patch);
117+
118+
std::string const& entity() const { return entity_; }
119+
std::string const& payload() const { return payload_; }
120+
121+
private:
122+
std::string entity_;
123+
std::string payload_;
124+
};
125+
126+
std::ostream& operator<<(std::ostream& os, PatchObjectAclRequest const& r);
127+
104128
} // namespace internal
105129
} // namespace STORAGE_CLIENT_NS
106130
} // namespace storage

google/cloud/storage/internal/object_acl_requests_test.cc

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
#include "google/cloud/storage/internal/object_acl_requests.h"
16+
#include "google/cloud/storage/internal/curl_request_builder.h"
1617
#include <gmock/gmock.h>
1718

1819
namespace google {
@@ -67,6 +68,69 @@ TEST(ObjectAclRequestTest, Stream) {
6768
EXPECT_THAT(str, HasSubstr("user-test-user"));
6869
}
6970

71+
ObjectAccessControl CreateObjectAccessControlForTest() {
72+
std::string text = R"""({
73+
"bucket": "foo-bar",
74+
"domain": "example.com",
75+
"email": "[email protected]",
76+
"entity": "user-foobar",
77+
"entityId": "user-foobar-id-123",
78+
"etag": "XYZ=",
79+
"generation": 42,
80+
"id": "object-foo-bar-baz-acl-234",
81+
"kind": "storage#objectAccessControl",
82+
"object": "baz",
83+
"projectTeam": {
84+
"projectNumber": "3456789",
85+
"team": "a-team"
86+
},
87+
"role": "OWNER"
88+
})""";
89+
return ObjectAccessControl::ParseFromString(text);
90+
}
91+
92+
TEST(PatchObjectAclRequestTest, ReadModifyWrite) {
93+
ObjectAccessControl original = CreateObjectAccessControlForTest();
94+
ObjectAccessControl new_acl =
95+
CreateObjectAccessControlForTest().set_role("READER");
96+
97+
PatchObjectAclRequest request("my-bucket", "my-object", "user-test-user",
98+
original, new_acl);
99+
nl::json expected = {
100+
{"role", "READER"},
101+
};
102+
nl::json actual = nl::json::parse(request.payload());
103+
EXPECT_EQ(expected, actual);
104+
}
105+
106+
TEST(PatchObjectAclRequestTest, Patch) {
107+
PatchObjectAclRequest request(
108+
"my-bucket", "my-object", "user-test-user",
109+
ObjectAccessControlPatchBuilder().set_role("READER").delete_entity());
110+
nl::json expected = {{"role", "READER"}, {"entity", nullptr}};
111+
nl::json actual = nl::json::parse(request.payload());
112+
EXPECT_EQ(expected, actual);
113+
}
114+
115+
TEST(PatchObjectAclRequestTest, PatchStream) {
116+
ObjectAccessControl original = CreateObjectAccessControlForTest();
117+
ObjectAccessControl new_acl =
118+
CreateObjectAccessControlForTest().set_role("READER");
119+
120+
PatchObjectAclRequest request("my-bucket", "my-object", "user-test-user",
121+
original, new_acl);
122+
request.set_multiple_options(UserProject("my-project"), Generation(7));
123+
std::ostringstream os;
124+
os << request;
125+
auto str = os.str();
126+
EXPECT_THAT(str, HasSubstr("userProject=my-project"));
127+
EXPECT_THAT(str, HasSubstr("generation=7"));
128+
EXPECT_THAT(str, HasSubstr("my-bucket"));
129+
EXPECT_THAT(str, HasSubstr("my-object"));
130+
EXPECT_THAT(str, HasSubstr("user-test-user"));
131+
EXPECT_THAT(str, HasSubstr(request.payload()));
132+
}
133+
70134
} // namespace
71135
} // namespace internal
72136
} // namespace STORAGE_CLIENT_NS

google/cloud/storage/object_access_control.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "google/cloud/storage/internal/access_control_common.h"
1919
#include "google/cloud/storage/internal/common_metadata.h"
2020
#include "google/cloud/storage/internal/nljson.h"
21+
#include "google/cloud/storage/internal/patch_builder.h"
2122

2223
namespace google {
2324
namespace cloud {
@@ -83,6 +84,50 @@ class ObjectAccessControl : private internal::AccessControlCommon {
8384

8485
std::ostream& operator<<(std::ostream& os, ObjectAccessControl const& rhs);
8586

87+
/**
88+
* Prepare a patch for an ObjectAccessControl resource.
89+
*
90+
* The ObjectAccessControl resource only has two modifiable fields: entity
91+
* and role. This class allows application developers to setup a PATCH message,
92+
* note that some of the possible PATCH messages may result in errors from the
93+
* server, for example: while it is possible to express "change the value of the
94+
* entity field" with a PATCH request, the server rejects such changes.
95+
*
96+
* @see
97+
* https://cloud.google.com/storage/docs/json_api/v1/how-tos/performance#patch
98+
* for general information on PATCH requests for the Google Cloud Storage
99+
* JSON API.
100+
*/
101+
class ObjectAccessControlPatchBuilder {
102+
public:
103+
ObjectAccessControlPatchBuilder() = default;
104+
105+
std::string BuildPatch() const { return impl_.ToString(); }
106+
107+
ObjectAccessControlPatchBuilder& set_entity(std::string const& v) {
108+
impl_.SetStringField("entity", v);
109+
return *this;
110+
}
111+
112+
ObjectAccessControlPatchBuilder& delete_entity() {
113+
impl_.RemoveField("entity");
114+
return *this;
115+
}
116+
117+
ObjectAccessControlPatchBuilder& set_role(std::string const& v) {
118+
impl_.SetStringField("role", v);
119+
return *this;
120+
}
121+
122+
ObjectAccessControlPatchBuilder& delete_role() {
123+
impl_.RemoveField("role");
124+
return *this;
125+
}
126+
127+
private:
128+
internal::PatchBuilder impl_;
129+
};
130+
86131
} // namespace STORAGE_CLIENT_NS
87132
} // namespace storage
88133
} // namespace cloud

google/cloud/storage/storage_client.bzl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ storage_client_HDRS = [
4848
"object_access_control.h",
4949
"object_metadata.h",
5050
"object_stream.h",
51-
"well_known_parameters.h",
5251
"retry_policy.h",
5352
"status.h",
5453
"storage_class.h",
5554
"version.h",
55+
"well_known_headers.h",
56+
"well_known_parameters.h",
5657
]
5758

5859
storage_client_SRCS = [
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_WELL_KNOWN_HEADERS_H_
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_WELL_KNOWN_HEADERS_H_
17+
18+
#include "google/cloud/internal/optional.h"
19+
#include "google/cloud/storage/version.h"
20+
#include <cstdint>
21+
#include <string>
22+
23+
namespace google {
24+
namespace cloud {
25+
namespace storage {
26+
inline namespace STORAGE_CLIENT_NS {
27+
/**
28+
* Refactor definition of well-known request headers using the CRTP.
29+
*
30+
* @tparam H the type we will use to represent the header.
31+
* @tparam T the C++ type of the query parameter
32+
*/
33+
template <typename P, typename T>
34+
class WellKnownHeader {
35+
public:
36+
WellKnownHeader() : value_{} {}
37+
explicit WellKnownHeader(T&& value) : value_(std::forward<T>(value)) {}
38+
39+
char const* header_name() const { return P::well_known_parameter_name(); }
40+
bool has_value() const { return value_.has_value(); }
41+
T const& value() const { return value_.value(); }
42+
43+
private:
44+
google::cloud::internal::optional<T> value_;
45+
};
46+
47+
struct IfMatchEtag : public WellKnownHeader<IfMatchEtag, std::string> {
48+
using WellKnownHeader<IfMatchEtag, std::string>::WellKnownHeader;
49+
static char const* header_name() { return "If-Match"; }
50+
};
51+
52+
struct IfNoneMatchEtag : public WellKnownHeader<IfNoneMatchEtag, std::string> {
53+
using WellKnownHeader<IfNoneMatchEtag, std::string>::WellKnownHeader;
54+
static char const* header_name() { return "If-None-Match"; }
55+
};
56+
57+
} // namespace STORAGE_CLIENT_NS
58+
} // namespace storage
59+
} // namespace cloud
60+
} // namespace google
61+
62+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_WELL_KNOWN_HEADERS_H_

0 commit comments

Comments
 (0)