Skip to content

Commit

Permalink
Merge pull request #61 from DEGoodmanWilson/master
Browse files Browse the repository at this point in the history
Adding error objects to request responses
  • Loading branch information
whoshuu committed Dec 11, 2015
2 parents 08c93fd + a74f4f7 commit bdb877c
Show file tree
Hide file tree
Showing 19 changed files with 446 additions and 26 deletions.
4 changes: 3 additions & 1 deletion cpr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ add_library(${CPR_LIBRARIES}
cookies.cpp
cprtypes.cpp
digest.cpp
error.cpp
multipart.cpp
parameters.cpp
payload.cpp
proxies.cpp
session.cpp
util.cpp
util.cpp

# Header files (useful in IDEs)
"${CPR_INCLUDE_DIRS}/api.h"
Expand All @@ -27,6 +28,7 @@ add_library(${CPR_LIBRARIES}
"${CPR_INCLUDE_DIRS}/curlholder.h"
"${CPR_INCLUDE_DIRS}/defines.h"
"${CPR_INCLUDE_DIRS}/digest.h"
"${CPR_INCLUDE_DIRS}/error.h"
"${CPR_INCLUDE_DIRS}/multipart.h"
"${CPR_INCLUDE_DIRS}/parameters.h"
"${CPR_INCLUDE_DIRS}/payload.h"
Expand Down
41 changes: 41 additions & 0 deletions cpr/error.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "error.h"
#include <curl/curl.h>

namespace cpr {

ErrorCode getErrorCodeForCurlError(int curlCode) {
switch(curlCode) {
case CURLE_OK: return ErrorCode::OK;

case CURLE_UNSUPPORTED_PROTOCOL: return ErrorCode::UNSUPPORTED_PROTOCOL;
case CURLE_URL_MALFORMAT: return ErrorCode::INVALID_URL_FORMAT;
case CURLE_COULDNT_RESOLVE_PROXY: return ErrorCode::PROXY_RESOLUTION_FAILURE;
case CURLE_COULDNT_RESOLVE_HOST: return ErrorCode::HOST_RESOLUTION_FAILURE;
case CURLE_COULDNT_CONNECT: return ErrorCode::CONNECTION_FAILURE;
case CURLE_OPERATION_TIMEDOUT: return ErrorCode::OPERATION_TIMEDOUT;
case CURLE_SSL_CONNECT_ERROR: return ErrorCode::SSL_CONNECT_ERROR;
case CURLE_PEER_FAILED_VERIFICATION: return ErrorCode::SSL_REMOTE_CERTIFICATE_ERROR;
case CURLE_GOT_NOTHING: return ErrorCode::EMPTY_RESPONSE;
case CURLE_SSL_ENGINE_NOTFOUND: return ErrorCode::GENERIC_SSL_ERROR;
case CURLE_SSL_ENGINE_SETFAILED: return ErrorCode::GENERIC_SSL_ERROR;
case CURLE_SEND_ERROR: return ErrorCode::NETWORK_SEND_FAILURE;
case CURLE_RECV_ERROR: return ErrorCode::NETWORK_RECEIVE_ERROR;
case CURLE_SSL_CERTPROBLEM: return ErrorCode::SSL_LOCAL_CERTIFICATE_ERROR;
case CURLE_SSL_CIPHER: return ErrorCode::GENERIC_SSL_ERROR;
case CURLE_SSL_CACERT: return ErrorCode::SSL_CACERT_ERROR;
case CURLE_USE_SSL_FAILED: return ErrorCode::GENERIC_SSL_ERROR;
case CURLE_SSL_ENGINE_INITFAILED: return ErrorCode::GENERIC_SSL_ERROR;
case CURLE_SSL_CACERT_BADFILE: return ErrorCode::SSL_CACERT_ERROR;
case CURLE_SSL_SHUTDOWN_FAILED: return ErrorCode::GENERIC_SSL_ERROR;
case CURLE_SSL_CRL_BADFILE: return ErrorCode::SSL_CACERT_ERROR;
case CURLE_SSL_ISSUER_ERROR: return ErrorCode::SSL_CACERT_ERROR;

//Degenerate errors that are already being handled in application-level code in other ways
case CURLE_TOO_MANY_REDIRECTS: return ErrorCode::OK;

default: return ErrorCode::INTERNAL_ERROR;
}

}

}
10 changes: 8 additions & 2 deletions cpr/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ Response Session::Impl::makeRequest(CURL* curl) {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_string);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header_string);

curl_easy_perform(curl);
auto curl_error = curl_easy_perform(curl);

char* raw_url;
long response_code;
Expand All @@ -356,6 +356,12 @@ Response Session::Impl::makeRequest(CURL* curl) {
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &elapsed);
curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &raw_url);

