Skip to content

feat(draft): Add Result. Add io_interface #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ystdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
add_subdirectory(containers)
add_subdirectory(error_handling)
add_subdirectory(io_interface)
4 changes: 4 additions & 0 deletions src/ystdlib/error_handling/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -3,9 +3,13 @@ cpp_library(
NAMESPACE ystdlib
PUBLIC_HEADERS
ErrorCode.hpp
Result.hpp
PUBLIC_LINK_LIBRARIES
outcome::hl
TESTS_SOURCES
test/constants.hpp
test/test_ErrorCode.cpp
test/test_Result.cpp
test/types.cpp
test/types.hpp
)
50 changes: 50 additions & 0 deletions src/ystdlib/error_handling/Result.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#ifndef YSTDLIB_ERROR_HANDLING_RESULT_HPP
#define YSTDLIB_ERROR_HANDLING_RESULT_HPP

#include <system_error>

#include <outcome/config.hpp>
#include <outcome/std_result.hpp>
#include <outcome/success_failure.hpp>

namespace ystdlib::error_handling {
template <typename ReturnType, typename ErrorType = std::error_code>
using Result = OUTCOME_V2_NAMESPACE::std_result<ReturnType, ErrorType>;

/**
* Default return value for ystdlib::error_handling::Result<void> when function succeeds.
*/
[[nodiscard]] inline auto success() -> OUTCOME_V2_NAMESPACE::success_type<void> {
return OUTCOME_V2_NAMESPACE::success();
}

// NOLINTBEGIN(bugprone-macro-parentheses, cppcoreguidelines-macro-usage)
/**
* This macro works the same way as Rust's `?` operator for error propagation.
*/
#define YSTDLIB_TRY(expr) \
({ \
auto result{(expr)}; \
if (result.has_error()) { \
return result.error(); \
} \
using ReturnType = decltype(result.value()); \
static_assert(std::is_move_constructible_v<ReturnType>); \
std::move(result.value()); \
})

/**
* This macro works the same way as Rust's `?` operator for error propagation, without returning
* any value.
*/
#define YSTDLIB_ASSERT_NO_ERROR(expr) \
({ \
if (auto const result{(expr)}; result.has_error()) { \
return result.error(); \
} \
})
// NOLINTEND(bugprone-macro-parentheses, cppcoreguidelines-macro-usage)
} // namespace ystdlib::error_handling

#endif // YSTDLIB_ERROR_HANDLING_RESULT_HPP

90 changes: 90 additions & 0 deletions src/ystdlib/error_handling/test/test_Result.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include <system_error>
#include <type_traits>

#include <ystdlib/error_handling/Result.hpp>

#include <catch2/catch_test_macros.hpp>

#include "types.hpp"

using ystdlib::error_handling::Result;
using ystdlib::error_handling::success;
using ystdlib::error_handling::test::BinaryErrorCode;
using ystdlib::error_handling::test::BinaryErrorCodeEnum;

namespace {
constexpr int cTestInt{123};
constexpr auto cVoidFunc = [](bool is_error) -> Result<void> {
if (is_error) {
return BinaryErrorCode{BinaryErrorCodeEnum::Failure};
}
return success();
};
constexpr auto cIntFunc = [](bool is_error) -> Result<int> {
if (is_error) {
return std::errc::bad_message;
}
return cTestInt;
};
} // namespace

