Skip to content

Commit d3086ef

Browse files
committed
No more throws! Everything is std::expected now
1 parent a9892a7 commit d3086ef

5 files changed

Lines changed: 134 additions & 85 deletions

File tree

src/s3cpp/httpclient.cpp

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
#include <curl/curl.h>
22
#include <curl/easy.h>
3+
#include <expected>
34
#include <format>
45
#include <s3cpp/httpclient.h>
56
#include <stdexcept>
67
#include <string>
78

89
// Route to its HttpMethod
9-
HttpResponse HttpRequest::execute() {
10+
std::expected<HttpResponse, std::string> HttpRequest::execute() {
1011
switch (this->http_method_) {
1112
case HttpMethod::Get:
1213
return client_.execute_get(*this);
1314
case HttpMethod::Head:
1415
return client_.execute_head(*this);
1516
default:
16-
throw std::runtime_error(std::format("No matching enum Http Method"));
17+
return std::unexpected<std::string>("No matching enum Http Method");
1718
}
1819
}
1920

20-
HttpResponse HttpBodyRequest::execute() {
21+
std::expected<HttpResponse, std::string> HttpBodyRequest::execute() {
2122
switch (this->http_method_) {
2223
case HttpMethod::Post:
2324
return client_.execute_post(*this);
@@ -26,16 +27,15 @@ HttpResponse HttpBodyRequest::execute() {
2627
case HttpMethod::Delete:
2728
return client_.execute_delete(*this);
2829
default:
29-
throw std::runtime_error(std::format("No matching enum Http Method"));
30+
return std::unexpected<std::string>("No matching enum Http Method");
3031
}
3132
}
3233

33-
HttpResponse HttpClient::execute_get(HttpRequest& request) {
34+
std::expected<HttpResponse, std::string> HttpClient::execute_get(HttpRequest& request) {
3435
if (!curl_handle) {
35-
throw std::runtime_error(
36-
// this can happen both when cURL handle is not initialized or when it
37-
// is invalidated in the HTTPClient copy constructor
38-
"cURL handle is invalid");
36+
// this can happen both when cURL handle is not initialized or when it
37+
// is invalidated in the HTTPClient copy constructor
38+
return std::unexpected<std::string>("cURL handle is invalid");
3939
}
4040
std::string body_buf;
4141
std::map<std::string, std::string, LowerCaseCompare> headers_buf;
@@ -74,8 +74,7 @@ HttpResponse HttpClient::execute_get(HttpRequest& request) {
7474
CURLcode code = curl_easy_perform(curl_handle);
7575
curl_slist_free_all(list);
7676
if (code != CURLE_OK) {
77-
throw std::runtime_error(
78-
std::format("libcurl error: {}", curl_easy_strerror(code)));
77+
return std::unexpected<std::string>(std::format("libcurl error: {}", curl_easy_strerror(code)));
7978
}
8079

8180
// get HTTP code
@@ -86,12 +85,11 @@ HttpResponse HttpClient::execute_get(HttpRequest& request) {
8685
std::move(headers_buf));
8786
}
8887

89-
HttpResponse HttpClient::execute_head(HttpRequest& request) {
88+
std::expected<HttpResponse, std::string> HttpClient::execute_head(HttpRequest& request) {
9089
if (!curl_handle) {
91-
throw std::runtime_error(
92-
// this can happen both when cURL handle is not initialized or when it
93-
// is invalidated in the HTTPClient copy constructor
94-
"cURL handle is invalid");
90+
// this can happen both when cURL handle is not initialized or when it
91+
// is invalidated in the HTTPClient copy constructor
92+
return std::unexpected<std::string>("cURL handle is invalid");
9593
}
9694
std::map<std::string, std::string, LowerCaseCompare> headers_buf;
9795
std::string error_buf;
@@ -117,8 +115,7 @@ HttpResponse HttpClient::execute_head(HttpRequest& request) {
117115
CURLcode code = curl_easy_perform(curl_handle);
118116
curl_slist_free_all(list);
119117
if (code != CURLE_OK) {
120-
throw std::runtime_error(
121-
std::format("libcurl error for request: {}", curl_easy_strerror(code)));
118+
return std::unexpected<std::string>(std::format("libcurl error: {}", curl_easy_strerror(code)));
122119
}
123120

124121
// get HTTP code
@@ -128,12 +125,11 @@ HttpResponse HttpClient::execute_head(HttpRequest& request) {
128125
return HttpResponse(static_cast<int>(response_code), std::move(headers_buf));
129126
}
130127

131-
HttpResponse HttpClient::execute_post(HttpBodyRequest& request) {
128+
std::expected<HttpResponse, std::string> HttpClient::execute_post(HttpBodyRequest& request) {
132129
if (!curl_handle) {
133-
throw std::runtime_error(
134-
// this can happen both when cURL handle is not initialized or when it
135-
// is invalidated in the HTTPClient copy constructor
136-
"cURL handle is invalid");
130+
// this can happen both when cURL handle is not initialized or when it
131+
// is invalidated in the HTTPClient copy constructor
132+
return std::unexpected<std::string>("cURL handle is invalid");
137133
}
138134
std::string body_buf;
139135
std::map<std::string, std::string, LowerCaseCompare> headers_buf;
@@ -178,8 +174,7 @@ HttpResponse HttpClient::execute_post(HttpBodyRequest& request) {
178174
CURLcode code = curl_easy_perform(curl_handle);
179175
curl_slist_free_all(list);
180176
if (code != CURLE_OK) {
181-
throw std::runtime_error(
182-
std::format("libcurl error for request: {}", curl_easy_strerror(code)));
177+
return std::unexpected<std::string>(std::format("libcurl error: {}", curl_easy_strerror(code)));
183178
}
184179

185180
// get HTTP code
@@ -190,12 +185,11 @@ HttpResponse HttpClient::execute_post(HttpBodyRequest& request) {
190185
std::move(headers_buf));
191186
}
192187

193-
HttpResponse HttpClient::execute_delete(HttpBodyRequest& request) {
188+
std::expected<HttpResponse, std::string> HttpClient::execute_delete(HttpBodyRequest& request) {
194189
if (!curl_handle) {
195-
throw std::runtime_error(
196-
// this can happen both when cURL handle is not initialized or when it
197-
// is invalidated in the HTTPClient copy constructor
198-
"cURL handle is invalid");
190+
// this can happen both when cURL handle is not initialized or when it
191+
// is invalidated in the HTTPClient copy constructor
192+
return std::unexpected<std::string>("cURL handle is invalid");
199193
}
200194
std::string body_buf;
201195
std::map<std::string, std::string, LowerCaseCompare> headers_buf;
@@ -235,8 +229,7 @@ HttpResponse HttpClient::execute_delete(HttpBodyRequest& request) {
235229
CURLcode code = curl_easy_perform(curl_handle);
236230
curl_slist_free_all(list);
237231
if (code != CURLE_OK) {
238-
throw std::runtime_error(
239-
std::format("libcurl error for request: {}", curl_easy_strerror(code)));
232+
return std::unexpected<std::string>(std::format("libcurl error: {}", curl_easy_strerror(code)));
240233
}
241234

242235
// get HTTP code

src/s3cpp/httpclient.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <cstddef>
66
#include <curl/curl.h>
77
#include <curl/easy.h>
8+
#include <expected>
89
#include <map>
910
#include <stdexcept>
1011
#include <string>
@@ -137,7 +138,7 @@ class HttpRequestBase {
137138
class HttpRequest : public HttpRequestBase<HttpRequest> {
138139
public:
139140
using HttpRequestBase::HttpRequestBase;
140-
HttpResponse execute();
141+
std::expected<HttpResponse, std::string> execute();
141142
};
142143

143144
// POST/PUT
@@ -155,7 +156,7 @@ class HttpBodyRequest : public HttpRequestBase<HttpBodyRequest> {
155156

156157
const std::string& getBody() const { return body_; }
157158

158-
HttpResponse execute();
159+
std::expected<HttpResponse, std::string> execute();
159160

160161
private:
161162
std::string body_ = "";
@@ -235,10 +236,10 @@ class HttpClient {
235236

236237
// main logic to perform the request
237238
// this is invoked by HttpRequest
238-
HttpResponse execute_get(HttpRequest& request);
239-
HttpResponse execute_head(HttpRequest& request);
240-
HttpResponse execute_post(HttpBodyRequest& request);
241-
HttpResponse execute_delete(HttpBodyRequest& request);
239+
std::expected<HttpResponse, std::string> execute_get(HttpRequest& request);
240+
std::expected<HttpResponse, std::string> execute_head(HttpRequest& request);
241+
std::expected<HttpResponse, std::string> execute_post(HttpBodyRequest& request);
242+
std::expected<HttpResponse, std::string> execute_delete(HttpBodyRequest& request);
242243

243244
const std::unordered_map<std::string, std::string>& getHeaders() const {
244245
return headers_;

src/s3cpp/s3.cpp

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ std::expected<ListObjectsResult, Error> S3Client::ListObjects(const std::string&
3636
req.header("x-amz-request-payer", options.RequestPayer.value());
3737

3838
Signer.sign(req);
39-
HttpResponse res = req.execute();
39+
auto result = req.execute();
40+
if (!result.has_value()) {
41+
return std::unexpected<Error>(Error { .Code = "HttpError", .Message = result.error() });
42+
}
43+
HttpResponse res = result.value();
4044

4145
const std::vector<XMLNode>& XMLBody = Parser.parse(res.body());
4246

@@ -73,7 +77,11 @@ std::expected<ListAllMyBucketsResult, Error> S3Client::ListBuckets(const ListBuc
7377
HttpRequest req = Client.get(url).header("Host", endpoint_);
7478

7579
Signer.sign(req);
76-
HttpResponse res = req.execute();
80+
auto result = req.execute();
81+
if (!result.has_value()) {
82+
return std::unexpected<Error>(Error { .Code = "HttpError", .Message = result.error() });
83+
}
84+
HttpResponse res = result.value();
7785

7886
const std::vector<XMLNode>& XMLBody = Parser.parse(res.body());
7987

@@ -274,7 +282,11 @@ std::expected<std::string, Error> S3Client::GetObject(const std::string& bucket,
274282
req.header("If-Unmodified-Since", options.If_Unmodified_Since.value());
275283

276284
Signer.sign(req);
277-
HttpResponse res = req.execute();
285+
auto result = req.execute();
286+
if (!result.has_value()) {
287+
return std::unexpected<Error>(Error { .Code = "HttpError", .Message = result.error() });
288+
}
289+
HttpResponse res = result.value();
278290

279291
if (res.is_ok()) {
280292
return res.body();
@@ -295,7 +307,11 @@ std::expected<PutObjectResult, Error> S3Client::PutObject(const std::string& buc
295307
// ...
296308

297309
Signer.sign(req);
298-
HttpResponse res = req.execute();
310+
auto result = req.execute();
311+
if (!result.has_value()) {
312+
return std::unexpected<Error>(Error { .Code = "HttpError", .Message = result.error() });
313+
}
314+
HttpResponse res = result.value();
299315

300316
if (res.is_ok()) {
301317
return deserializePutObjectResult(res.headers());
@@ -328,7 +344,11 @@ std::expected<DeleteObjectResult, Error> S3Client::DeleteObject(const std::strin
328344
req.header("x-amz-if-match-size", options.If_MatchSize.value());
329345

330346
Signer.sign(req);
331-
HttpResponse res = req.execute();
347+
auto result = req.execute();
348+
if (!result.has_value()) {
349+
return std::unexpected<Error>(Error { .Code = "HttpError", .Message = result.error() });
350+
}
351+
HttpResponse res = result.value();
332352

333353
if (res.is_ok()) {
334354
return deserializeDeleteObjectResult(res.headers());
@@ -402,7 +422,11 @@ std::expected<CreateBucketResult, Error> S3Client::CreateBucket(
402422
req.body(std::move(createBucketReqBodyXML));
403423

404424
Signer.sign(req);
405-
HttpResponse res = req.execute();
425+
auto result = req.execute();
426+
if (!result.has_value()) {
427+
return std::unexpected<Error>(Error { .Code = "HttpError", .Message = result.error() });
428+
}
429+
HttpResponse res = result.value();
406430

407431
if (res.is_ok()) {
408432
return deserializeCreateBucketResult(res.headers());
@@ -421,7 +445,11 @@ std::expected<void, Error> S3Client::DeleteBucket(const std::string& bucket, con
421445
req.header("x-amz-expected-bucket-owner", std::move(options.ExpectedBucketOwner.value()));
422446

423447
Signer.sign(req);
424-
HttpResponse res = req.execute();
448+
auto result = req.execute();
449+
if (!result.has_value()) {
450+
return std::unexpected<Error>(Error { .Code = "HttpError", .Message = result.error() });
451+
}
452+
HttpResponse res = result.value();
425453

426454
if (res.status() == 204) {
427455
return {};
@@ -440,7 +468,11 @@ std::expected<HeadBucketResult, Error> S3Client::HeadBucket(const std::string& b
440468
req.header("x-amz-expected-bucket-owner", std::move(options.ExpectedBucketOwner.value()));
441469

442470
Signer.sign(req);
443-
HttpResponse res = req.execute();
471+
auto result = req.execute();
472+
if (!result.has_value()) {
473+
return std::unexpected<Error>(Error { .Code = "HttpError", .Message = result.error() });
474+
}
475+
HttpResponse res = result.value();
444476

445477
if (res.status() == 200) {
446478
return deserializeHeadBucketResult(res.headers());
@@ -532,7 +564,11 @@ std::expected<HeadObjectResult, Error> S3Client::HeadObject(const std::string& b
532564
req.header("x-amz-server-side-encryption-customer-key-MD5", options.SideEncryptionCustomerKeyMD5.value());
533565

534566
Signer.sign(req);
535-
HttpResponse res = req.execute();
567+
auto result = req.execute();
568+
if (!result.has_value()) {
569+
return std::unexpected<Error>(Error { .Code = "HttpError", .Message = result.error() });
570+
}
571+
HttpResponse res = result.value();
536572

537573
if (res.status() == 200) {
538574
return deserializeHeadObjectResult(res.headers());

test/auth_test.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,16 @@ TEST(AUTH, MinIOBasicRequest) {
114114
.header("X-Amz-Content-Sha256", empty_payload_hash);
115115
signer.sign(req);
116116

117-
try {
118-
HttpResponse resp = req.execute();
119-
EXPECT_EQ(resp.status(), 200);
120-
} catch (const std::exception& e) {
121-
// Our exception in the GitHub CI will be "Couldn't connect to server"
122-
// will be exactly returned as a runtime error like so:
123-
// `libcurl error: Couldn't connect to server`
124-
const std::string emsg = e.what();
117+
auto result = req.execute();
118+
if (!result.has_value()) {
119+
// Our error in the GitHub CI will be "Couldn't connect to server"
120+
const std::string emsg = result.error();
125121
// CI has a different libcurl version xd
126122
if (emsg == "libcurl error: Could not connect to server" || emsg == "libcurl error: Couldn't connect to server") {
127123
GTEST_SKIP_("Skipping MinIOBasicRequest: Server not up");
128124
}
129-
throw;
125+
FAIL() << "Request failed: " << emsg;
130126
}
127+
HttpResponse resp = result.value();
128+
EXPECT_EQ(resp.status(), 200);
131129
}

0 commit comments

Comments
 (0)