diff --git a/universal/include/userver/formats/parse/try_parse.hpp b/universal/include/userver/formats/parse/try_parse.hpp new file mode 100644 index 000000000000..5010ce8516c2 --- /dev/null +++ b/universal/include/userver/formats/parse/try_parse.hpp @@ -0,0 +1,115 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +USERVER_NAMESPACE_BEGIN + +namespace formats::parse { + +namespace impl { +bool CheckInBounds(const auto& x, const auto& min, const auto& max) { + if (x < min || x > max) { + return false; + }; + return true; +}; + +} // namespace impl + +template +inline constexpr std::optional TryParse(Value&& value, To) { + if(!value.IsObject()) { + return std::nullopt; + } + return value.template As(); +} + +template +inline constexpr std::optional TryParse(Value&& value, To) { + if(!value.IsBool()) { + return std::nullopt; + } + return value.template As(); +} +template +inline constexpr std::optional TryParse(Value&& value, To) { + if(!value.IsDouble()) { + return std::nullopt; + } + return value.template As(); +} +template +inline constexpr std::optional TryParse(Value&& value, To) { + if(!value.IsString()) { + return std::nullopt; + } + return value.template As(); +} + +template +inline constexpr std::optional TryParse(Value&& value, To) requires meta::kIsInteger { + constexpr auto f = [](auto response){ + return impl::CheckInBounds(response, std::numeric_limits::min(), std::numeric_limits::max()) ? std::optional{static_cast(response)} : std::nullopt; + }; + if constexpr(std::is_unsigned_v) { + if(!value.IsUInt64()) { + return std::nullopt; + } + auto response = value.template As(); + return f(std::move(response)); + } else { + if(!value.IsInt64()) { + return std::nullopt; + } + auto response = value.template As(); + return f(std::move(response)); + } +} +template +inline constexpr std::optional> TryParse(Value&& value, To>) { + if(!value.IsArray()) { + return std::nullopt; + } + std::vector response; + response.reserve(value.GetSize()); + for(const auto& item : value) { + auto insert = TryParse(item, To{}); + if(!insert) { + return std::nullopt; + } + response.push_back(std::move(*insert)); + } + return response; +} + +template +constexpr inline std::optional> TryParse(Value&& value, To>) { + if (value.IsMissing() || value.IsNull()) { + return std::optional{std::nullopt}; + } + return TryParse(value, To{}); +} + +template +constexpr inline std::optional TryParse(Value&& value, To) { + auto response = value.template As(); + if(impl::CheckInBounds(response, std::numeric_limits::lowest(), + std::numeric_limits::max())) { + return static_cast(response); + } + return std::nullopt; +} + + + +} // namespace formats::parse + +USERVER_NAMESPACE_END diff --git a/universal/include/userver/formats/universal/common_containers.hpp b/universal/include/userver/formats/universal/common_containers.hpp new file mode 100644 index 000000000000..1b25d346f202 --- /dev/null +++ b/universal/include/userver/formats/universal/common_containers.hpp @@ -0,0 +1,360 @@ +#if __cplusplus >= 202002L +#pragma once +#include +#include +#include +#include +#include +#include + +USERVER_NAMESPACE_BEGIN +namespace formats::universal { +template +struct Max : public impl::Param { + inline constexpr Max(const T& value) : + impl::Param(value) {} + inline constexpr std::optional Check(const T& value) const { + if(value <= this->value) { + return std::nullopt; + } + return this->Error(value); + } + inline constexpr std::string Error(const T& value) const { + return std::format("{} > {}", value, this->value); + } +}; + + +struct MinElements : public impl::Param { + inline constexpr MinElements(const std::size_t& value) : + impl::Param(value) {} + template + inline constexpr auto Check(const Value& value) const -> std::optional { + if(value.size() >= this->value) { + return std::nullopt; + } + return this->Error(value); + } + template + inline constexpr auto Error(const Value&) const { + return "Error"; + } +}; +struct MaxElements : public impl::Param { + inline constexpr MaxElements(const std::size_t& value) : + impl::Param(value) {} + template + inline constexpr auto Check(const Value& value) const -> std::optional { + if(value.size() <= this->value) { + return std::nullopt; + } + return this->Error(value); + } + template + inline constexpr auto Error(const Value&) const { + return "Error"; + } +}; + +template +struct Min : public impl::Param { + inline constexpr Min(const T& value) : + impl::Param(value) {} + inline constexpr std::optional Check(const T& value) const { + if(value >= this->value) { + return std::nullopt; + } + return this->Error(value); + } + inline constexpr auto Error(const T& value) const { + return std::format("{} < {}", value, this->value); + } +}; +template +struct Default : public impl::EmptyCheck, private impl::Param { + inline constexpr Default(T (*ptr)()) : + impl::Param(ptr) {} + inline constexpr Default(auto f) requires requires {static_cast(f);} : + impl::Param(static_cast(f)) {} + inline constexpr auto value() const { + return static_cast&>(*this).value(); + } +}; + +template +struct Default>> : public impl::EmptyCheck, private impl::Param { + inline constexpr Default(const T& value) : + impl::Param(value) {} + inline constexpr auto value() const { + return static_cast&>(*this).value; + } +}; +template <> +struct Default : public impl::EmptyCheck, private impl::Param { + inline constexpr Default(std::string_view value) : + impl::Param(value) {} + template + inline constexpr Default(const char(&value)[N]) : + impl::Param(value) {} + inline constexpr auto value() const -> std::string { + return static_cast(static_cast&>(*this).value); + } +}; + + +template +static const utils::regex kRegex(Pattern); + +struct Pattern : public impl::EmptyCheck, public impl::Param { + constexpr inline Pattern(const utils::regex& regex) : + impl::Param(®ex) {} + constexpr inline std::optional Check(std::string_view str) const { + if(utils::regex_match(str, *this->value)) { + return std::nullopt; + } + return this->Error(str); + } + constexpr inline std::string Error(std::string_view) const { + return "Error"; + } +}; + +struct AdditionalProperties : public impl::EmptyCheck, public impl::Param { + constexpr inline AdditionalProperties(const bool& value) : + impl::Param(value) {} +}; + +template +struct FieldConfig> { + FieldConfig Main = {}; + std::optional> Default = std::nullopt; + bool Required = false; + bool Nullable = false; + static inline constexpr auto Checker(const auto& check, const auto& value) -> std::optional + requires requires{check.Check(*value);} { + if(value) { + return check.Check(*value); + }; + return std::nullopt; + }; + constexpr inline std::optional Check(const std::optional& value) const { + if(value) { + return this->Main.Check(*value); + }; + return std::nullopt; + } + constexpr auto Write(const std::optional& value, std::string_view field_name, const auto& names, auto& builder) const { + if(value) { + this->Main.Write(*value, field_name, names, builder); + return; + } + if(this->Default) { + this->Main.Write(this->Default->value(), field_name, names, builder); + return; + } + } + template + constexpr std::optional Read(Value&& value) const { + constexpr auto name = boost::pfr::get_name(); + if(this->Required && value[name].IsMissing()) { + using Type = std::remove_reference_t; + throw typename Type::Exception{fmt::format("required, !nullable was violated in {}", value.GetPath())}; + } + if(!value[name].IsMissing()) { + return this->Nullable + ? (value[name].IsNull() + ? std::nullopt + : std::optional{this->Main.template Read(std::forward(value))}) + : std::optional{this->Main.template Read(std::forward(value))}; + } + if(this->Default) { + return this->Default->value(); + } + return std::nullopt; + } + template + constexpr std::optional> TryRead(Value&& value) const { + constexpr auto name = boost::pfr::get_name(); + if(value[name].IsMissing() && this->Required) { + return std::nullopt; + } + if(!this->Nullable) { + if(value[name].IsNull()) { + return std::nullopt; + } + } + if(this->Default && (value[name].IsMissing() || value[name].IsNull())) { + return this->Default->value(); + } + return this->Main.template TryRead(std::forward(value)); + } +}; + +template +struct FieldConfig || std::is_floating_point_v>> { + std::optional> Maximum = std::nullopt; + std::optional> Minimum = std::nullopt; + template + constexpr int Read(Value&& value) const { + constexpr auto name = boost::pfr::get_name(); + return value[name].template As(); + } + template + constexpr std::optional TryRead(Value&& value) const { + constexpr auto name = boost::pfr::get_name(); + return parse::TryParse(value[name], parse::To{}); + } + constexpr auto Write(const int& value, std::string_view field_name, const auto&, auto& builder) const { + builder[static_cast(field_name)] = value; + } + inline constexpr std::optional Check(const int&) const { + return std::nullopt; + } +}; +template <> +struct FieldConfig { + std::optional Pattern = std::nullopt; + template + constexpr std::string Read(Value&& value) const { + constexpr auto name = boost::pfr::get_name(); + return value[name].template As(); + } + template + constexpr std::optional TryRead(Value&& value) const { + constexpr auto name = boost::pfr::get_name(); + return parse::TryParse(value[name], parse::To{}); + } + constexpr auto Write(std::string_view value, std::string_view field_name, const auto&, auto& builder) const { + builder[static_cast(field_name)] = value; + } + inline constexpr std::optional Check(std::string_view) const { + return std::nullopt; + } + +}; + +template +struct FieldConfig> { + std::optional AdditionalProperties = std::nullopt; + FieldConfig Items = {}; + using Type = std::unordered_map; + template + inline constexpr Type Read(Value2&& value) const { + Type response; + if(this->AdditionalProperties) { + constexpr auto fields = boost::pfr::names_as_array(); + for(const auto& [name, value] : userver::formats::common::Items(std::forward(value))) { + auto it = std::find(fields.begin(), fields.end(), name); + if(it == fields.end()) { + response.emplace(name, value.template As()); + } + } + return response; + } + constexpr auto fieldName = boost::pfr::get_name(); + for(const auto& [name, value] : userver::formats::common::Items(value[fieldName])) { + response.emplace(name, value.template As()); + } + return response; + } + template + inline constexpr std::optional TryRead(Value2&& value) const { + Type response; + if(this->AdditionalProperties) { + constexpr auto fields = boost::pfr::names_as_array(); + for(const auto& [name, value] : userver::formats::common::Items(std::forward(value))) { + if(std::find(fields.begin(), fields.end(), name) == fields.end()) { + auto New = parse::TryParse(value, parse::To{}); + if(!New) { + return std::nullopt; + } + response.emplace(name, *New); + } + } + return response; + } + constexpr auto field_name = boost::pfr::get_name(); + for(const auto& [name, value] : userver::formats::common::Items(value[field_name])) { + auto New = parse::TryParse(value, parse::To{}); + if(!New) { + return std::nullopt; + } + response.emplace(name, *New); + } + return response; + } + inline constexpr std::optional Check(const Type& map) const { + std::optional error; + for(const auto& [key, value] : map) { + auto add = impl::UniversalCheckField(value, this->Items); + if(add) { + if(!error) { + error = add; + continue; + } + *error += *add; + } + } + return error; + } + template + constexpr auto Write(const Type& value, std::string_view field_name, const auto&, Builder& builder) const { + if(this->AdditionalProperties) { + for(const auto& [name, value2] : value) { + builder[name] = value2; + } + return; + } + Builder newBuilder; + for(const auto& [name, value2] : value) { + newBuilder[name] = value2; + } + builder[static_cast(field_name)] = newBuilder.ExtractValue(); + } +}; +template +struct FieldConfig> { + std::optional MinimalElements = std::nullopt; + FieldConfig Items = {}; + template + inline constexpr auto Read(Value&& value) const { + constexpr auto name = boost::pfr::get_name(); + std::vector response; + for(const auto& element : value[name]) { + auto New = element.template As(); + response.push_back(std::move(New)); + } + return response; + } + template + inline constexpr std::optional> TryRead(Value&& value) const { + std::vector response; + constexpr auto name = boost::pfr::get_name(); + for(const auto& element : value[name]) { + auto New = parse::TryParse(element, parse::To{}); + if(!New) { + return std::nullopt; + } + response.push_back(std::move(*New)); + } + return response; + } + inline constexpr std::optional Check(const std::vector& obj) const { + std::optional error = std::nullopt; + for(const auto& element : obj) { + auto add = impl::UniversalCheckField(element, this->Items); + if(add) { + if(!error) { + error = add; + continue; + } + *error += *add; + } + } + return error; + } + +}; +} // namespace formats::universal +USERVER_NAMESPACE_END +#endif diff --git a/universal/include/userver/formats/universal/universal.hpp b/universal/include/userver/formats/universal/universal.hpp new file mode 100644 index 000000000000..19b85ed0f695 --- /dev/null +++ b/universal/include/userver/formats/universal/universal.hpp @@ -0,0 +1,371 @@ +#if __cplusplus >= 202002L +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace formats::universal { + + +template +struct FieldConfig { + constexpr inline std::optional Check(const T&) const { + return std::nullopt; + } + constexpr auto Write(const T& value, std::string_view field_name, const auto&, auto& builder) const { + builder[static_cast(field_name)] = value; + } + template + constexpr T Read(Value&& value) const { + constexpr auto name = boost::pfr::get_name(); + return value[name].template As(); + } + template + constexpr std::optional TryRead(Value&& value) const { + constexpr auto name = boost::pfr::get_name(); + return parse::TryParse(value[name], parse::To{}); + } +}; +namespace impl { + +template +using kFieldTypeOnIndex = std::remove_reference_t(std::declval()))>; + +template +consteval std::size_t getFieldIndexByName(std::string_view field_name) { + constexpr auto names = boost::pfr::names_as_array(); + return std::find(names.begin(), names.end(), field_name) - names.begin(); +} + +struct Disabled {}; + + +template +struct Param { + T value; + constexpr Param(const T& value) : + value(value) {} +}; + +template +struct Wrapper { + static constexpr auto kValue = Value; +}; + +template +inline constexpr std::optional Add(Args&&... args) { + std::optional result; + ([&](T&& arg){ + if(arg) { + if(!result) { + result.emplace(*std::forward(arg)); + return; + } + result->append(*std::forward(arg)); + } + }(std::forward(args)), ...); + return result; +} + +template +inline constexpr auto checkCaller(T&& response, TT&& check) + requires (requires{FieldConfig>::Checker(check, response);} + || requires{check.Check(std::forward(response));}) { + using Type = FieldConfig>; + if constexpr(requires{Type::Checker(check, response);}) { + return Type::Checker(std::forward(check), response); + } else { + return check.Check(std::forward(response)); + } +} + +template +using RemoveOptional = decltype(utils::Overloaded( + [](const std::optional&) -> TT {}, + [](auto&&) -> T {} + )(std::declval())); + + +template +inline constexpr std::optional UniversalCheckField( + T&& response, + FieldConfig config) { + auto error = [&](std::index_sequence) -> std::optional { + return Add([&](Wrapper) -> std::optional { + const auto& check = boost::pfr::get(config); + using CheckType = std::remove_cvref_t; + if constexpr(requires(RemoveOptional ch){checkCaller(response, ch);}) { + if constexpr(meta::kIsOptional) { + if(check) { + std::optional error = checkCaller(response, *check); + if(error) { + return error; + } + } + } else { + std::optional error = checkCaller(response, check); + if(error) { + return error; + } + } + } + return std::nullopt; + }(Wrapper{})...); + }(std::make_index_sequence>()); + if constexpr(meta::kIsOptional>) { + if(response) { + auto add = config.Check(*response); + if(error) { + if(add) { + *error += *add; + } + return error; + } + error = add; + } + } else { + auto add = config.Check(std::forward(response)); + if(error) { + if(add) { + *error += *add; + } + return error; + } + error = add; + } + return error; +} + + +template +inline constexpr auto UniversalParseField( + T&& value, + FieldConfig> config) { + auto response = config.template Read(value); + std::optional error = UniversalCheckField(response, config); + if(error) { + throw std::runtime_error(std::move(*error)); + } + return response; +} + +template +inline constexpr auto UniversalTryParseField( + T&& value, + FieldConfig> config) { + auto response = config.template TryRead(value); + return [&]() -> decltype(response) { + if(!UniversalCheckField(response, std::move(config))) { + return response; + } + return std::nullopt; + }(); +} + +template +inline constexpr auto UniversalSerializeField( + T&& value, + Builder& builder, + FieldConfig> config) { + std::optional error = UniversalCheckField(value, config); + if(error) { + throw std::runtime_error(std::move(*error)); + } + config.Write(std::forward(value), boost::pfr::get_name(), boost::pfr::names_as_array(), builder); +} +struct EmptyCheck { + template + inline constexpr std::optional Check(Value&&) const { + return std::nullopt; + } +}; + +template +concept kEnabled = !(std::same_as, Disabled>); + + + +} // namespace impl + +template +inline constexpr impl::Disabled kSerialization; + +template +inline constexpr auto kDeserialization = kSerialization; + + +template +class SerializationConfig { + using FieldsConfigType = decltype([](std::index_sequence){ + return std::type_identity>...>>(); + }(std::make_index_sequence>()))::type; + + public: + template + inline constexpr auto& With(FieldConfig(fieldName)>>&& field_config) { + constexpr auto Index = impl::getFieldIndexByName(fieldName); + std::get(this->fields_config) = std::move(field_config); + return *this; + } + inline constexpr SerializationConfig() = default; + + template + inline constexpr auto Get() const { + return std::get(this->fields_config); + } + private: + FieldsConfigType fields_config = {}; +}; + +template +class SerializationConfig> { + public: + template + inline constexpr auto& With(FieldConfig(utils::impl::TypeList{}))>&& field_config) { + std::get(this->variant_config) = std::move(field_config); + return *this; + } + template + inline constexpr auto& With(FieldConfig&& field_config) { + return this->With(utils::impl::TypeList{})>(std::move(field_config)); + } + inline constexpr SerializationConfig() = default; + private: + std::tuple...> variant_config = {}; +}; + + +} // namespace formats::universal + + +namespace formats::enums { + +template +inline constexpr auto kEnumMapping = universal::impl::Disabled{}; + + +} // namespace formats::enums + +namespace formats::parse { + +template +inline constexpr auto ParseFromString(std::string_view from) -> To { + return boost::lexical_cast(from); +} + +template <> +inline constexpr auto ParseFromString(std::string_view from) -> std::string_view { + return from; +}; + +template +inline constexpr auto ParseFromString(std::string_view value) -> T + requires universal::impl::kEnabled)> { + using M = decltype(enums::kEnumMapping>); + auto response = enums::kEnumMapping.TryFind(ParseFromString>(value)); + if(!response) { + throw std::runtime_error{"Can't serialize enum to string"}; + } + return *std::move(response); +} + +template +inline constexpr auto Parse(Value&& value, To) -> T + requires universal::impl::kEnabled)> { + return [&](std::index_sequence){ + auto config = universal::kDeserialization; + return T{universal::impl::UniversalParseField(value, config.template Get())...}; + }(std::make_index_sequence>()); +} + +template +inline constexpr auto Parse(Value&& value, To) -> T + requires universal::impl::kEnabled)> { + using M = decltype(enums::kEnumMapping); + constexpr auto f = [](auto response){ + if(response) { + return *response; + }; + throw std::runtime_error("Can't Parse to enum"); + }; + if constexpr(std::same_as, std::string_view>) { + return f(enums::kEnumMapping.TryFind(value.template As())); + } else { + return f(enums::kEnumMapping.TryFind(value.template As>())); + }; +} + +template +inline constexpr auto TryParse(Value&& value, To) -> std::optional + requires universal::impl::kEnabled)> { + return [&](std::index_sequence) -> std::optional { + auto config = universal::kDeserialization; + auto tup = std::make_tuple(universal::impl::UniversalTryParseField(value, config.template Get())...); + if((std::get(tup) && ...)) { + return T{*std::get(tup)...}; + } + return std::nullopt; + }(std::make_index_sequence>()); +} + +} // namespace formats::parse +namespace formats::serialize { + +template +inline constexpr auto ToString(From&& from) -> std::string { + return std::to_string(std::forward(from)); +} + +inline constexpr auto ToString(std::string_view from) -> std::string { + return static_cast(from); +}; + +template +inline constexpr auto ToString(T&& value) -> std::string + requires universal::impl::kEnabled>)> { + auto response = enums::kEnumMapping>.TryFind(std::forward(value)); + if(!response) { + throw std::runtime_error{"Can't serialize enum to string"}; + } + return ToString(*std::move(response)); +} + + + +template +inline constexpr auto Serialize(T&& obj, To) -> Value + requires universal::impl::kEnabled>)> { + auto finded = enums::kEnumMapping>.TryFind(std::forward(obj)); + if(!finded) { + throw std::runtime_error("Can't serialize enum"); + } + return typename Value::Builder(*std::move(finded)).ExtractValue(); +} + +template +inline constexpr auto Serialize(T&& obj, To) -> Value + requires universal::impl::kEnabled>)> { + using Type = std::remove_cvref_t; + return [&](std::index_sequence){ + typename Value::Builder builder; + auto config = universal::kSerialization; + (universal::impl::UniversalSerializeField(boost::pfr::get(obj), builder, config.template Get()), ...); + return builder.ExtractValue(); + }(std::make_index_sequence>()); +} + +} // namespace formats::serialize + +USERVER_NAMESPACE_END +#endif diff --git a/universal/include/userver/utils/constexpr_string.hpp b/universal/include/userver/utils/constexpr_string.hpp new file mode 100644 index 000000000000..9f48253490a1 --- /dev/null +++ b/universal/include/userver/utils/constexpr_string.hpp @@ -0,0 +1,62 @@ +#if __cplusplus >= 202002L +#pragma once +#include +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace utils { + +template +struct ConstexprString { + + static constexpr auto kSize = Size; + constexpr operator std::string_view() const { + return std::string_view{this->contents_.data(), Size - 1}; + }; + constexpr auto c_str() const -> const char* { + return this->contents_.data(); + }; + constexpr std::size_t size() const { + return Size - 1; + }; + constexpr const char* data() const { + return this->contents_.begin(); + }; + + constexpr operator char&() const { + return this->contents_.data(); + }; + + friend constexpr bool operator==(const ConstexprString& string, const char* other) { + return std::string_view(string) == std::string_view(other); + }; + template + friend constexpr auto& operator<<(Stream& stream, const ConstexprString& string) { + stream << static_cast(string); + return stream; + }; + + constexpr ConstexprString(const char (&str)[Size]) noexcept { + std::copy_n(str, Size, std::begin(this->contents_)); + }; + constexpr ConstexprString(std::array data) noexcept : contents_(data) {}; + template + constexpr ConstexprString operator+(const ConstexprString& other) const noexcept { + return + [&](std::index_sequence, std::index_sequence){ + return ConstexprString({this->contents_[I]..., other.contents_[I2]...}); + }(std::make_index_sequence(), std::make_index_sequence()); + }; + template + constexpr ConstexprString operator+(const char(&str)[OtherSize]) const noexcept { + return *this + ConstexprString{str}; + }; + std::array contents_; +}; +template +ConstexprString(char const (&)[n]) -> ConstexprString; +} // namespace utils +USERVER_NAMESPACE_END +#endif diff --git a/universal/include/userver/utils/impl/type_list.hpp b/universal/include/userver/utils/impl/type_list.hpp new file mode 100644 index 000000000000..dc9e13a9e95a --- /dev/null +++ b/universal/include/userver/utils/impl/type_list.hpp @@ -0,0 +1,58 @@ +#pragma once +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace utils::impl { + +template +struct TypeList {}; + + +template +consteval auto Size(TypeList) { + return sizeof...(Ts); +} + +template