diff --git a/README.adoc b/README.adoc index 7b40e52..c5a856d 100644 --- a/README.adoc +++ b/README.adoc @@ -15,13 +15,55 @@ :uri-curl: https://curl.haxx.se/ *{project}* is a C++ wrapper for the Mastodon API. It replaces - link:{uri-mastodon-cpp}[mastodon-cpp]. +link:{uri-mastodon-cpp}[mastodon-cpp]. + +We aim to create a library that is comfortable, yet minimal. All API endpoints +from Mastodon and Pleroma are stored in `enum class`es, to counteract typos and +make your life easier. The network-facing code is built on +link:{uri-curl}[libcurl], a mature and stable library that is available on +virtually every operating system. The library does not parse the responses +itself, but returns to you the raw data, because we know everyone has their +favorite JSON library and we don't want to impose our choice on you! + +== Features + +This is still a work in progress; here is a rough overview of the features: + +* [ ] Requests + ** [x] `GET` requests. + ** [x] Streaming `GET` requests. + ** [ ] `POST` requests. + ** [ ] `PATCH` requests. + ** [ ] `PUT` requests. + ** [ ] `DELETE` requests. +* [x] Report maximum allowed character per post. +* [ ] Comfortable access to pagination headers. +* [ ] Comfortable function to register a new “app” (get an access token). == Usage Have a look at the link:{uri-reference}[reference]. -// === Examples +=== Example + +[source,cpp] +-------------------------------------------------------------------------------- +#include "mastodonpp.hpp" +#include + +int main() +{ + mastodonpp::Instance instance{"example.com", {}}; + mastodonpp::Connection connection{instance}; + auto answer{connection.get(mastodonpp::API::v1::instance)}; + if (answer) + { + std::cout << answer << std::endl; + } +} +-------------------------------------------------------------------------------- + +link:{uri-reference}/examples.html[More examples] are included in the reference. == Install @@ -42,7 +84,7 @@ Have a look at the link:{uri-reference}[reference]. * Tested OS: Linux * C++ compiler (tested: link:{uri-gcc}[GCC] 7/8/9, link:{uri-lang}[clang] 6/7) * link:{uri-cmake}[CMake] (at least: 3.9) -* link:{uri-curl}[curl] (tested: 7.66 / 7.58) +* link:{uri-curl}[curl] (at least: 7.32) * Optional ** Documentation: link:{uri-doxygen}[Doxygen] (tested: 1.8) ** Tests: link:{uri-catch}[Catch] (tested: 2.5 / 1.2) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4be6be0..53c571d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,10 +1,12 @@ include(GNUInstallDirs) +find_package(Threads REQUIRED) + file(GLOB sources_examples *.cpp) foreach(src ${sources_examples}) get_filename_component(bin ${src} NAME_WE) add_executable(${bin} ${src}) - target_link_libraries(${bin} PRIVATE ${PROJECT_NAME}) + target_link_libraries(${bin} PRIVATE ${PROJECT_NAME} Threads::Threads) endforeach() if(WITH_DOC) diff --git a/examples/example02_streaming.cpp b/examples/example02_streaming.cpp new file mode 100644 index 0000000..0f95343 --- /dev/null +++ b/examples/example02_streaming.cpp @@ -0,0 +1,102 @@ +/* This file is part of mastodonpp. + * Copyright © 2020 tastytea + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Print information about an instance (/api/v1/instance). + +#include "mastodonpp.hpp" + +#include +#include +#include +#include +#include +#include + +namespace masto = mastodonpp; +using namespace std::chrono_literals; +using std::cout; +using std::cerr; +using std::endl; +using std::to_string; +using std::string_view; +using std::thread; +using std::this_thread::sleep_for; +using std::vector; + +int main(int argc, char *argv[]) +{ + const vector args(argv, argv + argc); + if (args.size() <= 1) + { + cerr << "Usage: " << args[0] << " \n"; + return 1; + } + + try + { + // Initialize an Instance. + masto::Instance instance{args[1], {}}; + + // Initialize a Connection. + masto::Connection connection{instance}; + + // Find out if the streaming service is fine. + auto answer{connection.get(masto::API::v1::streaming_health)}; + if (answer && answer.body == "OK") + { + // Make a thread, get all public events. + thread stream_thread{[&] + { + answer = connection.get(masto::API::v1::streaming_public); + }}; + + // Print new events every 2 seconds, for 10 seconds. + for (auto counter{0}; counter < 5; ++counter) + { + cout << "----------------------------------------" << endl; + sleep_for(2s); + cout << connection.get_new_stream_contents() << endl; + } + + // Cancel the stream, … + connection.cancel_stream(); + // … and get the rest of the data. + cout << connection.get_new_stream_contents() << endl; + stream_thread.join(); + } + else + { + if (answer.curl_error_code == 0) + { + // If it is no libcurl error, it must be an HTTP error. + cerr << "HTTP status: " << answer.http_status << endl; + } + else + { + // Network errors like “Couldn't resolve host.”. + cerr << "libcurl error " << to_string(answer.curl_error_code) + << ": " << answer.error_message << endl; + } + } + } + catch (const masto::CURLException &e) + { + // Only libcurl errors that are not network errors will be thrown. + // There went probably something wrong with the initialization. + cerr << e.what() << endl; + } + + return 0; +} diff --git a/include/api.hpp b/include/api.hpp index e508fa1..7c89af4 100644 --- a/include/api.hpp +++ b/include/api.hpp @@ -312,7 +312,7 @@ public: [[nodiscard]] inline string_view to_string_view() const { - return _endpoint_map.at(_endpoint).data(); + return _endpoint_map.at(_endpoint); } private: diff --git a/include/connection.hpp b/include/connection.hpp index 90027fb..e2735b8 100644 --- a/include/connection.hpp +++ b/include/connection.hpp @@ -33,7 +33,12 @@ using std::string; using std::string_view; using std::variant; -using endpoint_variant = variant; +/*! + * @brief An endpoint. Either API::endpoint_type or `std::string_view`. + * + * @since 0.1.0 + */ +using endpoint_variant = variant; /*! * @brief Represents a connection to an instance. Used for requests. @@ -66,7 +71,7 @@ public: * })}; * @endcode * - * @param endpoint Endpoint as API::endpoint_type or `std::string`. + * @param endpoint Endpoint as API::endpoint_type or `std::string_view`. * @param parameters A map of parameters. * * @@ -78,12 +83,13 @@ public: /*! * @brief Make a HTTP GET call. + * * Example: * @code * auto answer{connection.get("/api/v1/instance")}; * @endcode * - * @param endpoint Endpoint as API::endpoint_type or `std::string`. + * @param endpoint Endpoint as API::endpoint_type or `std::string_view`. * * @since 0.1.0 */ @@ -93,6 +99,24 @@ public: return get(endpoint, {}); } + /*! @copydoc CURLWrapper::set_proxy(string_view) + * + * Sets also the proxy for the Instance you used to initialize this + * Connection. + */ + void set_proxy(string_view proxy); + + /*! + * @brief Copy new stream contents and delete the “original”. + * + * Note that the last event is not necessarily complete, it could happen + * that you are calling this function mid-transfer. You have to check the + * data integrity yourself. + * + * @since 0.1.0 + */ + string get_new_stream_contents(); + private: Instance &_instance; const string_view _baseuri; diff --git a/include/curl_wrapper.hpp b/include/curl_wrapper.hpp index 146e481..1ff1d07 100644 --- a/include/curl_wrapper.hpp +++ b/include/curl_wrapper.hpp @@ -22,6 +22,7 @@ #include "curl/curl.h" #include +#include #include #include #include @@ -31,6 +32,7 @@ namespace mastodonpp { using std::map; +using std::mutex; using std::string; using std::string_view; using std::variant; @@ -58,13 +60,13 @@ enum class http_method * parametermap parameters * { * {"id", "12"}, - * {"poll[options]", vector{"Yes", "No", "Maybe"}} + * {"poll[options]", vector{"Yes", "No", "Maybe"}} * }; * @endcode * * @since 0.1.0 */ -using parametermap = map>>; +using parametermap = map>>; /*! * @brief Handles the details of network connections. @@ -91,10 +93,10 @@ public: CURLWrapper(); //! Copy constructor - CURLWrapper(const CURLWrapper &other) = default; + CURLWrapper(const CURLWrapper &other) = delete; //! Move constructor - CURLWrapper(CURLWrapper &&other) noexcept = default; + CURLWrapper(CURLWrapper &&other) noexcept = delete; /*! * @brief Cleans up curl and connection. @@ -108,10 +110,10 @@ public: virtual ~CURLWrapper() noexcept; //! Copy assignment operator - CURLWrapper& operator=(const CURLWrapper &other) = default; + CURLWrapper& operator=(const CURLWrapper &other) = delete; //! Move assignment operator - CURLWrapper& operator=(CURLWrapper &&other) noexcept = default; + CURLWrapper& operator=(CURLWrapper &&other) noexcept = delete; /*! * @brief Returns pointer to the CURL easy handle. @@ -127,7 +129,41 @@ public: return _connection; } + /*! + * @brief Set the proxy to use. + * + * See [CURLOPT_PROXY(3)] + * (https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html). + * + * @param proxy Examples: "socks4a://127.0.0.1:9050", "http://[::1]:3128". + * + * @since 0.1.0 + */ + void set_proxy(string_view proxy); + + /*! + * @brief Cancel the stream. + * + * The stream will be cancelled, usually whithin a second. The @link + * answer_type::curl_error_code curl_error_code @endlink of the answer will + * be set to 42 (`CURLE_ABORTED_BY_CALLBACK`). + * + * @since 0.1.0 + */ + void cancel_stream(); + protected: + /*! + * @brief Mutex for #get_buffer a.k.a. _curl_buffer_body. + * + * This mutex is locked in `writer_body()` and + * Connection::get_new_stream_contents before anything is read or written + * from/to _curl_buffer_body. + * + * @since 0.1.0 + */ + mutex buffer_mutex; + /*! * @brief Make a HTTP request. * @@ -141,19 +177,73 @@ protected: answer_type make_request(const http_method &method, string uri, const parametermap ¶meters); + /*! + * @brief Returns a reference to the buffer libcurl writes into. + * + * @since 0.1.0 + */ + [[nodiscard]] + string &get_buffer() + { + return _curl_buffer_body; + } + private: CURL *_connection; char _curl_buffer_error[CURL_ERROR_SIZE]; string _curl_buffer_headers; string _curl_buffer_body; + bool _stream_cancelled; /*! * @brief libcurl write callback function. * * @since 0.1.0 */ - static int writer(char *data, size_t size, size_t nmemb, - string *writerData); + size_t writer_body(char *data, size_t size, size_t nmemb); + + /*! + * @brief Wrapper for curl, because it can only call static member + * functions. + * + * + * + * @since 0.1.0 + */ + static inline size_t writer_body_wrapper(char *data, size_t sz, + size_t nmemb, void *f) + { + return static_cast(f)->writer_body(data, sz, nmemb); + } + + //! @copydoc writer_body + size_t writer_header(char *data, size_t size, size_t nmemb); + + //! @copydoc writer_body_wrapper + static inline size_t writer_header_wrapper(char *data, size_t sz, + size_t nmemb, void *f) + { + return static_cast(f)->writer_header(data, sz, nmemb); + } + + /*! + * @brief libcurl transfer info function. + * + * Used to cancel streams. + * + * @since 0.1.0 + */ + int progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow); + + //! @copydoc writer_body_wrapper + static inline int progress_wrapper(void *f, void *clientp, + curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow) + { + return static_cast(f)->progress(clientp, dltotal, dlnow, + ultotal, ulnow); + } /*! * @brief Setup libcurl connection. @@ -161,6 +251,16 @@ private: * @since 0.1.0 */ void setup_curl(); + + /*! + * @brief Add parameters to URI. + * + * @param uri Reference to the URI. + * @param parameters The parametermap. + * + * @since 0.1.0 + */ + void add_parameters_to_uri(string &uri, const parametermap ¶meters); }; } // namespace mastodonpp diff --git a/include/instance.hpp b/include/instance.hpp index 914a470..28f8f27 100644 --- a/include/instance.hpp +++ b/include/instance.hpp @@ -50,8 +50,7 @@ public: * * @since 0.1.0 */ - explicit Instance(const string_view &hostname, - const string_view &access_token); + explicit Instance(string_view hostname, string_view access_token); /*! * @brief Returns the hostname. diff --git a/include/mastodonpp.hpp b/include/mastodonpp.hpp index 85f1214..fec3ecf 100644 --- a/include/mastodonpp.hpp +++ b/include/mastodonpp.hpp @@ -74,21 +74,22 @@ * @section exceptions Exceptions * * Any unrecoverable libcurl error will be thrown as a - * mastodonpp::CURLException. Network errors will **not** be thrown, but - * reported via the return value. + * mastodonpp::CURLException. Network errors will not be thrown, but reported + * via the return value. * * @section thread_safety Thread safety * * The first time you construct an @link mastodonpp::Instance Instance @endlink - * or @link mastodonpp::Connection Connection @endlink, [curl_global_init()] + * or @link mastodonpp::Connection Connection @endlink, [curl_global_init(3)] * (https://curl.haxx.se/libcurl/c/curl_global_init.html) is called. When the * last @link mastodonpp::Instance Instance @endlink or @link * mastodonpp::Connection Connection @endlink is destroyed, - * [curl_global_cleanup()] + * [curl_global_cleanup(3)] * (https://curl.haxx.se/libcurl/c/curl_global_cleanup.html) is called. Both * are not thread safe. * * @example example01_instance_info.cpp + * @example example02_streaming.cpp */ /*! diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cdb651b..b9ede8d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ include(GNUInstallDirs) -find_package(CURL REQUIRED) +find_package(CURL 7.32 REQUIRED) # Write version in header. configure_file ("version.hpp.in" diff --git a/src/api.cpp b/src/api.cpp index d0ffefa..cb60a8b 100644 --- a/src/api.cpp +++ b/src/api.cpp @@ -23,6 +23,8 @@ API::API(const endpoint_type &endpoint) : _endpoint{endpoint} {} +// TODO: look for a better way. +// NOLINTNEXTLINE(cert-err58-cpp) const map API::_endpoint_map { {v1::apps, "/api/v1/apps"}, diff --git a/src/connection.cpp b/src/connection.cpp index 794247b..3aaa236 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -29,17 +29,33 @@ Connection::Connection(Instance &instance) answer_type Connection::get(const endpoint_variant &endpoint, const parametermap ¶meters) { - string uri{[&] + const string uri{[&] { if (holds_alternative(endpoint)) { - return string(_baseuri).append( - API{std::get(endpoint)}.to_string_view()); + return string(_baseuri) + += API{std::get(endpoint)}.to_string_view(); } - - return std::get(endpoint); + return string(std::get(endpoint)); }()}; + return make_request(http_method::GET, uri, parameters); } +void Connection::set_proxy(const string_view proxy) +{ + CURLWrapper::set_proxy(proxy); + _instance.set_proxy(proxy); +} + +string Connection::get_new_stream_contents() +{ + buffer_mutex.lock(); + auto &buffer{get_buffer()}; + auto buffer_copy{buffer}; + buffer.clear(); + buffer_mutex.unlock(); + return buffer_copy; +} + } // namespace mastodonpp diff --git a/src/curl_wrapper.cpp b/src/curl_wrapper.cpp index 1e1040b..490af70 100644 --- a/src/curl_wrapper.cpp +++ b/src/curl_wrapper.cpp @@ -40,6 +40,7 @@ static atomic curlwrapper_instances{0}; CURLWrapper::CURLWrapper() : _curl_buffer_error{} + , _stream_cancelled(false) { if (curlwrapper_instances == 0) { @@ -63,9 +64,26 @@ CURLWrapper::~CURLWrapper() noexcept } } +void CURLWrapper::set_proxy(const string_view proxy) +{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + CURLcode code = curl_easy_setopt(_connection, CURLOPT_PROXY, proxy); + if (code != CURLE_OK) + { + throw CURLException{code, "Failed to set proxy", _curl_buffer_error}; + } +} + +void CURLWrapper::cancel_stream() +{ + _stream_cancelled = true; +} + answer_type CURLWrapper::make_request(const http_method &method, string uri, const parametermap ¶meters) { + _stream_cancelled = false; + CURLcode code; switch (method) { @@ -74,50 +92,7 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri, // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) code = curl_easy_setopt(_connection, CURLOPT_HTTPGET, 1L); - for (const auto ¶m : parameters) - { - static constexpr array replace_in_uri - { - "id", "nickname", "nickname_or_id", - "hashtag", "permission_group" - }; - if (any_of(replace_in_uri.begin(), replace_in_uri.end(), - [¶m](const auto &s) { return s == param.first; })) - { - const auto pos{uri.find('<')}; - if (pos != string::npos) - { - uri.replace(pos, param.first.size() + 2, - get(param.second)); - } - continue; - } - static bool first{true}; - if (first) - { - uri.append("?"); - first = false; - } - else - { - uri.append("&"); - } - if (holds_alternative(param.second)) - { - uri.append(param.first + '=' + get(param.second)); - } - else - { - for (const auto &arg : get>(param.second)) - { - uri.append(param.first + "[]=" + arg); - if (arg != *get>(param.second).rbegin()) - { - uri.append("&"); - } - } - } - } + add_parameters_to_uri(uri, parameters); break; } @@ -162,7 +137,8 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri, answer_type answer; code = curl_easy_perform(_connection); - if (code == CURLE_OK) + if (code == CURLE_OK + || (code == CURLE_ABORTED_BY_CALLBACK && _stream_cancelled)) { long http_status; // NOLINT(google-runtime-int) // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) @@ -184,17 +160,40 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri, return answer; } -int CURLWrapper::writer(char *data, size_t size, size_t nmemb, - string *writerData) +size_t CURLWrapper::writer_body(char *data, size_t size, size_t nmemb) { - if(writerData == nullptr) + if(data == nullptr) { return 0; } - writerData->append(data, size*nmemb); + buffer_mutex.lock(); + _curl_buffer_body.append(data, size * nmemb); + buffer_mutex.unlock(); - return static_cast(size * nmemb); + return size * nmemb; +} + +size_t CURLWrapper::writer_header(char *data, size_t size, size_t nmemb) +{ + if(data == nullptr) + { + return 0; + } + + _curl_buffer_headers.append(data, size * nmemb); + + return size * nmemb; +} + +int CURLWrapper::progress(void *, curl_off_t , curl_off_t , + curl_off_t , curl_off_t ) +{ + if (_stream_cancelled) + { + return 1; + } + return 0; } void CURLWrapper::setup_curl() @@ -205,40 +204,29 @@ void CURLWrapper::setup_curl() } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - CURLcode code{curl_easy_setopt(_connection, CURLOPT_ERRORBUFFER, - _curl_buffer_error)}; - if (code != CURLE_OK) - { - throw CURLException{code, "Failed to set error buffer."}; - } + curl_easy_setopt(_connection, CURLOPT_ERRORBUFFER, _curl_buffer_error); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - code = curl_easy_setopt(_connection, CURLOPT_WRITEFUNCTION, writer); - if (code != CURLE_OK) - { - throw CURLException{code, "Failed to set writer", _curl_buffer_error}; - } + curl_easy_setopt(_connection, CURLOPT_WRITEFUNCTION, writer_body_wrapper); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + curl_easy_setopt(_connection, CURLOPT_WRITEDATA, this); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - code = curl_easy_setopt(_connection, CURLOPT_HEADERDATA, - &_curl_buffer_headers); - if (code != CURLE_OK) - { - throw CURLException{code, "Failed to set header data", - _curl_buffer_error}; - } + curl_easy_setopt(_connection, CURLOPT_HEADERFUNCTION, + writer_header_wrapper); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + curl_easy_setopt(_connection, CURLOPT_HEADERDATA, this); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - code = curl_easy_setopt(_connection, CURLOPT_WRITEDATA, &_curl_buffer_body); - if (code != CURLE_OK) - { - throw CURLException{code, "Failed to set write data", - _curl_buffer_error}; - } + curl_easy_setopt(_connection, CURLOPT_XFERINFOFUNCTION, progress_wrapper); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + curl_easy_setopt(_connection, CURLOPT_XFERINFODATA, this); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + curl_easy_setopt(_connection, CURLOPT_NOPROGRESS, 0L); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - code = curl_easy_setopt(_connection, CURLOPT_USERAGENT, - string("mastorss/").append(version).c_str()); + CURLcode code{curl_easy_setopt(_connection, CURLOPT_USERAGENT, + (string("mastorss/") += version).c_str())}; if (code != CURLE_OK) { throw CURLException{code, "Failed to set User-Agent", @@ -256,4 +244,55 @@ void CURLWrapper::setup_curl() curl_easy_setopt(_connection, CURLOPT_MAXREDIRS, 10L); } +void CURLWrapper::add_parameters_to_uri(string &uri, + const parametermap ¶meters) +{ + // Replace with the value of parameter “id” and so on. + for (const auto ¶m : parameters) + { + static constexpr array replace_in_uri + { + "id", "nickname", "nickname_or_id", + "hashtag", "permission_group" + }; + if (any_of(replace_in_uri.begin(), replace_in_uri.end(), + [¶m](const auto &s) { return s == param.first; })) + { + const auto pos{uri.find('<')}; + if (pos != string::npos) + { + uri.replace(pos, param.first.size() + 2, + get(param.second)); + continue; + } + } + + static bool first{true}; + if (first) + { + uri += "?"; + first = false; + } + else + { + uri += "&"; + } + if (holds_alternative(param.second)) + { + ((uri += param.first) += "=") += get(param.second); + } + else + { + for (const auto &arg : get>(param.second)) + { + ((uri += param.first) += "[]=") += arg; + if (arg != *get>(param.second).rbegin()) + { + uri += "&"; + } + } + } + } +} + } // namespace mastodonpp diff --git a/src/exceptions.cpp b/src/exceptions.cpp index 7c3da1a..7dd0e0f 100644 --- a/src/exceptions.cpp +++ b/src/exceptions.cpp @@ -42,7 +42,7 @@ const char *CURLException::what() const noexcept + " - " + _message}; if (!_error_buffer.empty()) { - error_string.append(" [" + _error_buffer + "]"); + error_string += " [" + _error_buffer + "]"; } return error_string.c_str(); } diff --git a/src/instance.cpp b/src/instance.cpp index e10675f..72369e5 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -23,7 +23,7 @@ namespace mastodonpp using std::stoull; -Instance::Instance(const string_view &hostname, const string_view &access_token) +Instance::Instance(const string_view hostname, const string_view access_token) : _hostname{hostname} , _baseuri{"https://" + _hostname} , _access_token{access_token}