From eb3916c84e266c034e61580bc5d18e96ec873b8a Mon Sep 17 00:00:00 2001 From: tastytea Date: Sat, 13 Jan 2018 15:49:46 +0100 Subject: [PATCH] error handling, debug output, documentation --- CMakeLists.txt | 2 +- README.md | 31 +++++++++++++- src/api_get.cpp | 47 ++++++++++++--------- src/example/example.cpp | 54 ++++++++++++++++++++---- src/http_sync.cpp | 37 +++++++++++++---- src/macros.hpp | 28 +++++++++++++ src/mastodon-cpp.hpp | 67 +++++++++++++++++++++--------- src/tests/test_01_get_instance.cpp | 21 ++++++---- 8 files changed, 220 insertions(+), 67 deletions(-) create mode 100644 src/macros.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index aee1539..6a96d89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required (VERSION 3.7) project (mastodon-cpp - VERSION 0.0.3 + VERSION 0.1.0 LANGUAGES CXX ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") diff --git a/README.md b/README.md index 8117e2d..591a439 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ The library takes care of the network stuff. You submit a query and get the raw All versions below 1.0.0 (SOVERSION 0) are considered insecure, unstable and can change any time. # Install + ## Dependencies + * Tested OS: Linux * C++ compiler (tested: gcc 6.4) * [cmake](https://cmake.org/) (tested: 3.9.6) @@ -11,34 +13,59 @@ All versions below 1.0.0 (SOVERSION 0) are considered insecure, unstable and can * Optional: [doxygen](https://www.stack.nl/~dimitri/doxygen/) (tested: 1.8.13) ## Get sourcecode + ### Development version + git clone https://… ## Compile + mkdir build cd build/ cmake .. make +If you want to compile a debug build, use `cmake -DCMAKE_BUILD_TYPE=Debug` +instead. + # Usage The reference can be generated with `build_doc.sh`, if doxygen is installed. Or just look in `src/mastodon-cpp.hpp`. There is an example in `src/example`. +## Error codes + +| Code | Explanation | +| --------: |:------------------------------| +| 0 | No error | +| 1 | Invalid call | +| 2 | Not implemented | +| 16 | Connection failed | +| 17 | TLS error | +| 18 | Invalid response from server | +| 100 - 999 | HTTP status codes | +| 65535 | Unknown exception | + +If you use a debug build, you get more verbose error messages. + # TODO + * Version 0.1.0 * [x] Implement all GET methods - * [ ] Proper error handling + * [x] Usable error handling * [x] Network stuff - * [ ] Comprehensive example + * [x] Comprehensive example * Version 0.2.0 * [ ] Implement all PATCH methods * [ ] Implement all POST methods * [ ] Implement all DELETE methods + * Version 0.3.0 + * [ ] Handling HTTP statuses 301 & 302 * Later * [ ] Escape user input * [ ] Asynchronous I/O ## Status of implementation + * [x] GET /api/v1/accounts/:id * [x] GET /api/v1/accounts/verify_credentials * [ ] PATCH /api/v1/accounts/update_credentials diff --git a/src/api_get.cpp b/src/api_get.cpp index 343f24d..cb9c880 100644 --- a/src/api_get.cpp +++ b/src/api_get.cpp @@ -22,14 +22,14 @@ using namespace Mastodon; using std::string; using std::cerr; -const string API::get(const Mastodon::API::v1 &call) +const std::uint16_t API::get(const Mastodon::API::v1 &call, string &answer) { const std::vector v{}; - return get(call, v); + return get(call, v, answer); } -const string API::get(const Mastodon::API::v1 &call, - const std::vector ¶meters) +const std::uint16_t API::get(const Mastodon::API::v1 &call, + const std::vector ¶meters, string &answer) { string strcall = ""; switch (call) @@ -75,24 +75,35 @@ const string API::get(const Mastodon::API::v1 &call, break; default: cerr << "ERROR: Invalid call.\n"; - return ""; + return 1; break; } - string answer; - _http.request_sync(http::method::GET, strcall, answer); - return answer; + if (parameters.size() > 0) + { + char delim = '?'; + for (const string p : parameters) + { + strcall += delim + p; + if (delim == '?') + { + delim = '&'; + } + } + } + + return _http.request_sync(http::method::GET, strcall, answer); } -const string API::get(const Mastodon::API::v1 &call, - const string &argument) +const std::uint16_t API::get(const Mastodon::API::v1 &call, + const string &argument, string &answer) { const std::vector v; - return get(call, argument, v); + return get(call, argument, v, answer); } -const string API::get(const Mastodon::API::v1 &call, +const std::uint16_t API::get(const Mastodon::API::v1 &call, const string &argument, - const std::vector ¶meters) + const std::vector ¶meters, string &answer) { string strcall = ""; char delim = '?'; @@ -158,7 +169,7 @@ const string API::get(const Mastodon::API::v1 &call, break; default: cerr << "ERROR: Invalid call.\n"; - return ""; + return 1; break; } @@ -174,12 +185,10 @@ const string API::get(const Mastodon::API::v1 &call, } } - string answer; - _http.request_sync(http::method::GET, strcall, answer); - return answer; + return _http.request_sync(http::method::GET, strcall, answer); } -const string API::get(const std::string &call) +const std::uint16_t API::get(const std::string &call, string &answer) { - return call; + return _http.request_sync(http::method::GET, call, answer); } diff --git a/src/example/example.cpp b/src/example/example.cpp index 385846c..f28cc55 100644 --- a/src/example/example.cpp +++ b/src/example/example.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../mastodon-cpp.hpp" using Mastodon::API; @@ -17,13 +18,52 @@ int main(int argc, char *argv[]) } Mastodon::API masto(argv[1], argv[2]); + std::string answer; + std::uint16_t ret; - std::vector parameters = + ret = masto.get(API::v1::accounts_verify_credentials, answer); + if (ret == 0) { - "limit=2", - "only_media=1" - }; - std::cout << - masto.get(API::v1::accounts_id_statuses, "44897", parameters) << - '\n'; + std::cout << "Your last toot with media attached:\n"; + std::string uid = answer.substr(7, answer.find("\"", 7) - 7); + std::vector parameters = + { + "limit=1", + "only_media=1" + }; + + ret = masto.get(API::v1::accounts_id_statuses, uid, + parameters, answer); + if (ret == 0) + { + std::cout << answer << '\n'; + } + else + { + std::cerr << "Error code: " << ret << '\n'; + return ret; + } + + std::cout << "\nYour last 2 followers:\n"; + parameters = + { + "limit=2", + "exclude_types[]=favourite", + "exclude_types[]=reblog", + "exclude_types[]=mention" + }; + ret = masto.get(API::v1::notifications, parameters, answer); + if (ret == 0) + { + std::cout << answer << '\n'; + return 0; + } + else + { + std::cerr << "Error code: " << ret << '\n'; + return ret; + } + } + + return 0; } diff --git a/src/http_sync.cpp b/src/http_sync.cpp index 177ddcd..a7fb153 100644 --- a/src/http_sync.cpp +++ b/src/http_sync.cpp @@ -21,6 +21,7 @@ #include #include #include +#include "macros.hpp" #include "mastodon-cpp.hpp" using namespace Mastodon; @@ -64,6 +65,15 @@ const std::uint16_t API::http::request_sync(const method &meth, tcp::resolver::iterator endpoint_iterator = _resolver.resolve(query); boost::asio::connect(_socket.lowest_layer(), endpoint_iterator); _socket.lowest_layer().set_option(tcp::no_delay(true)); + } + catch (const std::exception &e) + { + ttdebug << "ERROR: " << e.what() << "\n"; + return 16; + } + + try + { // Server Name Indication (SNI) SSL_set_tlsext_host_name(_socket.native_handle(), _instance.c_str()); @@ -71,7 +81,15 @@ const std::uint16_t API::http::request_sync(const method &meth, _socket.set_verify_callback(ssl::rfc2818_verification(_instance)); _socket.handshake(ssl_socket::client); + } + catch (const std::exception &e) + { + ttdebug << "ERROR: " << e.what() << "\n"; + return 17; + } + try + { boost::asio::streambuf request; std::ostream request_stream(&request); switch (meth) @@ -90,8 +108,8 @@ const std::uint16_t API::http::request_sync(const method &meth, // request_stream << "DELETE"; // break; default: - cerr << "NOT IMPLEMENTED\n"; - return 0xffff; + ttdebug << "ERROR: Not implemented\n"; + return 2; } request_stream << " HTTP/1.0\r\n"; request_stream << "Host: " << _instance << "\r\n"; @@ -115,14 +133,16 @@ const std::uint16_t API::http::request_sync(const method &meth, std::getline(response_stream, status_message); if (!response_stream || http_version.substr(0, 5) != "HTTP/") { - cerr << "Invalid response\n"; - return 0xffff; + ttdebug << "ERROR: Invalid response from server\n"; + ttdebug << "Response was: " << http_version << " " << status_code + << " " << status_message << '\n'; + return 18; } if (status_code != 200) { - cerr << "Response returned with status code " << status_code - << ": " << status_message << "\n"; - return 0xffff; + ttdebug << "ERROR: Response returned with status code " + << status_code << ": " << status_message << "\n"; + return status_code; } // Read headers @@ -130,7 +150,6 @@ const std::uint16_t API::http::request_sync(const method &meth, std::string header; while (std::getline(response_stream, header) && header != "\r") {} - //response.consume(response.size()); // Read body boost::system::error_code error; @@ -149,7 +168,7 @@ const std::uint16_t API::http::request_sync(const method &meth, } catch (const std::exception &e) { - cerr << "Exception: " << e.what() << "\n"; + ttdebug << "Exception: " << e.what() << "\n"; return 0xffff; } diff --git a/src/macros.hpp b/src/macros.hpp new file mode 100644 index 0000000..50cb294 --- /dev/null +++ b/src/macros.hpp @@ -0,0 +1,28 @@ +/* This file is part of mastodon-cpp. + * Copyright © 2018 tastytea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MACROS_HPP +#define MACROS_HPP + + +#ifdef DEBUG + #define ttdebug std::cerr << "[" << __FILE__ << ":" << __LINE__ << "] DEBUG: " +#else + #define ttdebug false && std::cerr +#endif + + +#endif // MACROS_HPP diff --git a/src/mastodon-cpp.hpp b/src/mastodon-cpp.hpp index a91cb6b..638df09 100644 --- a/src/mastodon-cpp.hpp +++ b/src/mastodon-cpp.hpp @@ -83,21 +83,26 @@ public: * @brief Make a GET request which doesn't require an argument. * * @param call A call defined in Mastodon::API::v1 + * @param answer The answer from the server. Usually JSON. On error an + * empty string. * - * @return The answer from the server. Usually JSON. + * @return The HTTP error code, or 0xffff if an other error happens. */ - const std::string get(const Mastodon::API::v1 &call); + const std::uint16_t get(const Mastodon::API::v1 &call, std::string &answer); /*! * @brief Make a GET request which requires an argument * * @param call A call defined in Mastodon::API::v1 * @param argument The non-optional argument + * @param answer The answer from the server. Usually JSON. On error an + * empty string. * - * @return The answer from the server. Usually JSON. + * @return The HTTP error code, or 0xffff if an other error happens. */ - const std::string get(const Mastodon::API::v1 &call, - const std::string &argument); + const std::uint16_t get(const Mastodon::API::v1 &call, + const std::string &argument, + std::string &answer); /*! * @brief Make a GET request which doesn't require an argument, pass @@ -106,11 +111,14 @@ public: * @param call A call defined in Mastodon::API::v1 * @param parameters A std::vector containing optional parameters in the * form `field=value` + * @param answer The answer from the server. Usually JSON. On error + * an empty string. * - * @return The answer from the server. Usually JSON. + * @return The HTTP error code, or 0xffff if an other error happens. */ - const std::string get(const Mastodon::API::v1 &call, - const std::vector ¶meters); + const std::uint16_t get(const Mastodon::API::v1 &call, + const std::vector ¶meters, + std::string &answer); /*! * @brief Make a GET request which requires an argument, pass optional @@ -119,32 +127,38 @@ public: * Example: * * Mastodon::API masto(argv[1], argv[2]); - * std::vector parameters = - * { - * "limit=2", - * "only_media=1" - * }; - * masto.get(Mastodon::API::v1::accounts_id_statuses, "12345", parameters); + * std::vector parameters = + * { + * "limit=2", + * "only_media=1" + * }; + * masto.get(Mastodon::API::v1::accounts_id_statuses, "12345", parameters); * * @param call A call defined in Mastodon::API::v1 * @param argument The non-optional argument * @param parameters A std::vector containing optional parameters in the * form `field=value` + * @param answer The answer from the server. Usually JSON. On error + * an empty string. * - * @return The answer from the server. Usually JSON. + * @return The HTTP error code, or 0xffff if an other error happens. */ - const std::string get(const Mastodon::API::v1 &call, - const std::string &argument, - const std::vector ¶meters); + const std::uint16_t get(const Mastodon::API::v1 &call, + const std::string &argument, + const std::vector ¶meters, + std::string &answer); /*! * @brief Make a custom GET request. * * @param call String in the form `/api/v1/example` + * @param answer The answer from the server. Usually JSON. On error an + * empty string. * - * @return The answer from the server. Usually JSON. + * @return The HTTP error code, or 0xffff if an other error happens. */ - const std::string get(const std::string &call); + const std::uint16_t get(const std::string &call, + std::string &answer); /*! * @brief Sets the useragent. Default is mastodon-cpp/version. @@ -181,6 +195,19 @@ private: const std::uint16_t request_sync(const method &meth, const std::string &path, std::string &answer); + + /*! + * @brief Blocking request. + * + * @param meth The method defined in http::method + * @param path The api call as string + * @param data The form data for PATCH and POST request. Not + * implemented at the moment. This will likely change + * into a std::vector. + * @param answer The answer from the server + * + * @return The HTTP error code, or 0xffff if an other error happens. + */ const std::uint16_t request_sync(const method &meth, const std::string &path, const std::string &data, diff --git a/src/tests/test_01_get_instance.cpp b/src/tests/test_01_get_instance.cpp index 474a86d..b43b72c 100644 --- a/src/tests/test_01_get_instance.cpp +++ b/src/tests/test_01_get_instance.cpp @@ -2,19 +2,22 @@ */ #include +#include #include "../mastodon-cpp.hpp" int main(int argc, char *argv[]) { Mastodon::API test("soc.ialis.me", ""); - std::string answer = test.get(Mastodon::API::v1::instance); + std::string answer; + std::uint16_t ret = test.get(Mastodon::API::v1::instance, answer); + if (ret == 0) + { + if (answer.substr(7, 14) == "\"soc.ialis.me\"") + { + return 0; + } + } - if (answer.substr(7, 14) == "\"soc.ialis.me\"") - { - return 0; - } - else - { - return 1; - } + std::cout << ret << ": " << answer.substr(7, 14) << '\n'; + return 1; }