Skip to content
Closed
Show file tree
Hide file tree
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
58 changes: 5 additions & 53 deletions universal/src/formats/yaml/value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,6 @@
#include "exttypes.hpp"
#include "string_view_support.hpp"

namespace {

// Helper structure for YAML conversions. YAML has built in conversion logic and
// an `T Node::as<T>(U default_value)` function that uses it. We provide
// `IsConvertibleChecker<T>{}` as a `default_value`, and if the
// `IsConvertibleChecker` was converted to T (an implicit conversion operator
// was called), then the conversion failed.
template <class T>
struct IsConvertibleChecker {
bool& convertible;

operator T() const {
convertible = false;
return {};
}
};

} // anonymous namespace

namespace YAML {

// Reverting harm done by https://github.com/jbeder/yaml-cpp/commit/96f5c887f373ac483844c51cfc9a3621002314f0
// to detect if the conversion is successful without throwing an exception
template <typename T>
requires std::integral<T> || std::floating_point<T>
struct as_if<T, IsConvertibleChecker<T> > {
explicit as_if(const Node& node_in)
: node(node_in)
{}
const Node& node;

T operator()(const IsConvertibleChecker<T>& fallback) const {
if (!node.m_pNode) {
return fallback;
}

T t;
if (convert<T>::decode(node, t)) {
return t;
}
return fallback;
}
};

} // namespace YAML

USERVER_NAMESPACE_BEGIN

namespace formats::yaml {
Expand Down Expand Up @@ -182,21 +136,19 @@ bool Value::IsConvertibleToArithmetic() const {
return false;
}

bool ok = true;
value_pimpl_->as<T>(IsConvertibleChecker<T>{ok});
return ok && !IsExplicitlyTypedString(*value_pimpl_);
T t{};
return YAML::convert<T>::decode(*value_pimpl_, t) && !IsExplicitlyTypedString(*value_pimpl_);
}

template <class T>
T Value::ValueAsArithmetic() const {
CheckNotMissing();

bool ok = true;
auto res = value_pimpl_->as<T>(IsConvertibleChecker<T>{ok});
if (!ok || IsExplicitlyTypedString(*value_pimpl_)) {
T t{};
if (!YAML::convert<T>::decode(*value_pimpl_, t) || IsExplicitlyTypedString(*value_pimpl_)) {
throw TypeMismatchException(*value_pimpl_, compiler::GetTypeName<T>(), path_.ToStringView());
}
return res;
return t;
}

bool Value::IsNull() const noexcept {
Expand Down
19 changes: 19 additions & 0 deletions universal/src/formats/yaml/value_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,23 @@ TEST(FormatsYaml, NodesTags) {
EXPECT_EQ(yaml["field_tag_prefix"].GetTag(), "tag:example,2024:foo");
}

TEST(FormatsYaml, IsBoolFromParsedScalar) {
auto f = formats::yaml::FromString("false");
auto t = formats::yaml::FromString("true");

EXPECT_TRUE(f.IsBool());
EXPECT_TRUE(t.IsBool());
EXPECT_FALSE(f.As<bool>());
EXPECT_TRUE(t.As<bool>());

EXPECT_FALSE(formats::yaml::FromString("\"false\"").IsBool());
EXPECT_FALSE(formats::yaml::FromString("hello").IsBool());
}

TEST(FormatsYaml, IsIntFromParsedScalar) {
auto v = formats::yaml::FromString("42");
EXPECT_TRUE(v.IsInt());
EXPECT_EQ(v.As<int>(), 42);
}

USERVER_NAMESPACE_END
12 changes: 12 additions & 0 deletions universal/src/yaml_config/merge_schemas_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,16 @@ TEST(MergeSchemas, RequiredParentOnlyNoChildRequired) {
EXPECT_EQ(*schema.required, (std::unordered_set<std::string>{"parent_option"}));
}

TEST(YamlConfigSchema, AdditionalPropertiesFalse) {
auto schema = yaml_config::impl::SchemaFromString(R"(
type: object
description: test
additionalProperties: false
properties: {}
)");
ASSERT_TRUE(schema.additional_properties.has_value());
ASSERT_TRUE(std::holds_alternative<bool>(schema.additional_properties.value()));
EXPECT_FALSE(std::get<bool>(schema.additional_properties.value()));
}

USERVER_NAMESPACE_END
Loading