Skip to content

Commit

Permalink
Clean up [de]serialization api and support different result types.
Browse files Browse the repository at this point in the history
  • Loading branch information
asoffer committed Feb 21, 2024
1 parent d4294bc commit b24fd04
Show file tree
Hide file tree
Showing 20 changed files with 764 additions and 288 deletions.
24 changes: 24 additions & 0 deletions nth/io/deserialize/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package(default_visibility = ["//visibility:public"])

cc_library(
name = "deserialize",
hdrs = ["deserialize.h"],
deps = [
"//nth/io/deserialize/internal:deserialize",
"//nth/io/internal:sequence",
"//nth/container:free_functions",
"//nth/meta:concepts",
"//nth/io/reader",
"//nth/utility:bytes",
],
)

cc_test(
name = "deserialize_test",
srcs = ["deserialize_test.cc"],
deps = [
":deserialize",
"//nth/base:platform",
"//nth/test:main",
],
)
93 changes: 93 additions & 0 deletions nth/io/deserialize/deserialize.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#ifndef NTH_IO_DESERIALIZE_DESERIALIZE_H
#define NTH_IO_DESERIALIZE_DESERIALIZE_H

#include <climits>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <optional>
#include <span>
#include <type_traits>

#include "nth/container/free_functions.h"
#include "nth/io/deserialize/internal/deserialize.h"
#include "nth/io/internal/sequence.h"
#include "nth/io/reader/reader.h"
#include "nth/meta/concepts.h"
#include "nth/utility/bytes.h"

namespace nth::io {

// An alias template that extracts the result type associated with the
// deserializer `D`. By default, the result type is `bool`, where `true`
// indicates successful deserialization. Users may override this choice by
// adding a member type named `nth_deserializer_result_type` to their
// deserializer. The type must be convertible to `bool` in such a way that
// deserialization is understood to be successful if and only if the result
// value converts to `true`.
template <typename D>
using deserializer_result_type =
internal_deserialize::deserializer_result<D>::type;

// A concept indicating that a type `T` can be deserialized with a deserializer
// `D`.
template <typename T, typename D>
concept deserializable_with = requires(D& d, T& value) {
{
NthDeserialize(d, value)
} -> std::convertible_to<deserializer_result_type<D>>;
};

// Deserializes a sequence of `values...` with the deserializer `D`, one
// immediately after the other.
template <typename D>
deserializer_result_type<D>
deserialize(D& d, deserializable_with<D> auto&&... values) requires(
lvalue_proxy<decltype(values)>and...) {
deserializer_result_type<D> result;
(void)(static_cast<bool>(result = NthDeserialize(d, values)) and ...);
return result;
}

// Deserializes a sequence of `Seq::value_type` into the passed-in `sequence`,
// formatted as a count of the number of elements followed by serializations of
// that many elements.
template <typename D, typename Seq>
deserializer_result_type<D> NthDeserialize(
D& d, as_sequence<Seq&> sequence) requires(not std::is_const_v<Seq>) {
auto& seq = sequence.ref();
using size_type = decltype(std::size(seq));
size_type seq_size;
if (not nth::io::read_integer(d, seq_size)) { return false; }

if constexpr (requires { nth::reserve(seq, seq_size); }) {
nth::reserve(seq, seq_size);
}

using value_type = std::decay_t<decltype(seq)>::value_type;
if constexpr (requires {
{ nth::emplace_back(seq) } -> std::same_as<value_type&>;
}) {
for (size_type i = 0; i < seq_size; ++i) {
if (not nth::io::deserialize(d, nth::emplace_back(seq))) { return false; }
}
} else if constexpr (requires {
{ nth::emplace(seq) } -> std::same_as<value_type&>;
}) {
for (size_type i = 0; i < seq_size; ++i) {
if (not nth::io::deserialize(d, nth::emplace(seq))) { return false; }
}
} else {
value_type element;
for (size_type i = 0; i < seq_size; ++i) {
if (not nth::io::deserialize(d, element)) { return false; }
nth::insert(seq, element);
}
}
return true;
}

} // namespace nth::io

#endif // NTH_IO_DESERIALIZE_DESERIALIZE_H
174 changes: 174 additions & 0 deletions nth/io/deserialize/deserialize_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#include "nth/io/deserialize/deserialize.h"

#include <array>
#include <coroutine>
#include <cstddef>
#include <string>
#include <utility>

#include "nth/base/platform.h"
#include "nth/test/test.h"

