Skip to content

Commit a641319

Browse files
committed
New convention: required params go through parameter list, optional bloat goes through a struct. Just like in the Go SDK
1 parent 9a44f7c commit a641319

4 files changed

Lines changed: 75 additions & 42 deletions

File tree

main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ int main() {
77
int totalObjects = 0;
88

99
while (paginator.HasMorePages()) {
10-
std::expected<ListBucketResult, Error> page = paginator.NextPage();
10+
std::expected<ListObjectsResult, Error> page = paginator.NextPage();
1111

1212
if (!page) {
1313
std::println("Error: {}", page.error().Message);

src/s3cpp/s3.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include <expected>
22
#include <s3cpp/s3.h>
33

4-
std::expected<ListBucketResult, Error> S3Client::ListObjects(const std::string& bucket, const std::string& prefix, int maxKeys, const std::string& continuationToken) {
4+
std::expected<ListObjectsResult, Error> S3Client::ListObjects(const std::string& bucket, const std::string& prefix, int maxKeys, const std::string& continuationToken) {
55
// Silent-ly accept maxKeys > 1000, even though we will return 1K at most
66
// Pagination is opt-in as in the Go SDK, the user must be aware of this
77
const std::string baseUrl = buildURL(bucket);
@@ -24,8 +24,8 @@ std::expected<ListBucketResult, Error> S3Client::ListObjects(const std::string&
2424
return deserializeListBucketResult(XMLBody, maxKeys);
2525
}
2626

27-
std::expected<ListBucketResult, Error> S3Client::deserializeListBucketResult(const std::vector<XMLNode>& nodes, const int maxKeys) {
28-
ListBucketResult response;
27+
std::expected<ListObjectsResult, Error> S3Client::deserializeListBucketResult(const std::vector<XMLNode>& nodes, const int maxKeys) {
28+
ListObjectsResult response;
2929
response.Contents.reserve(maxKeys);
3030
response.CommonPrefixes.reserve(maxKeys);
3131

@@ -105,8 +105,8 @@ std::expected<ListBucketResult, Error> S3Client::deserializeListBucketResult(con
105105
response.Contents[contentsIdx].StorageClass = std::move(node.value);
106106
} else {
107107
// Detect and parse error
108-
// Note(cristian): This fallback should not be needed as we have
109-
// the HTTP status codes for this, however, I like it
108+
// Note(cristian): This fallback should not be needed as we have
109+
// the HTTP status codes for this, however, I like it
110110
if (node.tag.substr(0, 6) == "Error.") {
111111
return std::unexpected<Error>(deserializeError(nodes));
112112
}
@@ -132,6 +132,22 @@ std::expected<ListBucketResult, Error> S3Client::deserializeListBucketResult(con
132132
return response;
133133
}
134134

135+
std::expected<std::string, Error> S3Client::GetObject(const std::string& bucket, const std::string& key) {
136+
std::string url = buildURL(bucket) + std::format("/{}", key);
137+
138+
HttpRequest req = Client.get(url).header("Host", getHostHeader(bucket));
139+
Signer.sign(req);
140+
HttpResponse res = req.execute();
141+
142+
const std::vector<XMLNode>& XMLBody = Parser.parse(res.body());
143+
144+
if (res.status() != 200) {
145+
return std::unexpected<Error>(deserializeError(XMLBody));
146+
}
147+
148+
return res.body();
149+
}
150+
135151
Error S3Client::deserializeError(const std::vector<XMLNode>& nodes) {
136152
Error error;
137153

@@ -147,7 +163,7 @@ Error S3Client::deserializeError(const std::vector<XMLNode>& nodes) {
147163
} else if (node.tag == "Error.RequestId") {
148164
error.RequestId = Parser.parseNumber<int>(std::move(node.value));
149165
} else {
150-
continue;
166+
continue;
151167
}
152168
}
153169

src/s3cpp/s3.h

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include <s3cpp/auth.h>
44
#include <s3cpp/xml.hpp>
55

6-
// ListBucketResult
6+
// ListObjects
77
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html#API_ListObjectsV2_ResponseSyntax
88

99
struct Contents_ {
@@ -28,7 +28,23 @@ struct CommonPrefix {
2828
std::string Prefix;
2929
};
3030

31-
struct ListBucketResult {
31+
struct GetObjectInput {
32+
std::optional<std::string> If_Match;
33+
std::optional<std::string> If_Modified_Since;
34+
std::optional<std::string> If_None_Match;
35+
std::optional<std::string> If_Unmodified_Since;
36+
std::optional<int> partNumber;
37+
std::optional<std::string> Range; // e.g. bytes=0-9
38+
std::optional<std::string> response_cache_control;
39+
std::optional<std::string> response_content_disposition;
40+
std::optional<std::string> response_content_encoding;
41+
std::optional<std::string> response_content_language;
42+
std::optional<std::string> response_content_type;
43+
std::optional<std::string> response_expires;
44+
std::optional<std::string> versionId;
45+
};
46+
47+
struct ListObjectsResult {
3248
bool IsTruncated;
3349
std::string Marker;
3450
std::string NextMarker;
@@ -47,7 +63,6 @@ struct ListBucketResult {
4763

4864
// REST generic error
4965
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#RESTErrorResponses
50-
// TODO(cristian): Should we add the 3xx, 4xx, or 5xx HTTP status code
5166
struct Error {
5267
std::string Code;
5368
std::string Message;
@@ -87,29 +102,31 @@ class S3Client {
87102
, addressing_style_(style) {
88103
}
89104

90-
std::expected<std::string, Error> GetObject(const std::string& bucket, const std::string& key) {
91-
std::string url = buildURL(bucket) + std::format("/{}", key);
92-
93-
HttpRequest req = Client.get(url).header("Host", getHostHeader(bucket));
94-
Signer.sign(req);
95-
HttpResponse res = req.execute();
96-
97-
const std::vector<XMLNode>& XMLBody = Parser.parse(res.body());
98-
99-
if (res.status() != 200) {
100-
return std::unexpected<Error>(deserializeError(XMLBody));
101-
}
102-
103-
return res.body();
104-
}
105-
// TODO(cristian): HeadBucket and HeadObject
106-
107-
std::expected<ListBucketResult, Error> ListObjects(const std::string& bucket) { return ListObjects(bucket, "/", 1000, ""); }
108-
std::expected<ListBucketResult, Error> ListObjects(const std::string& bucket, const std::string& prefix) { return ListObjects(bucket, prefix, 1000, ""); }
109-
std::expected<ListBucketResult, Error> ListObjects(const std::string& bucket, const std::string& prefix, int maxKeys) { return ListObjects(bucket, prefix, maxKeys, ""); }
110-
std::expected<ListBucketResult, Error> ListObjects(const std::string& bucket, const std::string& prefix, int maxKeys, const std::string& continuationToken);
111-
112-
std::expected<ListBucketResult, Error> deserializeListBucketResult(const std::vector<XMLNode>& nodes, const int maxKeys);
105+
// S3 operations
106+
// TODO(cristian): ListObjectsV2 missing URI params:
107+
// - Bucket
108+
// - Continuation token
109+
// - Delimiter
110+
// - EncodingType
111+
// - ExpectedBucketOwner
112+
// - FetchOwner
113+
// - MaxKeys
114+
// - OptionalObjectAttributes
115+
// - Prefix
116+
// - RequestPayer
117+
// - StartAfter
118+
std::expected<ListObjectsResult, Error> ListObjects(const std::string& bucket) { return ListObjects(bucket, "/", 1000, ""); }
119+
std::expected<ListObjectsResult, Error> ListObjects(const std::string& bucket, const std::string& prefix) { return ListObjects(bucket, prefix, 1000, ""); }
120+
std::expected<ListObjectsResult, Error> ListObjects(const std::string& bucket, const std::string& prefix, int maxKeys) { return ListObjects(bucket, prefix, maxKeys, ""); }
121+
std::expected<ListObjectsResult, Error> ListObjects(const std::string& bucket, const std::string& prefix, int maxKeys, const std::string& continuationToken);
122+
123+
std::expected<std::string, Error> GetObject(const std::string& bucket, const std::string& key);
124+
std::expected<std::string, Error> GetObject(const std::string& bucket, const std::string& key, const GetObjectInput& opt);
125+
// TODO(cristian): Add all overloading needed for different params
126+
// TODO(cristian): HeadBucket and HeadObject, PutObject, CreateBucket
127+
128+
// S3 responses
129+
std::expected<ListObjectsResult, Error> deserializeListBucketResult(const std::vector<XMLNode>& nodes, const int maxKeys);
113130
Error deserializeError(const std::vector<XMLNode>& nodes);
114131

115132
private:
@@ -153,7 +170,7 @@ class ListObjectsPaginator {
153170

154171
bool HasMorePages() const { return hasMorePages_; }
155172

156-
std::expected<ListBucketResult, Error> NextPage() {
173+
std::expected<ListObjectsResult, Error> NextPage() {
157174
auto response = client_.ListObjects(bucket_, prefix_, maxKeys_, continuationToken_);
158175
if (response.has_value()) {
159176
hasMorePages_ = response.value().IsTruncated;

test/s3_test.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ TEST(S3, ListObjectsBucket) {
66
try {
77
// Assuming the bucket has the 10K objects
88
// Once we implement PutObject we will do this ourselves with s3cpp
9-
std::expected<ListBucketResult, Error> res = client.ListObjects("my-bucket");
9+
std::expected<ListObjectsResult, Error> res = client.ListObjects("my-bucket");
1010
if (!res)
1111
GTEST_FAIL();
1212
EXPECT_EQ(res->Contents.size(), 0);
@@ -22,7 +22,7 @@ TEST(S3, ListObjectsBucket) {
2222
TEST(S3, ListObjectsBucketNotExists) {
2323
S3Client client("minio_access", "minio_secret", "127.0.0.1:9000", S3AddressingStyle::PathStyle);
2424
try {
25-
std::expected<ListBucketResult, Error> res = client.ListObjects("Does-not-exist");
25+
std::expected<ListObjectsResult, Error> res = client.ListObjects("Does-not-exist");
2626
if (res.has_value()) // We must return error
2727
GTEST_FAIL();
2828
Error error = res.error();
@@ -40,7 +40,7 @@ TEST(S3, ListObjectsFilePrefix) {
4040
S3Client client("minio_access", "minio_secret", "127.0.0.1:9000", S3AddressingStyle::PathStyle);
4141
try {
4242
// path/to/file_1.txt must exist
43-
std::expected<ListBucketResult, Error> res = client.ListObjects("my-bucket", "path/to/file_1.txt");
43+
std::expected<ListObjectsResult, Error> res = client.ListObjects("my-bucket", "path/to/file_1.txt");
4444
if (!res)
4545
GTEST_FAIL();
4646
EXPECT_EQ(res->Contents.size(), 1);
@@ -57,7 +57,7 @@ TEST(S3, ListObjectsDirPrefix) {
5757
S3Client client("minio_access", "minio_secret", "127.0.0.1:9000", S3AddressingStyle::PathStyle);
5858
try {
5959
// Get 100 keys
60-
std::expected<ListBucketResult, Error> res = client.ListObjects("my-bucket", "path/to/", 100);
60+
std::expected<ListObjectsResult, Error> res = client.ListObjects("my-bucket", "path/to/", 100);
6161
if (!res)
6262
GTEST_FAIL();
6363
EXPECT_EQ(res->Contents.size(), 100);
@@ -73,7 +73,7 @@ TEST(S3, ListObjectsDirPrefix) {
7373
TEST(S3, ListObjectsDirPrefixMaxKeys) {
7474
S3Client client("minio_access", "minio_secret", "127.0.0.1:9000", S3AddressingStyle::PathStyle);
7575
try {
76-
std::expected<ListBucketResult, Error> res = client.ListObjects("my-bucket", "path/to/", 1);
76+
std::expected<ListObjectsResult, Error> res = client.ListObjects("my-bucket", "path/to/", 1);
7777
if (!res)
7878
GTEST_FAIL();
7979
EXPECT_EQ(res->Contents.size(), 1);
@@ -89,7 +89,7 @@ TEST(S3, ListObjectsDirPrefixMaxKeys) {
8989
TEST(S3, ListObjectsCheckFields) {
9090
S3Client client("minio_access", "minio_secret", "127.0.0.1:9000", S3AddressingStyle::PathStyle);
9191
try {
92-
std::expected<ListBucketResult, Error> res = client.ListObjects("my-bucket", "path/to/", 2);
92+
std::expected<ListObjectsResult, Error> res = client.ListObjects("my-bucket", "path/to/", 2);
9393
if (!res)
9494
GTEST_FAIL();
9595

@@ -123,7 +123,7 @@ TEST(S3, ListObjectsCheckLenKeys) {
123123
S3Client client("minio_access", "minio_secret", "127.0.0.1:9000", S3AddressingStyle::PathStyle);
124124
try {
125125
// has 10K objects - limit is 1000 keys
126-
std::expected<ListBucketResult, Error> res = client.ListObjects("my-bucket", "path/to/");
126+
std::expected<ListObjectsResult, Error> res = client.ListObjects("my-bucket", "path/to/");
127127
if (!res)
128128
GTEST_FAIL();
129129
EXPECT_EQ(res->Contents.size(), 1000);
@@ -146,7 +146,7 @@ TEST(S3, ListObjectsPaginator) {
146146
int pageCount = 0;
147147

148148
while (paginator.HasMorePages()) {
149-
std::expected<ListBucketResult, Error> page = paginator.NextPage();
149+
std::expected<ListObjectsResult, Error> page = paginator.NextPage();
150150
if (!page) {
151151
GTEST_FAIL();
152152
}

0 commit comments

Comments
 (0)