Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details

This commit is contained in:
tastytea 2020-01-05 13:26:44 +01:00
commit 398383143a
Signed by: tastytea
GPG Key ID: CFC39497F1B26E07
16 changed files with 459 additions and 114 deletions

View File

@ -29,6 +29,11 @@ set(CMAKE_CXX_EXTENSIONS OFF)
include(debug_flags)
# Disable debug log.
if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug")
add_definitions("-DNDEBUG")
endif()
add_subdirectory(src)
add_subdirectory(include)

View File

@ -18,13 +18,13 @@
#define MASTODONPP_API_HPP
#include <map>
#include <string>
#include <string_view>
#include <variant>
namespace mastodonpp
{
using std::string;
using std::string_view;
using std::variant;
/*!
@ -76,14 +76,15 @@ public:
*
* @since 0.1.0
*/
explicit API(endpoint_type &endpoint);
explicit API(const endpoint_type &endpoint);
/*!
* @brief Convert #endpoint_type to string.
* @brief Convert #endpoint_type to `std::string_view`.
*
* @since 0.1.0
*/
string to_string() const;
[[nodiscard]]
string_view to_string_view() const;
private:
const endpoint_type _endpoint;

View File

@ -14,50 +14,68 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MASTODONPP_REQUEST_HPP
#define MASTODONPP_REQUEST_HPP
#ifndef MASTODONPP_CONNECTION_HPP
#define MASTODONPP_CONNECTION_HPP
#include "api.hpp"
#include "curl_wrapper.hpp"
#include "instance.hpp"
#include "return_types.hpp"
#include <string>
#include <string_view>
namespace mastodonpp
{
using std::string;
using std::string_view;
/*!
* @brief Used to make a request to the Mastodon API.
* @brief Represents a connection to an instance. Used for requests.
*
* @since 0.1.0
*
* @headerfile request.hpp mastodonpp/request.hpp
* @headerfile connection.hpp mastodonpp/connection.hpp
*/
class Request
class Connection : public CURLWrapper
{
public:
/*!
* @brief Construct a new Request object.
* @brief Construct a new Connection object.
*
* @param instance An Instance with the access data.
*
* @since 0.1.0
*/
explicit Request(Instance &instance);
explicit Connection(Instance &instance);
/*!
* @brief Make a HTTP GET call.
*
* @param endpoint Endpoint as API::endpoint_type, for example:
* `mastodonpp::API::v1::instance`.
*
* @since 0.1.0
*/
answer_type get(API::endpoint_type endpoint) const;
[[nodiscard]]
answer_type get(const API::endpoint_type &endpoint);
/*!
* @brief Make a HTTP GET call.
*
* @param endpoint Endpoint as string, for example: "/api/v1/instance".
*
* @since 0.1.0
*/
[[nodiscard]]
answer_type get(const string_view &endpoint);
private:
Instance &_instance;
const string_view _baseuri;
};
} // namespace mastodonpp
#endif // MASTODONPP_REQUEST_HPP
#endif // MASTODONPP_CONNECTION_HPP

125
include/curl_wrapper.hpp Normal file
View File

@ -0,0 +1,125 @@
/* This file is part of mastodonpp.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MASTODONPP_CURL_WRAPPER_HPP
#define MASTODONPP_CURL_WRAPPER_HPP
#include "return_types.hpp"
#include "curl/curl.h"
#include <string>
#include <string_view>
namespace mastodonpp
{
using std::string;
using std::string_view;
/*!
* @brief The HTTP method.
*
* @since 0.1.0
*/
enum class http_method
{
GET,
POST
};
/*!
* @brief Handles the details of network connections.
*
* You don't need to use this.
*
* @since 0.1.0
*
* @headerfile curl_wrapper.hpp mastodonpp/curl_wrapper.hpp
*/
class CURLWrapper
{
public:
/*!
* @brief Initializes curl and sets up connection.
*
* Calls `curl_global_init`, which is not thread-safe. For more information
* consult [curl_global_init(3)]
* (https://curl.haxx.se/libcurl/c/curl_global_init.html).
*
* @since 0.1.0
*/
CURLWrapper();
//! Copy constructor
CURLWrapper(const CURLWrapper &other) = default;
//! Move constructor
CURLWrapper(CURLWrapper &&other) noexcept = default;
/*!
* @brief Cleans up curl and connection.
*
* Calls `curl_global_cleanup`, which is not thread-safe. For more
* information consult [curl_global_cleanup(3)]
* (https://curl.haxx.se/libcurl/c/curl_global_cleanup.html).
*
* @since 0.1.0
*/
virtual ~CURLWrapper() noexcept;
//! Copy assignment operator
CURLWrapper& operator=(const CURLWrapper &other) = default;
//! Move assignment operator
CURLWrapper& operator=(CURLWrapper &&other) noexcept = default;
/*!
* @brief Make a request.
*
* @param method The HTTP method.
* @param uri The full URI.
*
* @since 0.1.0
*/
[[nodiscard]]
answer_type make_request(const http_method &method, const string_view &uri);
private:
CURL *_connection;
char _curl_buffer_error[CURL_ERROR_SIZE];
string _curl_buffer_headers;
string _curl_buffer_body;
/*!
* @brief libcurl write callback function.
*
* @since 0.1.0
*/
static int writer(char *data, size_t size, size_t nmemb,
string *writerData);
/*!
* @brief Setup libcurl connection.
*
* @since 0.1.0
*/
void setup_curl();
};
} // namespace mastodonpp
#endif // MASTODONPP_CURL_WRAPPER_HPP