namespace nth::io {
namespace {

struct Thing {
int value = 0;
};

struct BasicDeserializer {
int get_value() { return value_++; }

private:
int value_ = 0;
};

struct BoolReturningDeserializer : BasicDeserializer {};

struct result_type {
explicit result_type() : message_("unknown failure") {}
explicit result_type(std::string message)
: message_(std::move(message)) {}
static result_type success() { return result_type(""); }

operator bool() const { return message_.empty(); };

friend void NthPrint(auto& p, auto& , result_type const &r) {
p.write("result[");
p.write(r.message_);
p.write("]");
}

struct promise_type;

bool await_ready() { return static_cast<bool>(*this); }
void await_suspend(std::coroutine_handle<promise_type>);
static constexpr void await_resume() {}

private:
friend struct result_promise_return_type;

result_type(result_type*& location) { location = this; }
std::string message_;
};

struct result_promise_return_type {
result_promise_return_type(result_type::promise_type& promise);
result_promise_return_type(result_promise_return_type&&) = delete;
result_promise_return_type(result_promise_return_type const&) = delete;
result_promise_return_type& operator=(result_promise_return_type&&) = delete;
result_promise_return_type& operator=(result_promise_return_type const&) =
delete;
operator result_type() {
// TODO: This assumes eager conversion `get_return_object()` to the actual
// result type.
return result_type(pointer_);
}

private:
result_type*& pointer_;
};

struct result_type::promise_type {
result_promise_return_type get_return_object() { return *this; }
static constexpr std::suspend_never initial_suspend() noexcept { return {}; }
static constexpr std::suspend_never final_suspend() noexcept { return {}; }
static constexpr void unhandled_exception() noexcept {}
void return_value(result_type value) { set(std::move(value)); }

void set(result_type result) { *value_ = std::move(result); }

private:
friend result_promise_return_type;
result_type* value_;
};

result_promise_return_type::result_promise_return_type(
result_type::promise_type& promise)
: pointer_(promise.value_) {}

void result_type::await_suspend(std::coroutine_handle<promise_type> h) {
h.promise().set(std::move(*this));
h.destroy();
}

struct ResultReturningDeserializer : BasicDeserializer {
using nth_deserializer_result_type = result_type;
};

bool NthDeserialize(BoolReturningDeserializer& d, Thing& t) {
t.value = d.get_value();
return t.value < 5;
}

NTH_TEST("deserialize/bool-result") {
BoolReturningDeserializer d;
Thing t1{.value = -1};
Thing t2{.value = -1};

NTH_EXPECT(deserialize(d, t1));
NTH_EXPECT(t1.value == 0);

NTH_EXPECT(deserialize(d, t1));
NTH_EXPECT(t1.value == 1);

NTH_EXPECT(deserialize(d, t1, t2));
NTH_EXPECT(t1.value == 2);
NTH_EXPECT(t2.value == 3);

NTH_EXPECT(not deserialize(d, t1, t2));
}

result_type NthDeserialize(ResultReturningDeserializer& d, Thing& t) {
t.value = d.get_value();
return result_type(t.value < 5 ? "" : "failure");
}

NTH_TEST("deserialize/custom-result") {
ResultReturningDeserializer d;
Thing t1{.value = -1};
Thing t2{.value = -1};

NTH_EXPECT(deserialize(d, t1) == true);
NTH_EXPECT(t1.value == 0);

NTH_EXPECT(deserialize(d, t1) == true);
NTH_EXPECT(t1.value == 1);

NTH_EXPECT(deserialize(d, t1, t2) == true);
NTH_EXPECT(t1.value == 2);
NTH_EXPECT(t2.value == 3);

NTH_EXPECT(deserialize(d, t1, t2) == false);
}

template <size_t N>
result_type NthDeserialize(ResultReturningDeserializer& d,
std::array<Thing, N>& ts) {
for (size_t i = 0; i < N; ++i) { co_await deserialize(d, ts[i]); }
co_return result_type::success();
}

NTH_TEST("deserialize/coroutine") {
ResultReturningDeserializer d;
std::array<Thing, 2> things{
Thing{.value = -1},
Thing{.value = -1},
};

std::array<Thing, 4> more_things{
Thing{.value = -1},
Thing{.value = -1},
Thing{.value = -1},
Thing{.value = -1},
};

NTH_EXPECT(deserialize(d, things) == true);
NTH_EXPECT(things[0].value == 0);
NTH_EXPECT(things[1].value == 1);

NTH_EXPECT(deserialize(d, more_things) == false);
}

} // namespace
} // namespace nth::io
7 changes: 7 additions & 0 deletions nth/io/deserialize/internal/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package(default_visibility = ["//nth/io/deserialize:__subpackages__"])

cc_library(
name = "deserialize",
hdrs = ["deserialize.h"],
deps = [],
)
22 changes: 22 additions & 0 deletions nth/io/deserialize/internal/deserialize.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef NTH_IO_DESERIALIZE_INTERNAL_DESERIALIZE_H
#define NTH_IO_DESERIALIZE_INTERNAL_DESERIALIZE_H

#include <type_traits>

namespace nth::io::internal_deserialize {

template <typename D>
struct deserializer_result {
using type = bool;
};

template <typename D>
requires requires { typename D::nth_deserializer_result_type; }
struct deserializer_result<D> {
using type = D::nth_deserializer_result_type;
static_assert(std::is_convertible_v<type, bool>);
};

} // namespace nth::io::internal_deserialize

#endif // NTH_IO_DESERIALIZE_INTERNAL_DESERIALIZE_H
7 changes: 7 additions & 0 deletions nth/io/internal/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package(default_visibility = ["//nth/io:__subpackages__"])

cc_library(
name = "sequence",
hdrs = ["sequence.h"],
deps = [],
)
12 changes: 1 addition & 11 deletions nth/io/serialize/sequence.h → nth/io/internal/sequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,7 @@ struct as_sequence<Seq&> {

explicit as_sequence(Seq& seq) : ref_(seq) {}

template <typename S>
requires std::is_const_v<Seq>
friend bool NthSerialize(S& s, as_sequence seq) {
return serialize_sequence(s, seq.ref_);
}

template <typename D>
requires(not std::is_const_v<Seq>) //
friend bool NthDeserialize(D& s, as_sequence seq) {
return deserialize_sequence(s, seq.ref_);
}
Seq& ref() const { return ref_; }

private:
Seq& ref_;
Expand Down
4 changes: 3 additions & 1 deletion nth/io/reader/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ cc_test(
cc_library(
name = "reader",
hdrs = ["reader.h"],
deps = [],
deps = [
"//nth/utility:bytes",
],
)

cc_library(
Expand Down
Loading

0 comments on commit b24fd04

Please sign in to comment.