Skip to content

Commit

Permalink
Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
linuxnyasha committed Apr 6, 2024
1 parent 30a7b78 commit 4c2bd65
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 22 deletions.
9 changes: 6 additions & 3 deletions universal/include/userver/formats/parse/try_parse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,21 @@ inline constexpr std::optional<std::string> TryParse(Value&& value, To<std::stri

template <typename Value, typename T>
inline constexpr std::optional<T> TryParse(Value&& value, To<T>) requires meta::kIsInteger<T> {
constexpr auto f = [](auto response){
return impl::CheckInBounds(response, std::numeric_limits<T>::min(), std::numeric_limits<T>::max()) ? std::optional{static_cast<T>(response)} : std::nullopt;
};
if constexpr(std::is_unsigned_v<T>) {
if(!((sizeof(T) == sizeof(std::uint64_t)) ? value.IsUInt64() : value.IsInt64())) {
if(!value.IsUInt64()) {
return std::nullopt;
}
auto response = value.template As<std::uint64_t>();
return impl::CheckInBounds(response, std::numeric_limits<T>::min(), std::numeric_limits<T>::max()) ? std::optional{static_cast<T>(response)} : std::nullopt;
return f(std::move(response));
} else {
if(!value.IsInt64()) {
return std::nullopt;
}
auto response = value.template As<std::int64_t>();
return impl::CheckInBounds(response, std::numeric_limits<T>::min(), std::numeric_limits<T>::max()) ? std::optional{static_cast<T>(response)} : std::nullopt;
return f(std::move(response));
}
}
template <typename Value, typename T>
Expand Down
18 changes: 5 additions & 13 deletions universal/include/userver/formats/universal/common_containers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ struct FieldConfig<std::optional<T>> {
constexpr std::optional<T> Read(Value&& value) const {
constexpr auto name = boost::pfr::get_name<I, MainClass>();
if(this->Required && value[name].IsMissing()) {
return std::nullopt;
using Type = std::remove_reference_t<Value>;
throw typename Type::Exception{fmt::format("required, !nullable was violated in {}", value.GetPath())};
}
if(!value[name].IsMissing()) {
return this->Nullable
Expand All @@ -177,24 +178,15 @@ struct FieldConfig<std::optional<T>> {
if(value[name].IsMissing() && this->Required) {
return std::nullopt;
}
if(this->Nullable) {
std::optional response = this->Main.template TryRead<MainClass, I>(std::forward<Value>(value));
if(response) {
return response;
}
} else {
if(!this->Nullable) {
if(value[name].IsNull()) {
return std::nullopt;
};
std::optional response = this->Main.template TryRead<MainClass, I>(std::forward<Value>(value));
if(response) {
return response;
}
}
if(this->Default) {
if(this->Default && (value[name].IsMissing() || value[name].IsNull())) {
return this->Default->value();
}
return {{}};
return this->Main.template TryRead<MainClass, I>(std::forward<Value>(value));
}
};

Expand Down
91 changes: 87 additions & 4 deletions universal/include/userver/formats/universal/universal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
#include <userver/formats/parse/try_parse.hpp>
#include <userver/formats/serialize/to.hpp>
#include <userver/utils/meta.hpp>
#include <userver/utils/trivial_map.hpp>
#include <userver/utils/overloaded.hpp>
#include <variant>
#include <type_traits>
#include <boost/pfr/core.hpp>
#include <boost/pfr/core_name.hpp>

#include <boost/lexical_cast.hpp>

USERVER_NAMESPACE_BEGIN

Expand Down Expand Up @@ -189,6 +190,12 @@ struct EmptyCheck {
return std::nullopt;
}
};

template <typename T>
concept kEnabled = !(std::same_as<std::remove_cvref_t<T>, Disabled>);



} // namespace impl

template <typename T>
Expand Down Expand Up @@ -242,21 +249,66 @@ class SerializationConfig<std::variant<Ts...>> {
} // namespace formats::universal


namespace formats::enums {

template <typename T>
inline constexpr auto kEnumMapping = universal::impl::Disabled{};


} // namespace formats::enums

namespace formats::parse {

template <typename To>
inline constexpr auto ParseFromString(std::string_view from) -> To {
return boost::lexical_cast<To>(from);
}

template <>
inline constexpr auto ParseFromString<std::string_view>(std::string_view from) -> std::string_view {
return from;
};

template <typename T>
inline constexpr auto ParseFromString(std::string_view value) -> T
requires universal::impl::kEnabled<decltype(enums::kEnumMapping<T>)> {
using M = decltype(enums::kEnumMapping<std::remove_cvref_t<T>>);
auto response = enums::kEnumMapping<T>.TryFind(ParseFromString<typename M::template MappedTypeFor<T>>(value));
if(!response) {
throw std::runtime_error{"Can't serialize enum to string"};
}
return *std::move(response);
}

template <typename Value, typename T>
inline constexpr auto Parse(Value&& value, To<T>) -> T
requires (!std::same_as<std::remove_cvref_t<decltype(universal::kDeserialization<std::remove_cvref_t<T>>)>, universal::impl::Disabled>) {
requires universal::impl::kEnabled<decltype(universal::kDeserialization<T>)> {
return [&]<auto... I>(std::index_sequence<I...>){
auto config = universal::kDeserialization<T>;
return T{universal::impl::UniversalParseField<T, I>(value, config.template Get<I>())...};
}(std::make_index_sequence<boost::pfr::tuple_size_v<T>>());
}

template <typename Value, typename T>
inline constexpr auto Parse(Value&& value, To<T>) -> T
requires universal::impl::kEnabled<decltype(enums::kEnumMapping<T>)> {
using M = decltype(enums::kEnumMapping<T>);
constexpr auto f = [](auto response){
if(response) {
return *response;
};
throw std::runtime_error("Can't Parse to enum");
};
if constexpr(std::same_as<typename M::template MappedTypeFor<T>, std::string_view>) {
return f(enums::kEnumMapping<T>.TryFind(value.template As<std::string>()));
} else {
return f(enums::kEnumMapping<T>.TryFind(value.template As<typename M::template MappedTypeFor<T>>()));
};
}

template <typename Value, typename T>
inline constexpr auto TryParse(Value&& value, To<T>) -> std::optional<T>
requires (!std::same_as<std::remove_cvref_t<decltype(universal::kDeserialization<std::remove_cvref_t<T>>)>, universal::impl::Disabled>) {
requires universal::impl::kEnabled<decltype(universal::kDeserialization<T>)> {
return [&]<auto... I>(std::index_sequence<I...>) -> std::optional<T> {
auto config = universal::kDeserialization<T>;
auto tup = std::make_tuple(universal::impl::UniversalTryParseField<T, I>(value, config.template Get<I>())...);
Expand All @@ -270,9 +322,40 @@ inline constexpr auto TryParse(Value&& value, To<T>) -> std::optional<T>
} // namespace formats::parse
namespace formats::serialize {

template <typename From>
inline constexpr auto ToString(From&& from) -> std::string {
return std::to_string(std::forward<From>(from));
}

inline constexpr auto ToString(std::string_view from) -> std::string {
return static_cast<std::string>(from);
};

template <typename T>
inline constexpr auto ToString(T&& value) -> std::string
requires universal::impl::kEnabled<decltype(enums::kEnumMapping<std::remove_cvref_t<T>>)> {
auto response = enums::kEnumMapping<std::remove_cvref_t<T>>.TryFind(std::forward<T>(value));
if(!response) {
throw std::runtime_error{"Can't serialize enum to string"};
}
return ToString(*std::move(response));
}



template <typename Value, typename T>
inline constexpr auto Serialize(T&& obj, To<Value>) -> Value
requires universal::impl::kEnabled<decltype(enums::kEnumMapping<std::remove_cvref_t<T>>)> {
auto finded = enums::kEnumMapping<std::remove_cvref_t<T>>.TryFind(std::forward<T>(obj));
if(!finded) {
throw std::runtime_error("Can't serialize enum");
}
return typename Value::Builder(*std::move(finded)).ExtractValue();
}

template <typename Value, typename T>
inline constexpr auto Serialize(T&& obj, To<Value>) -> Value
requires (!std::same_as<std::remove_cvref_t<decltype(universal::kSerialization<std::remove_cvref_t<T>>)>, universal::impl::Disabled>) {
requires universal::impl::kEnabled<decltype(universal::kSerialization<std::remove_cvref_t<T>>)> {
using Type = std::remove_cvref_t<T>;
return [&]<auto... I>(std::index_sequence<I...>){
typename Value::Builder builder;
Expand Down
39 changes: 37 additions & 2 deletions universal/src/formats/json/universal_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ TEST(TryParse, Optional) {
const auto json = formats::json::FromString("{}");
SomeStruct2 valid{.field1 = 114, .field4 = {{"42"}}};
EXPECT_EQ(formats::parse::TryParse(json, formats::parse::To<SomeStruct2>{}), valid);
const auto json2 = formats::json::FromString(R"({"field1": ""})");
SomeStruct2 valid2{.field4 = {{"42"}}};
EXPECT_EQ(formats::parse::TryParse(json2, formats::parse::To<SomeStruct2>{}), valid2);

};


Expand Down Expand Up @@ -284,13 +288,13 @@ TEST(Serialize, NonUniversalInUniversal) {
const auto valid = formats::json::FromString(R"({"field":{"field":1}})");
const auto json = formats::json::ValueBuilder(SomeStruct12{.field = {.field = 1}}).ExtractValue();
EXPECT_EQ(json, valid);
};
}

TEST(Parse, NonUniversalInUniversal) {
const auto json = formats::json::FromString(R"({"field":{"field":1}})");
const SomeStruct12 valid{.field = {.field = 1}};
EXPECT_EQ(json.As<SomeStruct12>(), valid);
};
}

struct SomeStruct13 {
std::optional<std::vector<SomeStruct11>> field;
Expand All @@ -309,8 +313,39 @@ TEST(Parse, NonLiteralDefaultTest) {
const auto json = formats::json::FromString(R"({})");
const SomeStruct13 valid{.field{std::vector<SomeStruct11>{{.field = 1}}}};
EXPECT_EQ(json.As<SomeStruct13>(), valid);
}

enum class SomeEnum {
kFoo, kBar
};

template <>
inline constexpr utils::TrivialBiMap formats::enums::kEnumMapping<SomeEnum> = [](auto selector) {
return selector()
.Case(SomeEnum::kFoo, "foo")
.Case(SomeEnum::kBar, "bar");
};

TEST(Parse, Enum) {
const auto json = formats::json::FromString(R"("foo")");
EXPECT_EQ(json.As<SomeEnum>(), SomeEnum::kFoo);
EXPECT_EQ(formats::parse::ParseFromString<SomeEnum>("foo"), SomeEnum::kFoo);
const auto json2 = formats::json::FromString(R"("bar")");
EXPECT_EQ(json2.As<SomeEnum>(), SomeEnum::kBar);
EXPECT_EQ(formats::parse::ParseFromString<SomeEnum>("bar"), SomeEnum::kBar);
}

TEST(Serialize, Enum) {
const auto valid = formats::json::FromString(R"("foo")");
const auto json = formats::json::ValueBuilder(SomeEnum::kFoo).ExtractValue();
EXPECT_EQ(json, valid);
EXPECT_EQ(formats::serialize::ToString(SomeEnum::kFoo), "foo");
const auto valid2 = formats::json::FromString(R"("bar")");
const auto json2 = formats::json::ValueBuilder(SomeEnum::kBar).ExtractValue();
EXPECT_EQ(json2, valid2);
EXPECT_EQ(formats::serialize::ToString(SomeEnum::kBar), "bar");
}


USERVER_NAMESPACE_END
#endif

0 comments on commit 4c2bd65

Please sign in to comment.