View File

@ -58,7 +58,8 @@ public:
/*!
* @brief The error code returned by libcurl.
*
* For more information consult libcurl-errors(3).
* For more information consult
* [libcurl-errors(3)](https://curl.haxx.se/libcurl/c/libcurl-errors.html).
*
* @since 0.1.0
*/

View File

@ -17,58 +17,92 @@
#ifndef MASTODONPP_INSTANCE_HPP
#define MASTODONPP_INSTANCE_HPP
#include <curl/curl.h>
#include "curl_wrapper.hpp"
#include <cstdint>
#include <string>
#include <string_view>
namespace mastodonpp
{
using std::uint64_t;
using std::string;
using std::string_view;
/*!
* @brief Holds the hostname and access token of an instance.
* @brief Holds the access data of an instance.
*
* @since 0.1.0
*
* @headerfile instance.hpp mastodonpp/instance.hpp
*/
class Instance
class Instance : public CURLWrapper
{
public:
/*!
* @brief Construct a new Instance object.
*
* @param instance The hostname of the instance.
* Also queries `/api/v1/instance` for `max_toot_chars'.
*
* @param hostname The hostname of the instance.
* @param access_token Your access token.
*
* @since 0.1.0
*/
explicit Instance(string instance, string access_token);
~Instance();
explicit Instance(string hostname, string access_token);
/*!
* @brief Returns the hostname.
*
* @since 0.1.0
*/
[[nodiscard]]
inline string_view get_hostname() const
{
return _hostname;
}
/*!
* @brief Returns the base URI.
*
* The base URI is https://” + the hostname.
*
* @since 0.1.0
*/
[[nodiscard]]
inline string_view get_baseuri() const
{
return _baseuri;
}
/*!
* @brief Returns the access token.
*
* @since 0.1.0
*/
[[nodiscard]]
inline string_view get_access_token() const
{
return _access_token;
}
/*!
* @brief Returns the maximum number of characters per post.
*
* @since 0.1.0
*/
[[nodiscard]]
inline uint64_t get_max_chars() const
{
return _max_chars;
}
private:
const string _instance;
const string _hostname;
const string _baseuri;
string _access_token;
CURL *_connection;
char _curl_buffer_error[CURL_ERROR_SIZE];
string _curl_buffer;
/*!
* @brief libcurl write callback function.
*
* @since 0.1.0
*/
static int writer(char *data, size_t size, size_t nmemb,
string *writerData);
/*!
* @brief Setup libcurl connection.
*
* @since 0.1.0
*/
void setup_curl();
uint64_t _max_chars;
};
} // namespace mastodonpp

View File

