Skip to content

Commit

Permalink
feat(schema): add Field, ref #149
Browse files Browse the repository at this point in the history
  • Loading branch information
iboB committed Nov 29, 2024
1 parent ff66c06 commit 3e65810
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 0 deletions.
141 changes: 141 additions & 0 deletions schema/code/ac/schema/Field.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright (c) Alpaca Core
// SPDX-License-Identifier: MIT
//
#pragma once
#include <optional>
#include <bitset>
#include <concepts>

namespace ac::local::schema {

namespace impl {
template <typename T>
struct DefaultT { T value; };
template <>
struct DefaultT<void> {};
} // namespace impl

template <typename T>
impl::DefaultT<T> Default(T&& t) {
return {std::forward<T>(t)};
}

inline impl::DefaultT<void> Default() {
return {};
}

template <typename T>
class Field {
public:
using Type = T;

Field() {
m_flags.set(Flag_Required);
}

Field(std::nullopt_t) {}

template <std::convertible_to<T> U>
Field(impl::DefaultT<U>&& def)
: m_value(std::move(def.value))
{
m_flags.set(Flag_DefaultSet);
}

Field(impl::DefaultT<void>)
: m_value(std::in_place)
{
m_flags.set(Flag_DefaultSet);
}

template <std::convertible_to<T> U>
Field(U&& val) : m_value(std::forward<U>(val)) {}

Field(const Field&) = default;
Field& operator=(const Field&) = default;
Field(Field&&) noexcept = default;
Field& operator=(Field&&) noexcept = default;

Field& operator=(T&& def) {
m_value = std::forward<T>(def);
m_flags.reset(Flag_DefaultSet);
return *this;
}

bool hasValue() const noexcept {
return m_value.has_value();
}

bool required() const noexcept {
return m_flags.test(Flag_Required);
}

bool defaultSet() const noexcept {
return m_flags.test(Flag_DefaultSet);
}

T valueOr(T&& def) const& {
if (m_value.has_value()) {
return *m_value;
}
return std::forward<T>(def);
}

T& materialize() {
if (!m_value.has_value()) {
m_value.emplace();
}
return *m_value;
}

T* operator->() noexcept {
return &value();
}
const T* operator->() const noexcept {
return &value();
}

// yes, all implicit operators are intentional

T& value() noexcept { return *m_value; }
const T& value() const noexcept { return *m_value; }

operator T& ()& noexcept { return value(); }
operator const T& () const& noexcept { return value(); }
operator T()&& noexcept { return std::move(value()); }

std::optional<T>& opt() noexcept { return m_value; }
const std::optional<T>& opt() const noexcept { return m_value; }

operator std::optional<T>& ()& noexcept { return opt(); }
operator const std::optional<T>& () const& noexcept { return opt(); }
operator std::optional<T>()&& noexcept { return std::move(opt()); }

template <typename U>
friend auto operator<=>(const Field& f, const U& t) noexcept {
return f.m_value <=> t;
}

template <typename U>
friend auto operator==(const Field& f, const U& t) noexcept {
return f.m_value == t;
}

template <typename U>
friend auto operator<=>(const U& t, const Field& f) noexcept {
return t <=> f.m_value;
}

private:
// ideally, instead of using optional + flags, we would reimplement optional with flags
// however that's too much work for now so we will have some bits wasted and an additional byte as member
std::optional<T> m_value;

enum Flags {
Flag_Required,
Flag_DefaultSet
};
std::bitset<2> m_flags;
};

} // namespace ac::local::schema
46 changes: 46 additions & 0 deletions schema/test/t-Field.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Alpaca Core
// SPDX-License-Identifier: MIT
//
#include <ac/schema/Field.hpp>
#include <doctest/doctest.h>

using namespace ac::local::schema;

struct Person {
Field<std::string> name;
Field<int> age = std::nullopt;
Field<bool> isChild = Default(false);
Field<std::string> address = Default();
Field<int> height = std::nullopt;
};

TEST_CASE("field") {
Person p = {
.name = "Alice"
};
CHECK(p.name == "Alice");
CHECK_FALSE(p.name.defaultSet());

CHECK_FALSE(p.age.hasValue());

CHECK(p.address == "");
CHECK(p.address.defaultSet());

CHECK_FALSE(p.height.hasValue());

p.age = 25;
CHECK(p.age == 25);
auto i = p.age.materialize();
CHECK(i == 25);
CHECK_FALSE(p.age.defaultSet());

CHECK(p.isChild == false);
CHECK(p.isChild.defaultSet());

p.isChild = true;
CHECK(p.isChild);
CHECK_FALSE(p.isChild.defaultSet());

i = p.height.materialize();
CHECK(i == 0);
}

0 comments on commit 3e65810

Please sign in to comment.