Error error;
if (curl_error != CURLE_OK) {
error.code = getErrorCodeForCurlError(curl_error);
error.message = curl_->error; //copies the error message
}

Cookies cookies;
struct curl_slist* raw_cookies;
curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &raw_cookies);
Expand All @@ -369,7 +375,7 @@ Response Session::Impl::makeRequest(CURL* curl) {

auto header = cpr::util::parseHeader(header_string);
response_string = cpr::util::parseResponse(response_string);
return Response{response_code, response_string, header, raw_url, elapsed, cookies};
return Response{response_code, response_string, header, raw_url, elapsed, error, cookies};
}

// clang-format off
Expand Down
55 changes: 55 additions & 0 deletions include/error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#ifndef CPR_ERROR_H
#define CPR_ERROR_H

#include <string>

#include "cprtypes.h"
#include "defines.h"

namespace cpr {

enum class ErrorCode {
OK = 0,
CONNECTION_FAILURE,
EMPTY_RESPONSE,
HOST_RESOLUTION_FAILURE,
INTERNAL_ERROR,
INVALID_URL_FORMAT,
NETWORK_RECEIVE_ERROR,
NETWORK_SEND_FAILURE,
OPERATION_TIMEDOUT,
PROXY_RESOLUTION_FAILURE,
SSL_CONNECT_ERROR,
SSL_LOCAL_CERTIFICATE_ERROR,
SSL_REMOTE_CERTIFICATE_ERROR,
SSL_CACERT_ERROR,
GENERIC_SSL_ERROR,
UNSUPPORTED_PROTOCOL,
UNKNOWN_ERROR = 1000,
};

ErrorCode getErrorCodeForCurlError(int curlCode); //int so we don't have to include curl.h

class Error {
public:
Error()
: code{ErrorCode::OK}, message{""} {}

template <typename ErrorCodeType, typename TextType>
Error(ErrorCode& p_error_code, TextType&& p_error_message)
: code{p_error_code}, message{CPR_FWD(p_error_message)} {}


ErrorCode code;
std::string message;

//allow easy checking of errors with:
// if(error) { do something; }
explicit operator bool() const {
return code != ErrorCode::OK;
}
};

} // namespace cpr

#endif
10 changes: 7 additions & 3 deletions include/response.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,28 @@
#include "cookies.h"
#include "cprtypes.h"
#include "defines.h"
#include "error.h"

namespace cpr {
class Response {
public:
Response() = default;

template <typename TextType, typename HeaderType, typename UrlType, typename CookiesType>
template <typename TextType, typename HeaderType, typename UrlType, typename CookiesType, typename ErrorType>
Response(const long& p_status_code, TextType&& p_text, HeaderType&& p_header, UrlType&& p_url,
const double& p_elapsed, CookiesType&& p_cookies = Cookies{})
const double& p_elapsed, ErrorType&& p_error = Error{}, CookiesType&& p_cookies = Cookies{})
: status_code{p_status_code}, text{CPR_FWD(p_text)}, header{CPR_FWD(p_header)},
url{CPR_FWD(p_url)}, elapsed{p_elapsed}, cookies{CPR_FWD(p_cookies)} {}
url{CPR_FWD(p_url)}, elapsed{p_elapsed}, cookies{CPR_FWD(p_cookies)}, error{CPR_FWD(p_error)} {}

long status_code;
std::string text;
Header header;
Url url;
double elapsed;
Cookies cookies;

//error conditions
Error error;
};

} // namespace cpr
Expand Down
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ macro(add_cpr_test _TEST_NAME)
endmacro()