namespace ystdlib::error_handling::test {
TEST_CASE("test_result_void", "[error_handling][Result]") {
auto const result_no_error{cVoidFunc(false)};
REQUIRE_FALSE(result_no_error.has_error());
REQUIRE(std::is_void_v<decltype(result_no_error.value())>);

auto const result_has_error{cVoidFunc(true)};
REQUIRE(result_has_error.has_error());
REQUIRE(BinaryErrorCode{BinaryErrorCodeEnum::Failure} == result_has_error.error());
}

TEST_CASE("test_result_void_in_main", "[error_handling][Result]") {
auto main_func = [&](bool is_error) -> Result<void> {
YSTDLIB_ASSERT_NO_ERROR(cVoidFunc(is_error));
return success();
};
auto const main_no_error{main_func(false)};
REQUIRE_FALSE(main_no_error.has_error());
REQUIRE(std::is_void_v<decltype(main_no_error.value())>);

auto const main_has_error{main_func(true)};
REQUIRE(main_has_error.has_error());
REQUIRE((BinaryErrorCode{BinaryErrorCodeEnum::Failure} == main_has_error.error()));
}

TEST_CASE("test_result_int", "[error_handling][Result]") {
auto const result_no_error{cIntFunc(false)};
REQUIRE_FALSE(result_no_error.has_error());
REQUIRE((cTestInt == result_no_error.value()));

auto const result_has_error{cIntFunc(true)};
REQUIRE(result_has_error.has_error());
REQUIRE(std::errc::bad_message == result_has_error.error());
}

TEST_CASE("test_result_int_in_main", "[error_handling][Result]") {
auto main_func = [&](bool is_error) -> Result<void> {
YSTDLIB_ASSERT_NO_ERROR(cIntFunc(is_error));
return success();
};
auto const main_no_error{main_func(false)};
REQUIRE_FALSE(main_no_error.has_error());
REQUIRE(std::is_void_v<decltype(main_no_error.value())>);

auto const main_has_error{main_func(true)};
REQUIRE(main_has_error.has_error());
REQUIRE((std::errc::bad_message == main_has_error.error()));
}

TEST_CASE("test_result_int_propagate", "[error_handling][Result]") {
auto main_func = [&](bool is_error) -> Result<int> { return YSTDLIB_TRY(cIntFunc(is_error)); };
auto const main_no_error{main_func(false)};
REQUIRE_FALSE(main_no_error.has_error());
REQUIRE((cTestInt == main_no_error.value()));

auto const main_has_error{main_func(true)};
REQUIRE(main_has_error.has_error());
REQUIRE((std::errc::bad_message == main_has_error.error()));
}
} // namespace ystdlib::error_handling::test
17 changes: 17 additions & 0 deletions src/ystdlib/io_interface/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
cpp_library(
NAME io_interface
NAMESPACE ystdlib
PUBLIC_HEADERS
ErrorCode.hpp
ReaderInterface.hpp
WriterInterface.hpp
PRIVATE_SOURCES
ErrorCode.cpp
ReaderInterface.cpp
StreamInterface.hpp
PUBLIC_LINK_LIBRARIES
ystdlib::error_handling
TESTS_SOURCES
test/test_ReaderInterface.cpp
test/test_WriterInterface.cpp
)
37 changes: 37 additions & 0 deletions src/ystdlib/io_interface/ErrorCode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include "ErrorCode.hpp"

#include <string>

#include <ystdlib/error_handling/ErrorCode.hpp>

using ystdlib::io_interface::ErrorCodeEnum;
using ErrorCategory = ystdlib::error_handling::ErrorCategory<ErrorCodeEnum>;

template <>
auto ErrorCategory::name() const noexcept -> char const* {
return "ystdlib::io_interface::ErrorCode";
}

template <>
auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
switch (error_enum) {
case ErrorCodeEnum::BadParam:
return "Supplied parameters are invalid.";
case ErrorCodeEnum::BufferFull:
return "The output buffer is full and cannot accept more data.";
case ErrorCodeEnum::Corrupt:
return "The data is corrupted or malformed.";
case ErrorCodeEnum::EndOfFile:
return "End of file reached.";
case ErrorCodeEnum::EndOfStream:
return "End of the data stream reached.";
case ErrorCodeEnum::OutOfBounds:
return "Attempted to access data outside of valid bounds.";
case ErrorCodeEnum::OutOfMemory:
return "Memory allocation failed.";
case ErrorCodeEnum::Truncated:
return "The data was unexpectedly truncated or cut off.";
default:
return "Unrecognized ystdlib::io_interface::ErrorCodeEnum.";
}
}
25 changes: 25 additions & 0 deletions src/ystdlib/io_interface/ErrorCode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef YSTDLIB_IO_INTERFACE_ERRORCODE_HPP
#define YSTDLIB_IO_INTERFACE_ERRORCODE_HPP

#include <cstdint>

#include <ystdlib/error_handling/ErrorCode.hpp>

namespace ystdlib::io_interface {
enum class ErrorCodeEnum : uint8_t {
BadParam,
BufferFull,
Corrupt,
EndOfFile,
EndOfStream,
OutOfBounds,
OutOfMemory,
Truncated
};

using ErrorCode = ystdlib::error_handling::ErrorCode<ErrorCodeEnum>;
} // namespace ystdlib::io_interface

YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM(ystdlib::io_interface::ErrorCodeEnum);

#endif // YSTDLIB_IO_INTERFACE_ERRORCODE_HPP
45 changes: 45 additions & 0 deletions src/ystdlib/io_interface/ReaderInterface.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include "ReaderInterface.hpp"

#include <cstddef>
#include <span>
#include <string>

#include <ystdlib/error_handling/Result.hpp>

#include "ErrorCode.hpp"

using ystdlib::error_handling::Result;

