diff --git a/universal/include/userver/formats/common/meta.hpp b/universal/include/userver/formats/common/meta.hpp index a63cb7653f08..f1af6d610876 100644 --- a/universal/include/userver/formats/common/meta.hpp +++ b/universal/include/userver/formats/common/meta.hpp @@ -32,9 +32,14 @@ template using HasConvert = decltype(Convert(std::declval(), parse::To{})); +template +using HasTryParse = + decltype(TryParse(std::declval(), parse::To{})); + template using IsFormatValue = typename Value::ParseException; + template constexpr inline bool kHasParse = meta::kIsDetected; @@ -44,6 +49,9 @@ constexpr inline bool kHasSerialize = meta::kIsDetected; template constexpr inline bool kHasConvert = meta::kIsDetected; +template +constexpr inline bool kHasTryParse = meta::kIsDetected; + } // namespace impl /// Used in `Parse` overloads that are templated on `Value`, avoids clashing 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..20ea92db7ac9 --- /dev/null +++ b/universal/include/userver/formats/parse/try_parse.hpp @@ -0,0 +1,97 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +USERVER_NAMESPACE_BEGIN + +namespace formats::parse { + +namespace impl { + +template +constexpr inline bool Is(Value&& value) { + if constexpr(std::is_same_v) { + return value.IsBool(); + } else if constexpr(meta::kIsInteger) { + return (std::is_unsigned_v && sizeof(T) == sizeof(std::uint64_t)) ? value.IsUInt64() : value.IsInt64(); + } else if constexpr(std::is_convertible_v) { + return value.IsString(); + } else if constexpr(std::is_convertible_v) { + return value.IsDouble(); + } else if constexpr(meta::kIsRange) { + return value.IsArray(); + } else { + return value.IsObject(); + } +} +bool CheckInBounds(const auto& x, const auto& min, const auto& max) { + if (x < min || x > max) { + return false; + }; + return true; +}; +inline constexpr utils::impl::TypeList kBaseTypes; + +} // namespace impl + + +template +constexpr inline std::enable_if_t< + utils::impl::AnyOf(utils::impl::IsSameCarried(), impl::kBaseTypes) || + meta::kIsInteger, std::optional> +TryParse(Value&& value, userver::formats::parse::To) { + if(!impl::Is(value)) { + return std::nullopt; + } + return value.template As(); +} + +template +constexpr inline std::optional> TryParse(Value&& value, userver::formats::parse::To>) { + return TryParse(std::forward(value), userver::formats::parse::To{}); +} +template +constexpr inline std::optional TryParse(Value&& value, userver::formats::parse::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; +}; + +template +constexpr inline std::enable_if_t && !meta::kIsMap && + !std::is_same_v && + !utils::impl::AnyOf(utils::impl::IsConvertableCarried(), impl::kBaseTypes) && + !std::is_convertible_v< + T&, utils::impl::strong_typedef::StrongTypedefTag&>, + std::optional> +TryParse(Value&& from, To) { + T response; + auto inserter = std::inserter(response, response.end()); + using ValueType = meta::RangeValueType; + for(const auto& item : from) { + auto insert = TryParse(item, userver::formats::parse::To{}); + if(!insert) { + return std::nullopt; + }; + *inserter = *insert; + ++inserter; + }; + return response; +} + + + +} // namespace formats::parse + +USERVER_NAMESPACE_END diff --git a/universal/include/userver/formats/universal/common_checks.hpp b/universal/include/userver/formats/universal/common_checks.hpp new file mode 100644 index 000000000000..a1701ea57488 --- /dev/null +++ b/universal/include/userver/formats/universal/common_checks.hpp @@ -0,0 +1,204 @@ + +#pragma once +#include +#include +#include +#include + +USERVER_NAMESPACE_BEGIN +namespace formats::universal::impl { + + +template +struct Min { + static constexpr auto kValue = Value; +}; + +template +struct Max { + static constexpr auto kValue = Value; +}; + +template +struct MaxItems { + static constexpr auto kValue = Value; +}; + +template +struct MinItems { + static constexpr auto kValue = Value; +}; + +template +struct Default { + static constexpr auto kValue = Value; +}; + +template +struct Pattern { + static constexpr auto kValue = Regex; +}; + +template +struct Items { + static constexpr auto kChecks = std::make_tuple(Checks...); +}; + +struct Additional {}; + + +template +constexpr inline std::enable_if_t, bool> +Check(const Field& field, MaxItems) { + return field.size() <= Value; +}; + +template +constexpr inline std::enable_if_t, bool> +Check(const Field& field, MinItems) { + return field.size() >= Value; +}; + +template +constexpr inline std::enable_if_t, bool> +Check(const Field& field, Items) { + for(const auto& element : field) { + if(!(Check(element, Checks) && ...)) { + return false; + }; + }; + return true; +}; + + +template +constexpr inline std::enable_if_t, bool> +Check(const Field& field, Max) noexcept { + return Value >= field; +}; + +template +constexpr inline std::enable_if_t, bool> +Check(const Field& field, Min) noexcept { + return field >= Value; +}; + +template +constexpr inline auto Check(const Field&, Default) noexcept { + return true; +}; + +template +static const userver::utils::regex kRegex(Regex); + +template +constexpr inline auto Check(const std::string& field, Pattern) noexcept { + return utils::regex_match(field, kRegex); +}; + +template +constexpr inline auto Check(const std::optional&, Default) noexcept { + return true; +}; + +template +constexpr inline +std::enable_if_t, bool> +Check(const Field&, Additional) noexcept { + return true; +}; + +template +constexpr inline auto Check(const std::vector& field, Max) noexcept { + for(const auto& element : field) { + if(!(Value <= field)) { + return false; + }; + }; + return true; +}; + +template +constexpr inline auto Check(const std::vector& field, Min) noexcept { + for(const auto& element : field) { + if(!(Value >= field)) { + return false; + }; + }; + return true; +}; + +template +constexpr inline auto Check(const std::map& field, Max) noexcept { + return Value >= field.size(); +}; + +template +constexpr inline auto Check(const std::unordered_map& field, Max) noexcept { + return Value >= field.size(); +}; + +template +constexpr inline bool Check(const std::optional& field, CheckT check) noexcept { + if(field.has_value()) { + return Check(*field, check); + }; + return true; +}; + +template +constexpr inline auto ErrorMessage(const std::unordered_map& field, Max) { + return fmt::format("Error with field {0} Map size: {1} Maximum Size: {2}", boost::pfr::get_name(), field.size(), Maximum); +}; + +template typename Check, auto Value> +constexpr inline auto ErrorMessage(const Field& field, Check) { + return fmt::format("Error with field {0} Field value: {1} Check Value: {2}", boost::pfr::get_name(), field, Value); +}; + + +template +constexpr inline auto ErrorMessage(const std::unordered_map&, Additional) { + return "Error"; +}; + +template +constexpr inline auto ErrorMessage(const Field&, Check) { + return "Error"; +}; + +template typename Check, auto Value> +constexpr inline auto ErrorMessage(const std::optional& field, Check check) { + return ErrorMessage(*field, check); +}; + +} // namespace formats::universal::impl +namespace formats::universal { +template +inline constexpr impl::Min Min; + +template +inline constexpr impl::Max Max; + +template +inline constexpr impl::Default Default; + +template +inline constexpr impl::Items Items; + + +inline constexpr impl::Additional Additional; + +template +inline constexpr impl::Pattern Pattern; + +template +inline constexpr impl::MaxItems MaxItems; + +template +inline constexpr impl::MinItems MinItems; + +} // namespace formats::universal + +USERVER_NAMESPACE_END + diff --git a/universal/include/userver/formats/universal/universal.hpp b/universal/include/userver/formats/universal/universal.hpp new file mode 100644 index 000000000000..84b31a9553ae --- /dev/null +++ b/universal/include/userver/formats/universal/universal.hpp @@ -0,0 +1,371 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +USERVER_NAMESPACE_BEGIN +namespace formats::universal { + +namespace impl { + +struct Disabled {}; + +struct Additional; + +template +struct Default; + +} //namespace impl + +template +struct Configurator { + using kParams = utils::impl::TypeList; +}; + +template +inline static constexpr auto kSerialization = impl::Disabled{}; + +template +inline static constexpr auto kDeserialization = kSerialization; + +namespace impl { + +template +consteval auto TransformIfEqual(const T& obj, const F& f) { + if constexpr(Needed == Value) { + return f(obj); + } else { + return obj; + }; +}; + +template +struct FieldParametries { + static constexpr auto kIndex = I; + using kType = T; + using kFieldType = std::remove_cvref_t(std::declval()))>; +}; + +template +constexpr inline auto Check(const Field&, Disabled) noexcept { + return true; +}; + +struct NeverToBeUsed; +template +struct Error : public std::false_type {}; + +template <> // https://eel.is/c++draft/temp#res.general-6.1 +struct Error : public std::true_type {}; + +namespace exam { + +template +constexpr inline bool Check(const Field&, CheckT) { + static_assert(Error::value, "Check Not Found " + "Probably you forgot to include the " + ""); + return false; +}; + +template +constexpr inline const char* ErrorMessage(Field&&, CheckT) { + static_assert(Error::value, "Error Message Not Found " + "Probably you forgot to include the " + ""); + return ""; +}; + +template +constexpr inline auto RunCheckFor(const Builder&, Field&&, CheckT) { + static_assert(Error::value, "Check Not Found " + "Probably you forgot to include the " + ""); +}; + +template +constexpr inline auto RunParseCheckFor(Value&&, const Field& field, CheckT check) { + using exam::ErrorMessage; + if(!Check(field, check)) { + throw std::runtime_error(ErrorMessage(field, check)); + }; +}; + +template +constexpr inline auto RunWrite(Builder& builder, Field&& field) { + builder[std::string(boost::pfr::get_name())] = std::forward(field); +}; +template +constexpr inline auto RunWrite(Builder& builder, const std::optional& field) { + if(field) { + return RunWrite(builder, *field); + }; +}; + +template +constexpr inline Field Read(Value&& value, parse::To) { + return value[boost::pfr::get_name()].template As(); +}; + +template +constexpr inline +std::enable_if_t(), utils::impl::TypeList{}), std::unordered_map> +Read(const From& value, parse::To>) { + constexpr auto names = boost::pfr::names_as_array(); + std::unordered_map result; + for(const auto& [name, value] : common::Items(value)) { + if(std::find(names.begin(), names.end(), name) == names.end()) { + result[name] = value.template As(); + }; + }; + return result; +}; + +template +constexpr inline +std::enable_if_t(), utils::impl::TypeList{}), std::optional>> +Read(const From& value, parse::To>>) { + return Read(value, parse::To>{}); +}; + +template +struct IsDefault : public std::false_type {}; + +template +struct IsDefault> : public std::true_type {}; + +template +constexpr inline +std::enable_if_t(utils::impl::TypeList{}), std::optional> +Read(Value&& value, parse::To>) { + using parse::TryParse; + static_assert(common::impl::kHasTryParse, "Not Found Try Parse"); + return TryParse(value[boost::pfr::get_name()], parse::To{}); +}; +template +constexpr inline +std::enable_if_t(utils::impl::TypeList{}), std::optional> +Read(Value&& value, parse::To>) { + using parse::TryParse; + static_assert(common::impl::kHasTryParse, "Not Found Try Parse"); + auto response = TryParse(value[boost::pfr::get_name()], parse::To{}); + if(!response) { + return [](Ts..., Default, Tss...){ + return DefaultValue; + }(Params{}...); + }; + return response; +}; + +template +constexpr inline std::optional Read(Value&& value, parse::To>>) { + return Read(std::forward(value), parse::To>{}); +}; + + +template +constexpr inline auto RunCheckFor(Builder& builder, const std::optional& field, Default) { + if(!field.has_value()) { + builder[std::string(boost::pfr::get_name())] = Value; + }; +}; + + +template +constexpr inline std::enable_if_t(), utils::impl::TypeList{}), void> +RunWrite(Builder& builder, const std::unordered_map& field) { + for(const auto& element : field) { + builder[element.first] = element.second; + }; +}; + +template +constexpr inline auto RunCheckFor(Builder&, Field&&, Disabled) noexcept {}; + +template +constexpr inline auto RunCheckFor(Builder&, const std::optional&, Disabled) noexcept {}; + +template +constexpr inline auto RunCheckFor(Builder&, Field&& field, CheckT check) { + if(!Check(field, check)) { + throw std::runtime_error(ErrorMessage(std::forward(field), check)); + }; +}; + +} // namespace exam +template +constexpr inline auto UniversalSerializeField( + FieldParametries + ,Builder& builder + ,const T& obj) { + const auto& value = boost::pfr::get(obj); + using exam::RunCheckFor; + using exam::RunWrite; + (RunCheckFor(builder, value, Params{}), ...); + RunWrite(builder, value); +}; + +template +constexpr inline auto UniversalParseField( + FieldParametries + ,Format&& from) { + using exam::Read; + using exam::RunParseCheckFor; + using FieldType = std::remove_cvref_t(std::declval()))>; + auto value = Read(from, userver::formats::parse::To{}); + (RunParseCheckFor(from, value, Params{}), ...); + return value; +}; + + +template +constexpr inline std::optional(std::declval()))>> +UniversalTryParseField( + FieldParametries + ,Format&& from) noexcept { + using FieldType = std::remove_cvref_t(std::declval()))>; + using exam::Check; + using exam::Read; + + + auto val = Read(from, userver::formats::parse::To>{}); + if((Check(val, Params{}) && ...)) { + return val; + }; + return std::nullopt; +}; + +} // namespace impl + +template +class SerializationConfig { + public: + static consteval auto Create() { + return [](std::index_sequence){ + return SerializationConfig...>{}; + }(std::make_index_sequence>()); + }; + + + template + consteval auto With(ConfigElements...) const { + return SerializationConfig::AddParamsTo(); + }; + + template + consteval auto With(ConfigElements... config) const { + constexpr auto names = boost::pfr::names_as_array(); + constexpr std::size_t index = std::find(names.begin(), names.end(), field) - names.begin(); + return With(config...); + }; + + template + consteval auto With(Configurator) const { + return With(ConfigElements...); + }; + + template + consteval auto FromStruct() { + if constexpr(I == boost::pfr::tuple_size_v) { + return SerializationConfig(); + } else { + constexpr auto fieldName = boost::pfr::get_name(); + constexpr auto names = boost::pfr::names_as_array(); + constexpr std::size_t Index = std::find(names.begin(), names.end(), fieldName) - names.begin(); + return SerializationConfig().With(boost::pfr::get(From{})).template FromStruct(); + }; + }; + + template + consteval auto FromStruct() { + return FromStruct(); + }; + + constexpr SerializationConfig() noexcept { + static_assert(sizeof...(Params) == boost::pfr::tuple_size_v, "Use Create"); + }; + private: + template + static consteval auto AddParamsTo() { + return [](std::index_sequence){ + return SerializationConfig(Params{}, + [](impl::FieldParametries){ + return impl::FieldParametries(); + }) + )...>(); + }(std::make_index_sequence()); + }; + + +}; + + +} // namespace formats::universal +namespace formats::parse { + +template +constexpr inline +std::enable_if_t>), const universal::impl::Disabled>, T> +Parse(Format&& from, + To) { + using Config = std::remove_const_t>)>; + using Type = std::remove_cvref_t; + return [from = std::forward(from)](universal::SerializationConfig){ + return T{universal::impl::UniversalParseField(Params{}, std::forward(from))...}; + }(Config{}); +}; + +template +constexpr inline +std::enable_if_t>), const universal::impl::Disabled>, std::optional> +TryParse(Format&& from, + To) { + using Config = std::remove_const_t>)>; + using Type = std::remove_cvref_t; + return [&](universal::SerializationConfig) -> std::optional { + auto fields = std::make_tuple(universal::impl::UniversalTryParseField(Params{}, from)...); + constexpr auto fieldsCount = boost::pfr::tuple_size_v; + if([&](std::index_sequence){ + return (std::get(fields) && ...); + }(std::make_index_sequence())) { + return [&](std::index_sequence){ + return T{*std::get(fields)...}; + }(std::make_index_sequence()); + }; + return std::nullopt; + }(Config{}); +}; + +} // namespace formats::parse + +namespace formats::serialize { + +template +inline constexpr +std::enable_if_t>), const universal::impl::Disabled>, Value> +Serialize(T&& obj, + serialize::To) { + using Config = std::remove_const_t>)>; + using Type = std::remove_cvref_t; + return [&] + (universal::SerializationConfig){ + typename Value::Builder builder; + (universal::impl::UniversalSerializeField(Params{}, builder, obj), ...); + return builder.ExtractValue(); + }(Config{}); +}; + +} // namespace formats::serialize + +USERVER_NAMESPACE_END + diff --git a/universal/include/userver/utils/constexpr_string.hpp b/universal/include/userver/utils/constexpr_string.hpp new file mode 100644 index 000000000000..81b946fa33e2 --- /dev/null +++ b/universal/include/userver/utils/constexpr_string.hpp @@ -0,0 +1,59 @@ +#pragma once +#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 + 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..eaf71d031211 --- /dev/null +++ b/universal/include/userver/utils/impl/type_list.hpp @@ -0,0 +1,46 @@ +#pragma once +#include + + +USERVER_NAMESPACE_BEGIN + +namespace utils::impl { + +template +struct TypeList {}; + + +template +consteval auto size(TypeList) { + return sizeof...(Ts); +} + + +template