include_directories(
${CURL_INCLUDE_DIRS}
${CMAKE_CURRENT_SOURCE_DIR}
${CPR_INCLUDE_DIRS}
${GTEST_INCLUDE_DIRS}
Expand All @@ -33,3 +34,4 @@ add_cpr_test(callback)
add_cpr_test(raw_body)
add_cpr_test(options)
add_cpr_test(patch)
add_cpr_test(error)
14 changes: 14 additions & 0 deletions test/delete_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ TEST(DeleteTests, DeleteTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(200, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, DeleteUnallowedTest) {
Expand All @@ -29,6 +30,7 @@ TEST(DeleteTests, DeleteUnallowedTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(405, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, SessionDeleteTest) {
Expand All @@ -41,6 +43,7 @@ TEST(DeleteTests, SessionDeleteTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(200, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, SessionDeleteUnallowedTest) {
Expand All @@ -53,6 +56,7 @@ TEST(DeleteTests, SessionDeleteUnallowedTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(405, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, SessionDeleteAfterGetTest) {
Expand All @@ -70,6 +74,7 @@ TEST(DeleteTests, SessionDeleteAfterGetTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(200, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, SessionDeleteUnallowedAfterGetTest) {
Expand All @@ -87,6 +92,7 @@ TEST(DeleteTests, SessionDeleteUnallowedAfterGetTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(405, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, SessionDeleteAfterHeadTest) {
Expand All @@ -104,6 +110,7 @@ TEST(DeleteTests, SessionDeleteAfterHeadTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(200, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, SessionDeleteUnallowedAfterHeadTest) {
Expand All @@ -121,6 +128,7 @@ TEST(DeleteTests, SessionDeleteUnallowedAfterHeadTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(405, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, SessionDeleteAfterPostTest) {
Expand All @@ -139,6 +147,7 @@ TEST(DeleteTests, SessionDeleteAfterPostTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(200, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, SessionDeleteUnallowedAfterPostTest) {
Expand All @@ -157,6 +166,7 @@ TEST(DeleteTests, SessionDeleteUnallowedAfterPostTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(405, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, AsyncDeleteTest) {
Expand All @@ -168,6 +178,7 @@ TEST(DeleteTests, AsyncDeleteTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(200, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, AsyncDeleteUnallowedTest) {
Expand All @@ -179,6 +190,7 @@ TEST(DeleteTests, AsyncDeleteUnallowedTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(405, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}

TEST(DeleteTests, AsyncMultipleDeleteTest) {
Expand All @@ -194,6 +206,7 @@ TEST(DeleteTests, AsyncMultipleDeleteTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(200, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}
}

Expand All @@ -210,6 +223,7 @@ TEST(DeleteTests, AsyncMultipleDeleteUnallowedTest) {
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(405, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}
}

Expand Down
74 changes: 74 additions & 0 deletions test/error_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include <gtest/gtest.h>

#include <string>

#include <cpr.h>
#include <curl/curl.h>


#include "server.h"

using namespace cpr;

static Server* server = new Server();
auto base = server->GetBaseUrl();
auto baseSSL = server->GetBaseUrlSSL();

TEST(ErrorTests, BasicSSLFailure) {
//If CURL has SSL enabled, we should expect SSL_CONECT_ERROR
// If not, we shoujld expect UNSUPPORTED_PROTOCOL


auto url = Url{baseSSL + "/hello.html"};
auto response = cpr::Get(url);
EXPECT_EQ(url, response.url);
EXPECT_EQ(0, response.status_code);

//This is ugly, because it presume CURL under the hood. Might be nice to have something in `cpr::Session`
// That could tell us if SSL is supported.
auto curl_version = curl_version_info(CURLVERSION_NOW);
auto expected = ErrorCode::UNSUPPORTED_PROTOCOL;
if(curl_version->features & CURL_VERSION_SSL) {
expected = ErrorCode::SSL_CONNECT_ERROR;
}
EXPECT_EQ(expected, response.error.code);

}

//Not terribly sure how to test other SSL error codes

TEST(ErrorTests, UnsupportedProtocolFailure) {
auto url = Url{"urk://wat.is.this"};
auto response = cpr::Get(url);
EXPECT_EQ(0, response.status_code);
EXPECT_EQ(ErrorCode::UNSUPPORTED_PROTOCOL, response.error.code);
}

TEST(ErrorTests, InvalidURLFailure) {
auto url = Url{"???"};
auto response = cpr::Get(url);
EXPECT_EQ(0, response.status_code);
EXPECT_EQ(ErrorCode::INVALID_URL_FORMAT, response.error.code);
}

TEST(ErrorTests, TimeoutFailure) {
auto url = Url{"http://railstars.com"}; //my own site, and notoriously slow to load. Need a better exemplar
auto response = cpr::Get(url, cpr::Timeout{1});
EXPECT_EQ(0, response.status_code);
EXPECT_EQ(ErrorCode::OPERATION_TIMEDOUT, response.error.code);
}

TEST(ErrorTests, ProxyFailure) {
auto url = Url{base + "/hello.html"};
auto response = cpr::Get(url, cpr::Proxies{{"http", "http://bad_host/"}});
EXPECT_EQ(url, response.url);
EXPECT_EQ(0, response.status_code);
EXPECT_EQ(ErrorCode::PROXY_RESOLUTION_FAILURE, response.error.code);
}


int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
::testing::AddGlobalTestEnvironment(server);
return RUN_ALL_TESTS();
}
Loading

0 comments on commit bdb877c

Please sign in to comment.