@ -18,9 +18,9 @@
#define MASTODONPP_HPP
#include "api.hpp"
#include "connection.hpp"
#include "exceptions.hpp"
#include "instance.hpp"
#include "request.hpp"
#include "return_types.hpp"
/*!
@ -68,7 +68,7 @@
*/
/*!
* @brief C++ wrapper for the Mastodon API.
* @brief C++ wrapper for the Mastodon %API.
*
* All text input is expected to be UTF-8.
*

View File

@ -36,31 +36,40 @@ using std::string_view;
*
* @headerfile return_types.hpp mastodonpp/return_types.hpp
*
* @section error Error codes
* | Code | Explanation |
* | --------: |:-------------------------------------------------------------|
* | 0 | No error. |
*/
struct answer_type
{
/*!
* @brief @ref error "Error code".
* @brief The error code returned by libcurl.
*
* For more information consult
* [libcurl-errors(3)](https://curl.haxx.se/libcurl/c/libcurl-errors.html).
*
* @since 0.1.0
*/
uint8_t error_code;
uint8_t curl_error_code{0};
/*!
* @brief The error message.
*
* @since 0.1.0
*/
string error_message;
/*!
* @brief HTTP status code.
*
* @since 0.1.0
*/
uint16_t http_status;
uint16_t http_status{0};
/*!
* @brief The headers of the response from the server.
*
* @since 0.1.0
*/
string headers;
/*!
* @brief The response from the server, usually JSON.
*
@ -69,7 +78,8 @@ struct answer_type
string body;
/*!
* @brief Returns true if #error_code is 0, false otherwise.
* @brief Returns true if #curl_error_code is 0 and #http_status is 200,
* false otherwise.
*
* @since 0.1.0
*/

View File

@ -17,21 +17,18 @@
#include "api.hpp"
#include <map>
#include <string_view>
#include <utility>
namespace mastodonpp
{
using std::map;
using std::string_view;
using std::move;
API::API(endpoint_type &endpoint)
: _endpoint{move(endpoint)}
API::API(const endpoint_type &endpoint)
: _endpoint{endpoint}
{}
string API::to_string() const
string_view API::to_string_view() const
{
static const map<endpoint_type,string_view> endpoint_map
{

View File

@ -14,20 +14,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "request.hpp"
#include "connection.hpp"
namespace mastodonpp
{
Request::Request(Instance &instance)
Connection::Connection(Instance &instance)
: _instance{instance}
, _baseuri{instance.get_baseuri()}
{}
answer_type Request::get(API::endpoint_type endpoint) const
answer_type Connection::get(const API::endpoint_type &endpoint)
{
answer_type answer;
answer.body = API{endpoint}.to_string();
return answer;
return make_request(
http_method::GET,
string(_baseuri).append(API{endpoint}.to_string_view()));
}
answer_type Connection::get(const string_view &endpoint)
{
return make_request(http_method::GET, string(_baseuri).append(endpoint));
}
} // namespace mastodonpp

153
src/curl_wrapper.cpp Normal file
View File

@ -0,0 +1,153 @@
/* This file is part of mastodonpp.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "curl_wrapper.hpp"
#include "exceptions.hpp"
#include "log.hpp"
#include <cstdint>
namespace mastodonpp
{
using std::uint8_t;
using std::uint16_t;
CURLWrapper::CURLWrapper()
: _curl_buffer_error{}
{
curl_global_init(CURL_GLOBAL_ALL); // NOLINT(hicpp-signed-bitwise)
_connection = curl_easy_init();
setup_curl();
}
CURLWrapper::~CURLWrapper() noexcept
{
curl_easy_cleanup(_connection);
curl_global_cleanup();
}
answer_type CURLWrapper::make_request(const http_method &method,
const string_view &uri)
{
debuglog << "Making request to: " << uri << '\n';
CURLcode code;
switch (method)
{
case http_method::GET:
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_HTTPGET, 1L);
break;
}
case http_method::POST:
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_POST, 1L);
break;
}
}
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set HTTP method",
_curl_buffer_error};
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_URL, uri.data());
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set URI", _curl_buffer_error};
}
answer_type answer;
code = curl_easy_perform(_connection);
if (code == CURLE_OK)
{
long http_status; // NOLINT(google-runtime-int)
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
curl_easy_getinfo(_connection, CURLINFO_RESPONSE_CODE, &http_status);
answer.http_status = static_cast<uint16_t>(http_status);
debuglog << "HTTP status code: " << http_status << '\n';
answer.headers = _curl_buffer_headers;
answer.body = _curl_buffer_body;
}
else
{
answer.curl_error_code = static_cast<uint8_t>(code);
answer.error_message = _curl_buffer_error;
debuglog << "libcurl error: " << code << '\n';
debuglog << _curl_buffer_error << '\n';
}
return answer;
}
int CURLWrapper::writer(char *data, size_t size, size_t nmemb,
string *writerData)
{
if(writerData == nullptr)
{
return 0;
}
writerData->append(data, size*nmemb);
return static_cast<int>(size * nmemb);
}
void CURLWrapper::setup_curl()
{
if (_connection == nullptr)
{
throw CURLException{CURLE_FAILED_INIT, "Failed to initialize 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."};
}
// 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};
}
// 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};
}
// 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};
}
}
} // namespace mastodonpp

View File

@ -42,7 +42,7 @@ const char *CURLException::what() const noexcept
+ " - " + _message};
if (!_error_buffer.empty())
{
error_string.append("[" + _error_buffer + "]");
error_string.append(" [" + _error_buffer + "]");
}
return error_string.c_str();
}

View File

@ -15,7 +15,8 @@
*/
#include "instance.hpp"
#include "exceptions.hpp"
#include "log.hpp"
#include "return_types.hpp"
#include <utility>
@ -24,55 +25,38 @@ namespace mastodonpp
using std::move;
Instance::Instance(string instance, string access_token)
: _instance{move(instance)}
Instance::Instance(string hostname, string access_token)
: _hostname{move(hostname)}
, _baseuri{"https://" + _hostname}
, _access_token{move(access_token)}
, _connection{curl_easy_init()}
, _max_chars{500}
{
setup_curl();
}
Instance::~Instance()
{
curl_easy_cleanup(_connection);
}
int Instance::writer(char *data, size_t size, size_t nmemb, string *writerData)
{
if(writerData == nullptr)
try
{
return 0;
const auto answer{make_request(http_method::GET,
_baseuri + "/api/v1/instance")};
if (answer)
{
debuglog << "Querying instance for max_toot_chars…\n";
auto &body{answer.body};
size_t pos_start{body.find("max_toot_chars")};
if (pos_start == string::npos)
{
debuglog << "max_toot_chars not found.";
return;
}
pos_start = body.find(':', pos_start) + 1;
const size_t pos_end{body.find(',', pos_start)};
const auto max_toot_chars{body.substr(pos_start,
pos_end - pos_start)};
_max_chars = std::stoull(max_toot_chars);
debuglog << "Set _max_chars to: " << _max_chars << '\n';
}
}
writerData->append(data, size*nmemb);
return static_cast<int>(size * nmemb);
}
void Instance::setup_curl()
{
if (_connection == nullptr)
catch (const std::exception &e)
{
throw CURLException{CURLE_FAILED_INIT, "Failed to initialize curl."};
}
CURLcode code{curl_easy_setopt(_connection, CURLOPT_ERRORBUFFER,
_curl_buffer_error)};
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set error buffer."};
}
code = curl_easy_setopt(_connection, CURLOPT_WRITEFUNCTION, writer);
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set writer", _curl_buffer_error};
}
code = curl_easy_setopt(_connection, CURLOPT_WRITEDATA, &_curl_buffer);
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set write data",
_curl_buffer_error};
debuglog << "Unexpected exception: " << e.what() << '\n';
}
}

View File

@ -14,11 +14,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mastodonpp.hpp"
#ifndef MASTODONPP_LOG_HPP
#define MASTODONPP_LOG_HPP
#include <iostream>
namespace mastodonpp
{
using std::cerr;
#ifndef NDEBUG
#define debuglog cerr << "[" << __func__ << "():" << __LINE__ << "] DEBUG: "
#else
#define debuglog false && cerr
#endif
} // namespace mastodonpp
#endif // MASTODONPP_LOG_HPP

View File

@ -21,7 +21,7 @@ namespace mastodonpp
answer_type::operator bool() const
{
return (error_code == 0);
return (curl_error_code == 0 && http_status == 200);
}
answer_type::operator string_view() const

View File

@ -15,7 +15,7 @@
*/
#include "instance.hpp"
#include "request.hpp"
#include "connection.hpp"
#include <catch.hpp>
@ -48,12 +48,12 @@ SCENARIO ("Instantiations.")
}
}
WHEN ("Request is instantiated.")
WHEN ("Connection is instantiated.")
{
try
{
Instance instance{"example.com", ""};
Request request{instance};
Connection connection{instance};
}
catch (const std::exception &e)
{