namespace ystdlib::io_interface {
auto ReaderInterface::read_char() -> ystdlib::error_handling::Result<char> {
char c{};
YSTDLIB_ASSERT_NO_ERROR(read({&c, 1}, 1, true));
return c;
}

auto ReaderInterface::read_string(size_t num_bytes)
-> ystdlib::error_handling::Result<std::string> {
std::string str(num_bytes, 0);
YSTDLIB_ASSERT_NO_ERROR(read({str.data(), num_bytes}, 1, true));
return str;
}

auto ReaderInterface::read_to_delimiter(std::span<char> output_buffer, char delim, bool keep_delim)
-> ystdlib::error_handling::Result<size_t> {
size_t num_bytes_read{0};
while (num_bytes_read < output_buffer.size()) {
auto const c{YSTDLIB_TRY(read_char())};
if (delim == c) {
break;
}
output_buffer[num_bytes_read++] = c;
}
if (num_bytes_read == output_buffer.size()) {
return ErrorCode{ErrorCodeEnum::BufferFull};
}
if (keep_delim) {
output_buffer[num_bytes_read++] = delim;
}
return num_bytes_read;
}
} // namespace ystdlib::io_interface
95 changes: 95 additions & 0 deletions src/ystdlib/io_interface/ReaderInterface.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#ifndef YSTDLIB_IO_INTERFACE_READERINTERFACE_HPP
#define YSTDLIB_IO_INTERFACE_READERINTERFACE_HPP

#include <cstddef>
#include <span>
#include <string>

#include <ystdlib/error_handling/Result.hpp>

#include "StreamInterface.hpp"

namespace ystdlib::io_interface {
class ReaderInterface : public StreamInterface {
public:
// Constructor
ReaderInterface() = default;

// Delete copy constructor and assignment operator
ReaderInterface(ReaderInterface const&) = delete;
auto operator=(ReaderInterface const&) -> ReaderInterface& = delete;

// Default move constructor and assignment operator
ReaderInterface(ReaderInterface&&) noexcept = default;
auto operator=(ReaderInterface&&) noexcept -> ReaderInterface& = default;

// Destructor
~ReaderInterface() override = default;

// Methods
/**
* Reads up to the given number of bytes from the underlying medium into the given buffer.
* @param output_buffer
* @param num_bytes
* @param exact If the number of bytes read needs to be equal to the amount requested.
* @return The actual number of bytes read.
* @return ystdlib::io_interface::ErrorCode::BadParam
* @return ystdlib::io_interface::ErrorCode::BufferFull
* @return ystdlib::io_interface::ErrorCode::Corrupt
* @return ystdlib::io_interface::ErrorCode::EndOfFile
* @return ystdlib::io_interface::ErrorCode::EndOfStream
*/
[[nodiscard]] virtual auto
read(std::span<char> output_buffer, size_t num_bytes, bool exact = false)
-> ystdlib::error_handling::Result<size_t>
= 0;

/**
* @return The read character from the underlying medium.
* @return ystdlib::io_interface::ErrorCode::Corrupt
* @return ystdlib::io_interface::ErrorCode::EndOfFile
* @return ystdlib::io_interface::ErrorCode::EndOfStream
*/
[[nodiscard]] virtual auto read_char() -> ystdlib::error_handling::Result<char>;

/**
* @param num_bytes
* @return The read sring from the underlying medium.
* @return ystdlib::io_interface::ErrorCode::Corrupt
* @return ystdlib::io_interface::ErrorCode::EndOfFile
* @return ystdlib::io_interface::ErrorCode::EndOfStream
* @return ystdlib::io_interface::ErrorCode::OutOfMemory if the requested read is too large.
*/
[[nodiscard]] virtual auto read_string(size_t num_bytes)
-> ystdlib::error_handling::Result<std::string>;

/**
* @return The read numeric value from the underlying medium.
* @return ystdlib::io_interface::ErrorCode::Corrupt
* @return ystdlib::io_interface::ErrorCode::EndOfFile
* @return ystdlib::io_interface::ErrorCode::EndOfStream
*/
template <NumericType T>
[[nodiscard]] auto read_numeric_value() -> ystdlib::error_handling::Result<T>;

/**
* Reads up to the next delimiter from the underlying medium into the given buffer.
* @param output_buffer
* @param delim The delimiter to stop at.
* @param keep_delim Whether to include the delimiter in the output.
* @return The actual number of bytes read.
* @return Same as ReaderInterface::read.
*/
[[nodiscard]] virtual auto
read_to_delimiter(std::span<char> output_buffer, char delim, bool keep_delim)
-> ystdlib::error_handling::Result<size_t>;
};

template <NumericType T>
auto ReaderInterface::read_numeric_value() -> ystdlib::error_handling::Result<T> {
T value{};
return YSTDLIB_TRY(read({static_cast<char*>(&value), sizeof(T)}, sizeof(T), true));
}
} // namespace ystdlib::io_interface

#endif // YSTDLIB_IO_INTERFACE_READERINTERFACE_HPP
Loading