add Schema
This commit is contained in:
parent
96d3269dee
commit
3ba04554c9
@ -12,6 +12,7 @@ target_sources(nf7_iface
|
||||
common/container.hh
|
||||
common/future.hh
|
||||
common/observer.hh
|
||||
common/schema.hh
|
||||
common/task.hh
|
||||
common/task_context.hh
|
||||
common/value.hh
|
||||
@ -33,6 +34,7 @@ target_sources(nf7_iface_test
|
||||
common/future_test.cc
|
||||
common/observer_test.hh
|
||||
common/observer_test.cc
|
||||
common/schema_test.cc
|
||||
common/task_test.cc
|
||||
common/value_test.cc
|
||||
)
|
||||
|
267
iface/common/schema.hh
Normal file
267
iface/common/schema.hh
Normal file
@ -0,0 +1,267 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "iface/common/exception.hh"
|
||||
#include "iface/common/value.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Schema;
|
||||
|
||||
class Schema {
|
||||
public:
|
||||
class Walker {
|
||||
public:
|
||||
using Key = std::variant<std::monostate, uint64_t, std::string>;
|
||||
|
||||
enum Msg {
|
||||
kTypeIncompatible,
|
||||
kNumericOverflow,
|
||||
kNumericUnderflow,
|
||||
kObjectUnknownItem,
|
||||
kObjectMissingItem,
|
||||
kCustomInfo,
|
||||
kCustomWarn,
|
||||
kCustomError,
|
||||
};
|
||||
|
||||
Walker() = default;
|
||||
Walker(const Walker&) = delete;
|
||||
Walker(Walker&&) = delete;
|
||||
Walker& operator=(const Walker&) = delete;
|
||||
Walker& operator=(Walker&&) = delete;
|
||||
|
||||
virtual void Enter(const Key&) noexcept { }
|
||||
virtual void Leave() noexcept { }
|
||||
|
||||
virtual void Push(const Schema&, const Value&) noexcept { }
|
||||
virtual void Pop(bool) noexcept { }
|
||||
|
||||
virtual void AddMsg(Msg, std::string_view = "") noexcept { }
|
||||
};
|
||||
|
||||
class ObjectItem final {
|
||||
public:
|
||||
enum Flag : uint8_t {
|
||||
kRequired = 1 << 0,
|
||||
};
|
||||
using Flags = uint8_t;
|
||||
|
||||
ObjectItem(std::string_view name,
|
||||
const std::shared_ptr<const Schema>& schema,
|
||||
Flags flags = 0) noexcept
|
||||
: name_(name),
|
||||
schema_(schema),
|
||||
flags_(flags) { }
|
||||
ObjectItem(std::string_view name,
|
||||
Schema&& schema,
|
||||
Flags flags = 0)
|
||||
try : ObjectItem(name,
|
||||
std::make_shared<Schema>(std::move(schema)),
|
||||
flags) {
|
||||
} catch (const std::bad_alloc&) {
|
||||
throw Exception {"memory shortage"};
|
||||
}
|
||||
|
||||
ObjectItem(const ObjectItem&) = default;
|
||||
ObjectItem(ObjectItem&&) = default;
|
||||
ObjectItem& operator=(const ObjectItem&) = default;
|
||||
ObjectItem& operator=(ObjectItem&&) = default;
|
||||
|
||||
const std::string& name() const noexcept { return name_; }
|
||||
const Schema& schema() const noexcept { return *schema_; }
|
||||
Flags flags() const noexcept { return flags_; }
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::shared_ptr<const Schema> schema_;
|
||||
Flags flags_;
|
||||
};
|
||||
|
||||
public:
|
||||
class Constraint {
|
||||
public:
|
||||
using Matcher = std::function<bool(Walker&, const Value&)>;
|
||||
|
||||
explicit Constraint(Matcher&& matcher = {}) noexcept
|
||||
: matcher_(std::move(matcher)) {
|
||||
}
|
||||
|
||||
Constraint(const Constraint&) = delete;
|
||||
Constraint(Constraint&&) = default;
|
||||
Constraint& operator=(const Constraint&) = delete;
|
||||
Constraint& operator=(Constraint&&) = default;
|
||||
|
||||
virtual bool Match(Walker& walker, const Value& v) const noexcept {
|
||||
return !matcher_ || matcher_(walker, v);
|
||||
}
|
||||
|
||||
private:
|
||||
Matcher matcher_;
|
||||
};
|
||||
|
||||
class Null : public Constraint {
|
||||
public:
|
||||
using Constraint::Constraint;
|
||||
|
||||
bool Match(Walker& walker, const Value& v) const noexcept override {
|
||||
bool accept = true;
|
||||
if (!v.is<Value::Null>()) {
|
||||
walker.AddMsg(Walker::kTypeIncompatible);
|
||||
accept = false;
|
||||
}
|
||||
return Constraint::Match(walker, v) && accept;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename N>
|
||||
class Numeric : public Constraint {
|
||||
public:
|
||||
explicit Numeric(N min, N max, Matcher&& matcher = {}) noexcept
|
||||
: Constraint(std::move(matcher)), min_(min), max_(max) {
|
||||
}
|
||||
|
||||
bool Match(Walker& walker, const Value& v) const noexcept override {
|
||||
bool accept = true;
|
||||
const auto n = v.asIf<N>();
|
||||
if (!n) {
|
||||
walker.AddMsg(Walker::kTypeIncompatible);
|
||||
accept = false;
|
||||
}
|
||||
if (n && *n < min_) {
|
||||
walker.AddMsg(Walker::kNumericUnderflow);
|
||||
accept = false;
|
||||
}
|
||||
if (n && *n > max_) {
|
||||
walker.AddMsg(Walker::kNumericOverflow);
|
||||
accept = false;
|
||||
}
|
||||
return Constraint::Match(walker, v) && accept;
|
||||
}
|
||||
|
||||
private:
|
||||
N min_, max_;
|
||||
};
|
||||
using Integer = Numeric<Value::Integer>;
|
||||
using Real = Numeric<Value::Real>;
|
||||
|
||||
class Object : public Constraint {
|
||||
public:
|
||||
enum Flag : uint8_t {
|
||||
kExclusive = 1 << 0,
|
||||
};
|
||||
using Flags = uint8_t;
|
||||
|
||||
explicit Object(std::vector<ObjectItem>&& items,
|
||||
Flags flags = 0,
|
||||
Matcher&& matcher = {}) noexcept
|
||||
: Constraint(std::move(matcher)),
|
||||
items_(std::move(items)),
|
||||
flags_(flags) { }
|
||||
|
||||
bool Match(Walker& walker, const Value& v) const noexcept override {
|
||||
bool accept = true;
|
||||
const auto obj = v.asIf<Value::Object>();
|
||||
if (!obj) {
|
||||
walker.AddMsg(Walker::kTypeIncompatible);
|
||||
accept = false;
|
||||
} else {
|
||||
for (const auto& item : items_) {
|
||||
const auto itr = std::find_if(
|
||||
obj->begin(), obj->end(),
|
||||
[&](const auto& pair) { return pair.first == item.name(); });
|
||||
if (itr != obj->end()) {
|
||||
walker.Enter(Walker::Key {item.name()});
|
||||
item.schema().Match(walker, itr->second);
|
||||
walker.Leave();
|
||||
} else if (item.flags() & ObjectItem::kRequired) {
|
||||
accept = false;
|
||||
walker.Enter(Walker::Key {item.name()});
|
||||
walker.AddMsg(Walker::kObjectMissingItem);
|
||||
walker.Leave();
|
||||
}
|
||||
}
|
||||
for (const auto& pair : *obj) {
|
||||
const auto itr = std::find_if(
|
||||
items_.begin(), items_.end(),
|
||||
[&](const auto& item) { return pair.first == item.name(); });
|
||||
if (itr == items_.end()) {
|
||||
walker.Enter(Walker::Key {pair.first});
|
||||
walker.AddMsg(Walker::kObjectUnknownItem);
|
||||
walker.Leave();
|
||||
|
||||
if (flags_ & kExclusive) {
|
||||
accept = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Constraint::Match(walker, v) && accept;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ObjectItem> items_;
|
||||
Flags flags_;
|
||||
};
|
||||
|
||||
using ConstraintVariant = std::variant<Null, Integer, Real, Object>;
|
||||
|
||||
public:
|
||||
static Schema MakeNull(Constraint::Matcher&& matcher = {}) noexcept {
|
||||
return Schema {Null {std::move(matcher)}};
|
||||
}
|
||||
static Schema MakeInteger(
|
||||
Value::Integer min = std::numeric_limits<Value::Integer>::min(),
|
||||
Value::Integer max = std::numeric_limits<Value::Integer>::max(),
|
||||
Constraint::Matcher&& matcher = {}) noexcept {
|
||||
return Schema {Integer {min, max, std::move(matcher)}};
|
||||
}
|
||||
static Schema MakeReal(
|
||||
Value::Real min = -std::numeric_limits<Value::Real>::infinity(),
|
||||
Value::Real max = std::numeric_limits<Value::Real>::infinity(),
|
||||
Constraint::Matcher&& matcher = {}) noexcept {
|
||||
return Schema {Real {min, max, std::move(matcher)}};
|
||||
}
|
||||
static Schema MakeObject(
|
||||
std::vector<ObjectItem>&& items,
|
||||
Object::Flags flags = 0,
|
||||
Constraint::Matcher&& matcher = {}) noexcept {
|
||||
return Schema {Object { std::move(items), flags, std::move(matcher) }};
|
||||
}
|
||||
|
||||
public:
|
||||
Schema(const Schema&) = delete;
|
||||
Schema(Schema&&) = default;
|
||||
Schema& operator=(const Schema&) = delete;
|
||||
Schema& operator=(Schema&&) = default;
|
||||
|
||||
bool Match(Walker& walker, const Value& v) const noexcept {
|
||||
walker.Push(*this, v);
|
||||
const auto accept =
|
||||
std::visit([&](auto& c){ return c.Match(walker, v); }, constraint_);
|
||||
walker.Pop(accept);
|
||||
return accept;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Schema(ConstraintVariant&& v) noexcept
|
||||
: constraint_(std::move(v)) { }
|
||||
|
||||
private:
|
||||
ConstraintVariant constraint_;
|
||||
};
|
||||
|
||||
} // namespace nf7
|
338
iface/common/schema_test.cc
Normal file
338
iface/common/schema_test.cc
Normal file
@ -0,0 +1,338 @@
|
||||
// No copyright
|
||||
#include "iface/common/schema.hh"
|
||||
#include "iface/common/schema_test.hh"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "iface/common/value.hh"
|
||||
|
||||
|
||||
inline void TestSchema(
|
||||
const nf7::Value& value,
|
||||
const nf7::Schema& schema,
|
||||
bool accept,
|
||||
auto setup) {
|
||||
nf7::test::SchemaWalkerMock walker;
|
||||
|
||||
::testing::Sequence seq;
|
||||
EXPECT_CALL(walker, Push(::testing::Ref(schema), ::testing::Ref(value)))
|
||||
.InSequence(seq);
|
||||
if constexpr (std::is_invocable_v<
|
||||
decltype(setup),
|
||||
nf7::test::SchemaWalkerMock&>) {
|
||||
setup(walker);
|
||||
} else if constexpr (std::is_invocable_v<
|
||||
decltype(setup),
|
||||
nf7::test::SchemaWalkerMock&,
|
||||
::testing::Sequence&>) {
|
||||
setup(walker, seq);
|
||||
}
|
||||
EXPECT_CALL(walker, Pop(accept))
|
||||
.InSequence(seq);
|
||||
|
||||
schema.Match(walker, value);
|
||||
}
|
||||
inline void TestSchema(
|
||||
const nf7::Value& value,
|
||||
const nf7::Schema& schema,
|
||||
bool accept) {
|
||||
TestSchema(value, schema, accept, [](auto&){});
|
||||
}
|
||||
|
||||
#define EXPECT_MSG_(m, msg) EXPECT_CALL((m), AddMsg(msg, ::testing::_))
|
||||
|
||||
|
||||
TEST(Schema, CustomMatcher) {
|
||||
TestSchema(nf7::Value::Null {},
|
||||
nf7::Schema::MakeNull([](auto&, auto&) { return false; }),
|
||||
false);
|
||||
}
|
||||
|
||||
TEST(Schema, NullAccept) {
|
||||
TestSchema(nf7::Value::Null {}, nf7::Schema::MakeNull(), true);
|
||||
}
|
||||
TEST(Schema, NullRejectByTypeIncompatible) {
|
||||
TestSchema(nf7::Value::Integer {}, nf7::Schema::MakeNull(), false,
|
||||
[](auto& m) {
|
||||
EXPECT_MSG_(m, nf7::Schema::Walker::kTypeIncompatible);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Schema, IntegerAccept) {
|
||||
TestSchema(nf7::Value::Integer {}, nf7::Schema::MakeInteger(), true);
|
||||
}
|
||||
TEST(Schema, IntegerAcceptWithRange) {
|
||||
TestSchema(nf7::Value::Integer {0}, nf7::Schema::MakeInteger(0, 100), true);
|
||||
TestSchema(nf7::Value::Integer {100}, nf7::Schema::MakeInteger(0, 100), true);
|
||||
}
|
||||
TEST(Schema, IntegerRejectByTypeIncompatible) {
|
||||
TestSchema(nf7::Value::Real {}, nf7::Schema::MakeInteger(), false,
|
||||
[](auto& m) {
|
||||
EXPECT_MSG_(m, nf7::Schema::Walker::kTypeIncompatible);
|
||||
});
|
||||
}
|
||||
TEST(Schema, IntegerRejectByUnderflow) {
|
||||
TestSchema(nf7::Value::Integer {-1}, nf7::Schema::MakeInteger(0, 100), false,
|
||||
[](auto& m) {
|
||||
EXPECT_MSG_(m, nf7::Schema::Walker::kNumericUnderflow);
|
||||
});
|
||||
}
|
||||
TEST(Schema, IntegerRejectByOverflow) {
|
||||
TestSchema(nf7::Value::Integer {101}, nf7::Schema::MakeInteger(0, 100), false,
|
||||
[](auto& m) {
|
||||
EXPECT_MSG_(m, nf7::Schema::Walker::kNumericOverflow);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Schema, RealAccept) {
|
||||
TestSchema(nf7::Value::Real {}, nf7::Schema::MakeReal(), true);
|
||||
}
|
||||
TEST(Schema, RealAcceptWithRange) {
|
||||
TestSchema(nf7::Value::Real {0}, nf7::Schema::MakeReal(0, 1), true);
|
||||
TestSchema(nf7::Value::Real {1}, nf7::Schema::MakeReal(0, 1), true);
|
||||
}
|
||||
TEST(Schema, RealRejectByTypeIncompatible) {
|
||||
TestSchema(nf7::Value::Integer {}, nf7::Schema::MakeReal(), false,
|
||||
[](auto& m) {
|
||||
EXPECT_MSG_(m, nf7::Schema::Walker::kTypeIncompatible);
|
||||
});
|
||||
}
|
||||
TEST(Schema, RealRejectByUnderflow) {
|
||||
TestSchema(nf7::Value::Real {-0.1}, nf7::Schema::MakeReal(0, 1), false,
|
||||
[](auto& m) {
|
||||
EXPECT_MSG_(m, nf7::Schema::Walker::kNumericUnderflow);
|
||||
});
|
||||
}
|
||||
TEST(Schema, RealRejectByOverflow) {
|
||||
TestSchema(nf7::Value::Real {1.1}, nf7::Schema::MakeReal(0, 1), false,
|
||||
[](auto& m) {
|
||||
EXPECT_MSG_(m, nf7::Schema::Walker::kNumericOverflow);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Schema, ObjectAccept) {
|
||||
TestSchema(
|
||||
nf7::MakeValue<nf7::Value::Object::Pair>({
|
||||
{"hello", nf7::Value::Null {}},
|
||||
{"world", nf7::Value::Null {}},
|
||||
}),
|
||||
nf7::Schema::MakeObject({
|
||||
nf7::Schema::ObjectItem {"hello", nf7::Schema::MakeNull()},
|
||||
nf7::Schema::ObjectItem {"world", nf7::Schema::MakeNull()},
|
||||
}),
|
||||
true,
|
||||
[](auto& m, auto& seq) {
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"hello"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"world"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
});
|
||||
}
|
||||
TEST(Schema, ObjectAcceptExclusive) {
|
||||
TestSchema(
|
||||
nf7::MakeValue<nf7::Value::Object::Pair>({
|
||||
{"hello", nf7::Value::Null {}},
|
||||
{"world", nf7::Value::Null {}},
|
||||
}),
|
||||
nf7::Schema::MakeObject({
|
||||
nf7::Schema::ObjectItem {"hello", nf7::Schema::MakeNull()},
|
||||
nf7::Schema::ObjectItem {"world", nf7::Schema::MakeNull()},
|
||||
}, nf7::Schema::Object::kExclusive),
|
||||
true,
|
||||
[](auto& m, auto& seq) {
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"hello"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"world"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
});
|
||||
}
|
||||
TEST(Schema, ObjectAcceptMissingItem) {
|
||||
::testing::Sequence seq_unknown;
|
||||
TestSchema(
|
||||
nf7::MakeValue<nf7::Value::Object::Pair>({
|
||||
{"hello", nf7::Value::Null {}},
|
||||
}),
|
||||
nf7::Schema::MakeObject({
|
||||
nf7::Schema::ObjectItem {"hello", nf7::Schema::MakeNull()},
|
||||
nf7::Schema::ObjectItem {"world", nf7::Schema::MakeNull()},
|
||||
}),
|
||||
true,
|
||||
[&](auto& m, auto& seq) {
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"hello"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
});
|
||||
}
|
||||
TEST(Schema, ObjectAcceptUnknownItem) {
|
||||
::testing::Sequence seq_unknown;
|
||||
TestSchema(
|
||||
nf7::MakeValue<nf7::Value::Object::Pair>({
|
||||
{"hello", nf7::Value::Null {}},
|
||||
{"world", nf7::Value::Null {}},
|
||||
{"bye", nf7::Value::Null {}},
|
||||
}),
|
||||
nf7::Schema::MakeObject({
|
||||
nf7::Schema::ObjectItem {"hello", nf7::Schema::MakeNull()},
|
||||
nf7::Schema::ObjectItem {"world", nf7::Schema::MakeNull()},
|
||||
}),
|
||||
true,
|
||||
[&](auto& m, auto& seq) {
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"hello"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"world"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"bye"}))
|
||||
.InSequence(seq_unknown);
|
||||
EXPECT_MSG_(m, nf7::Schema::Walker::kObjectUnknownItem)
|
||||
.InSequence(seq_unknown);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq_unknown);
|
||||
});
|
||||
}
|
||||
TEST(Schema, ObjectAcceptNested) {
|
||||
TestSchema(
|
||||
nf7::MakeValue<nf7::Value::Object::Pair>({
|
||||
{"hello", nf7::MakeValue<nf7::Value::Object::Pair>({
|
||||
{"world", nf7::Value::Null {}},
|
||||
})},
|
||||
}),
|
||||
nf7::Schema::MakeObject({
|
||||
{"hello", nf7::Schema::MakeObject({
|
||||
{"world", nf7::Schema::MakeNull()},
|
||||
})},
|
||||
}),
|
||||
true,
|
||||
[&](auto& m, auto& seq) {
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"hello"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"world"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
});
|
||||
}
|
||||
TEST(Schema, ObjectRejectMissingItem) {
|
||||
::testing::Sequence seq_unknown;
|
||||
TestSchema(
|
||||
nf7::MakeValue<nf7::Value::Object::Pair>({
|
||||
{"hello", nf7::Value::Null {}},
|
||||
}),
|
||||
nf7::Schema::MakeObject({
|
||||
{"hello", nf7::Schema::MakeNull()},
|
||||
{"world", nf7::Schema::MakeNull(), nf7::Schema::ObjectItem::kRequired},
|
||||
}),
|
||||
false,
|
||||
[&](auto& m, auto& seq) {
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"hello"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"world"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_MSG_(m, nf7::Schema::Walker::kObjectMissingItem)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
});
|
||||
}
|
||||
TEST(Schema, ObjectRejectUnknownItem) {
|
||||
::testing::Sequence seq_unknown;
|
||||
TestSchema(
|
||||
nf7::MakeValue<nf7::Value::Object::Pair>({
|
||||
{"hello", nf7::Value::Null {}},
|
||||
{"world", nf7::Value::Null {}},
|
||||
{"bye", nf7::Value::Null {}},
|
||||
}),
|
||||
nf7::Schema::MakeObject({
|
||||
{"hello", nf7::Schema::MakeNull()},
|
||||
{"world", nf7::Schema::MakeNull()},
|
||||
}, nf7::Schema::Object::kExclusive),
|
||||
false,
|
||||
[&](auto& m, auto& seq) {
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"hello"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"world"}))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Push)
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Pop(true))
|
||||
.InSequence(seq);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq);
|
||||
|
||||
EXPECT_CALL(m, Enter(nf7::Schema::Walker::Key {"bye"}))
|
||||
.InSequence(seq_unknown);
|
||||
EXPECT_MSG_(m, nf7::Schema::Walker::kObjectUnknownItem)
|
||||
.InSequence(seq_unknown);
|
||||
EXPECT_CALL(m, Leave())
|
||||
.InSequence(seq_unknown);
|
||||
});
|
||||
}
|
26
iface/common/schema_test.hh
Normal file
26
iface/common/schema_test.hh
Normal file
@ -0,0 +1,26 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include "iface/common/schema.hh"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
|
||||
namespace nf7::test {
|
||||
|
||||
class SchemaWalkerMock : public Schema::Walker {
|
||||
public:
|
||||
SchemaWalkerMock() = default;
|
||||
|
||||
MOCK_METHOD(void, Enter, (const Key&), (noexcept, override));
|
||||
MOCK_METHOD(void, Leave, (), (noexcept, override));
|
||||
|
||||
MOCK_METHOD(void, Push, (const Schema&, const Value&), (noexcept, override));
|
||||
MOCK_METHOD(void, Pop, (bool), (noexcept, override));
|
||||
|
||||
MOCK_METHOD(void, AddMsg, (Msg, std::string_view), (noexcept, override));
|
||||
};
|
||||
|
||||
} // namespace nf7::test
|
Loading…
x
Reference in New Issue
Block a user