From b434b32fc8a1f8db0be6a736741f2b689729a010 Mon Sep 17 00:00:00 2001 From: sabudilovskiy Date: Tue, 1 Aug 2023 19:35:22 +0000 Subject: [PATCH] - --- .vscode/launch.json | 16 +++ src/openapi/base/array_property.hpp | 20 +++ src/openapi/base/array_traits.hpp | 121 +++++++++++++++++ src/openapi/base/extended_object_property.hpp | 21 +++ src/openapi/base/named_traits.hpp | 78 +++++++++++ src/openapi/base/object_field_names.hpp | 1 + src/openapi/base/object_property.hpp | 20 +++ src/openapi/base/object_traits.hpp | 92 +++++++++++++ src/openapi/base/optional_property.hpp | 16 +++ src/openapi/base/preferences.hpp | 47 +++++++ src/openapi/base/property_base.hpp | 56 ++++++++ src/openapi/base/reflective_property.hpp | 38 ++++++ src/openapi/base/string_property.hpp | 19 +++ src/openapi/base/string_traits.hpp | 68 ++++++++++ src/openapi/json/parse/array_property.hpp | 67 ++++++++++ src/openapi/json/parse/object_property.hpp | 81 +++++++++++ src/openapi/json/parse/string_property.hpp | 42 ++++++ src/openapi/types/array_type.hpp | 123 +++++++++++++++++ src/openapi/types/object_type.hpp | 126 ++++++++++++++++++ src/openapi/types/string_type.hpp | 108 +++++++++++++++ src/utils/constexpr_optional.hpp | 93 +++++++++++++ utests/openapi/json/parse/basic_array.cpp | 108 +++++++++++++++ utests/openapi/json/parse/basic_object.cpp | 86 ++++++++++++ utests/openapi/json/parse/basic_string.cpp | 67 ++++++++++ 24 files changed, 1514 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 src/openapi/base/array_property.hpp create mode 100644 src/openapi/base/array_traits.hpp create mode 100644 src/openapi/base/extended_object_property.hpp create mode 100644 src/openapi/base/named_traits.hpp create mode 100644 src/openapi/base/object_field_names.hpp create mode 100644 src/openapi/base/object_property.hpp create mode 100644 src/openapi/base/object_traits.hpp create mode 100644 src/openapi/base/optional_property.hpp create mode 100644 src/openapi/base/preferences.hpp create mode 100644 src/openapi/base/property_base.hpp create mode 100644 src/openapi/base/reflective_property.hpp create mode 100644 src/openapi/base/string_property.hpp create mode 100644 src/openapi/base/string_traits.hpp create mode 100644 src/openapi/json/parse/array_property.hpp create mode 100644 src/openapi/json/parse/object_property.hpp create mode 100644 src/openapi/json/parse/string_property.hpp create mode 100644 src/openapi/types/array_type.hpp create mode 100644 src/openapi/types/object_type.hpp create mode 100644 src/openapi/types/string_type.hpp create mode 100644 src/utils/constexpr_optional.hpp create mode 100644 utests/openapi/json/parse/basic_array.cpp create mode 100644 utests/openapi/json/parse/basic_object.cpp create mode 100644 utests/openapi/json/parse/basic_string.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..53878a7a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Используйте IntelliSense, чтобы узнать о возможных атрибутах. + // Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов. + // Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/", + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/src/openapi/base/array_property.hpp b/src/openapi/base/array_property.hpp new file mode 100644 index 00000000..0500400a --- /dev/null +++ b/src/openapi/base/array_property.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include "property_base.hpp" + +namespace timetable_vsu_backend::openapi +{ +template +struct ArrayProperty : public PropertyBase, Traits> +{ +}; + +template +struct Property, Traits> : public ArrayProperty +{ +}; + +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/base/array_traits.hpp b/src/openapi/base/array_traits.hpp new file mode 100644 index 00000000..edb0aaac --- /dev/null +++ b/src/openapi/base/array_traits.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include "array_property.hpp" +#include "named_traits.hpp" +#include "preferences.hpp" +#include "utils/constexpr_optional.hpp" +#include "utils/constexpr_string.hpp" +namespace timetable_vsu_backend::openapi +{ +namespace checks +{ +template +concept HasMin = requires +{ + { + T::kMin + } + ->std::convertible_to>; +}; + +template +concept HasMax = requires +{ + { + T::kMax + } + ->std::convertible_to>; +}; + +template +concept HasUniqueItems = requires +{ + { + T::kUniqueItems + } + ->std::convertible_to>; +}; + +template +concept HasNotMin = !HasMin; + +template +concept HasNotMax = !HasMax; + +template +concept HasNotUniqueItems = !HasUniqueItems; +} // namespace checks + +namespace detail +{ +template +constexpr auto _getMin() +{ + return T::kMin; +} + +template +constexpr auto _getMin() +{ + return utils::ConstexprOptional{utils::kNull}; +} + +template +constexpr auto _getMax() +{ + return T::kMax; +} + +template +constexpr auto _getMax() +{ + return utils::ConstexprOptional{utils::kNull}; +} + +template +constexpr auto _getUniqueItems() +{ + return T::kUniqueItems; +} + +template +constexpr auto _getUniqueItems() +{ + return utils::ConstexprOptional{utils::kNull}; +} +} // namespace detail + +namespace traits +{ +template +constexpr utils::ConstexprOptional GetMin() +{ + return detail::_getMin(); +} +template +constexpr utils::ConstexprOptional GetMax() +{ + return detail::_getMax(); +} +template +constexpr utils::ConstexprOptional GetUniqueItems() +{ + return detail::_getUniqueItems(); +} + +template +struct ArrayHelperTraits : NamedHelperTraits +{ + static constexpr utils::ConstexprOptional min = + traits::GetMin(); + static constexpr utils::ConstexprOptional max = + traits::GetMax(); + static constexpr utils::ConstexprOptional unique_items = + traits::GetUniqueItems(); + constexpr ArrayHelperTraits() + { + } +}; + +} // namespace traits +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/base/extended_object_property.hpp b/src/openapi/base/extended_object_property.hpp new file mode 100644 index 00000000..d065ebe8 --- /dev/null +++ b/src/openapi/base/extended_object_property.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include "property_base.hpp" +#include "userver/formats/json/value.hpp" + +namespace timetable_vsu_backend::openapi +{ +template +struct ExtendedObjectProperty : public PropertyBase +{ + userver::formats::json::Value additional_properties{}; + const userver::formats::json::Value& GetAdditionalProperties() + { + return additional_properties; + } +}; + +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/base/named_traits.hpp b/src/openapi/base/named_traits.hpp new file mode 100644 index 00000000..6973c832 --- /dev/null +++ b/src/openapi/base/named_traits.hpp @@ -0,0 +1,78 @@ +#pragma once +#include +#include + +#include "property_base.hpp" +#include "utils/constexpr_optional.hpp" +#include "utils/constexpr_string.hpp" + +namespace timetable_vsu_backend::openapi +{ +template +struct NamedTraits +{ + static constexpr auto kName = Name; +}; + +namespace checks +{ +template +concept HasName = requires +{ + { + decltype(T::kName)::kSize + } + ->std::convertible_to; + requires std::is_same_v, + std::remove_cv_t>; + { + T::kName + } + ->std::convertible_to>; +}; + +template +concept HasNotName = !HasName; +} // namespace checks + +namespace details +{ +template +constexpr auto _getName() +{ + return T::kName; +} + +template +constexpr auto _getName() +{ + using Type = utils::ConstexprString<1>; + return Type{""}; +} +} // namespace details + + +struct ConstexprSetString{ + std::array test; +}; + +consteval ConstexprSetString foo (){ + return {}; +} + +namespace traits +{ +// returns ConstexprOptional +template +constexpr auto GetName() +{ + return details::_getName(); +} + +template +struct NamedHelperTraits +{ + static constexpr auto name = traits::GetName(); +}; +} // namespace traits +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/base/object_field_names.hpp b/src/openapi/base/object_field_names.hpp new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/src/openapi/base/object_field_names.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/src/openapi/base/object_property.hpp b/src/openapi/base/object_property.hpp new file mode 100644 index 00000000..50e91c23 --- /dev/null +++ b/src/openapi/base/object_property.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include "property_base.hpp" + +namespace timetable_vsu_backend::openapi +{ +template +struct ObjectProperty : public PropertyBase +{ +}; + +template +struct Property : public ObjectProperty +{ +}; + +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/base/object_traits.hpp b/src/openapi/base/object_traits.hpp new file mode 100644 index 00000000..7dd90125 --- /dev/null +++ b/src/openapi/base/object_traits.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include "extended_object_property.hpp" +#include "named_traits.hpp" +#include "object_property.hpp" +#include "preferences.hpp" +#include "utils/constexpr_optional.hpp" +#include "utils/constexpr_string.hpp" +namespace timetable_vsu_backend::openapi +{ +namespace checks +{ +template +concept HasAdditionalProperties = requires +{ + { + T::kAdditionalProperties + } + ->std::convertible_to>; +}; + +template +concept HasUseRoot = requires +{ + { + T::kUseRoot + } + ->std::convertible_to>; +}; + +} // namespace checks + +namespace detail +{ +template +requires checks::HasAdditionalProperties constexpr utils::ConstexprOptional< + bool> +_getAdditionalProperties() +{ + return T::kAdditionalProperties; +} + +template +requires(!checks::HasAdditionalProperties< + T>) constexpr utils::ConstexprOptional _getAdditionalProperties() +{ + return utils::kNull; +} + +template +requires checks::HasUseRoot constexpr utils::ConstexprOptional +_getUseRoot() +{ + return T::kUseRoot; +} + +template +requires(!checks::HasUseRoot) constexpr utils::ConstexprOptional< + bool> _getUseRoot() +{ + return utils::kNull; +} + +} // namespace detail + +namespace traits +{ +template +constexpr utils::ConstexprOptional GetAdditionalProperties() +{ + return detail::_getAdditionalProperties(); +} +template +constexpr utils::ConstexprOptional GetUseRoot() +{ + return detail::_getUseRoot(); +} + +template +struct ObjectHelperTraits : NamedHelperTraits +{ + static constexpr utils::ConstexprOptional use_root = + traits::GetUseRoot(); + static constexpr utils::ConstexprOptional additional_properties = + traits::GetAdditionalProperties(); + constexpr ObjectHelperTraits() + { + } +}; + +} // namespace traits +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/base/optional_property.hpp b/src/openapi/base/optional_property.hpp new file mode 100644 index 00000000..124148c7 --- /dev/null +++ b/src/openapi/base/optional_property.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "property_base.hpp" + +namespace timetable_vsu_backend::openapi +{ +template +struct OptionalProperty : public PropertyBase, Traits> +{ +}; + +template +struct Property, Traits> : public OptionalProperty +{ +}; + +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/base/preferences.hpp b/src/openapi/base/preferences.hpp new file mode 100644 index 00000000..6c4064f2 --- /dev/null +++ b/src/openapi/base/preferences.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "utils/constexpr_string.hpp" + +namespace timetable_vsu_backend::openapi::preferences +{ +template +struct value_holder +{ + static auto constexpr kValue = value; +}; + +template +struct Min : value_holder +{ +}; + +template +struct Max : value_holder +{ +}; + +template +struct UniqueItems : value_holder +{ +}; + +template +struct Name : value_holder +{ +}; + +template +struct Pattern : value_holder +{ +}; + +template +struct AdditionalProperties : value_holder +{ +}; + +template +struct UseRoot : value_holder +{ +}; + +} // namespace timetable_vsu_backend::openapi::preferences diff --git a/src/openapi/base/property_base.hpp b/src/openapi/base/property_base.hpp new file mode 100644 index 00000000..710f9eb2 --- /dev/null +++ b/src/openapi/base/property_base.hpp @@ -0,0 +1,56 @@ +#pragma once +#include +#include +#include +#include + +namespace timetable_vsu_backend::openapi +{ +struct EmptyTraits +{ +}; + +struct Yes +{ +}; + +namespace checks +{ +template +concept IsReflective = requires +{ + requires std::is_class_v; + typename T::Reflective; + requires std::same_as; +}; +}; // namespace checks + +template +struct PropertyBase +{ + using value_type = T; + using traits = Traits; + + template + value_type& operator=(Arg&& arg) + { + value = std::forward(arg); + return value; + } + value_type& operator()() + { + return value; + } + const value_type& operator()() const + { + return value; + } + value_type value; +}; + +template +struct Property +{ +}; + +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/base/reflective_property.hpp b/src/openapi/base/reflective_property.hpp new file mode 100644 index 00000000..50e1afeb --- /dev/null +++ b/src/openapi/base/reflective_property.hpp @@ -0,0 +1,38 @@ +#pragma once +#include + +#include "property_base.hpp" + +namespace timetable_vsu_backend::openapi +{ +template +concept IsReflective = requires +{ + requires std::is_class_v; + typename T::Enable; + requires std::is_same_v; +}; + +template +struct ReflectiveProperty : public PropertyBase +{ +}; + +template +struct Property : public ReflectiveProperty +{ +}; + +template +struct OptionalReflectiveProperty + : public PropertyBase, Traits> +{ +}; + +template +struct Property, Traits> + : public OptionalReflectiveProperty +{ +}; + +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/base/string_property.hpp b/src/openapi/base/string_property.hpp new file mode 100644 index 00000000..9ba1af15 --- /dev/null +++ b/src/openapi/base/string_property.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "property_base.hpp" + +namespace timetable_vsu_backend::openapi +{ +template +struct StringProperty : public PropertyBase +{ +}; + +template +struct Property : public StringProperty +{ +}; + +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/base/string_traits.hpp b/src/openapi/base/string_traits.hpp new file mode 100644 index 00000000..f9a84ed7 --- /dev/null +++ b/src/openapi/base/string_traits.hpp @@ -0,0 +1,68 @@ +#pragma once +#include +#include +#include + +#include "array_property.hpp" +#include "named_traits.hpp" +#include "preferences.hpp" +#include "utils/constexpr_optional.hpp" +#include "utils/constexpr_string.hpp" +namespace timetable_vsu_backend::openapi +{ +namespace checks +{ +template +concept HasPattern = requires +{ + { + decltype(T::kPattern)::kSize + } + ->std::convertible_to; + requires std::is_same_v< + utils::ConstexprString, + std::remove_cv_t>; + { + T::kPattern + } + ->std::convertible_to>; +}; +} // namespace checks + +namespace detail +{ +template +requires checks::HasPattern constexpr auto _getPattern() +{ + return T::kPattern; +} + +template +requires(!checks::HasPattern) constexpr auto _getPattern() +{ + using Type = utils::ConstexprString<1>; + return Type{""}; +} +} // namespace detail + +namespace traits +{ +template +constexpr auto GetPattern() +{ + return detail::_getPattern(); +} + +template +struct StringHelperTraits +{ + static constexpr utils::ConstexprString name = traits::GetName(); + static constexpr utils::ConstexprString pattern = + traits::GetPattern(); + constexpr StringHelperTraits() + { + } +}; + +} // namespace traits +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/json/parse/array_property.hpp b/src/openapi/json/parse/array_property.hpp new file mode 100644 index 00000000..f0f38762 --- /dev/null +++ b/src/openapi/json/parse/array_property.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +#include "openapi/base/array_property.hpp" +#include "openapi/base/array_traits.hpp" +#include "openapi/base/named_traits.hpp" +#include "userver/formats/json/value.hpp" +#include "userver/formats/parse/to.hpp" +#include "userver/logging/log.hpp" +#include "utils/constexpr_string.hpp" + +namespace userver::formats::parse +{ +template +timetable_vsu_backend::openapi::ArrayProperty Parse( + const json::Value& item, + To>) +{ + using namespace timetable_vsu_backend::openapi; + + constexpr traits::ArrayHelperTraits traits; + + std::vector result = item.As>(); + + if constexpr (traits.min.HasValue()) + { + if (result.size() < traits.min.value()) + { + throw std::runtime_error( + fmt::format("Field has elements less than " + "allowed, current: {}, min: {}", + result.size(), traits.min.value())); + } + } + if constexpr (traits.max.HasValue()) + { + if (result.size() > traits.max.value()) + { + throw std::runtime_error( + fmt::format("Field has elements more than " + "allowed, current: {}, max: {}", + result.size(), traits.max.value())); + } + } + if constexpr (traits.unique_items.value_or(false)) + { + std::unordered_map map; + size_t index = 0; + for (auto& elem : result) + { + auto [it, inserted] = map.try_emplace(elem, index); + if (!inserted) + { + throw std::runtime_error( + fmt::format("Field has equals elements, " + "element {} and {} are equal", + it->second + 1, index + 1)); + } + index++; + } + } + return {std::move(result)}; +} + +} // namespace userver::formats::parse diff --git a/src/openapi/json/parse/object_property.hpp b/src/openapi/json/parse/object_property.hpp new file mode 100644 index 00000000..de0e2d01 --- /dev/null +++ b/src/openapi/json/parse/object_property.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "boost/pfr.hpp" +#include "boost/pfr/core.hpp" +#include "openapi/base/extended_object_property.hpp" +#include "openapi/base/named_traits.hpp" +#include "openapi/base/object_property.hpp" +#include "openapi/base/object_traits.hpp" +#include "userver/formats/json/value.hpp" +#include "userver/formats/parse/to.hpp" +#include "userver/logging/log.hpp" +#include "utils/constexpr_string.hpp" + +namespace userver::formats::parse +{ +template +timetable_vsu_backend::openapi::ObjectProperty Parse( + const json::Value& item, + To>) +{ + using namespace timetable_vsu_backend::openapi; + + + + // constexpr traits::ObjectHelperTraits traits; + T result; + auto matcher_common_type = [&item](F& field) { + constexpr auto name = timetable_vsu_backend::openapi::traits::GetName< + typename F::traits>(); + static_assert(!name.empty(), "Common field must have name"); + field = item[name.AsStringView()].template As(); + }; + auto matcher_object = [&item]( + ObjectProperty& field) { + using R = ObjectProperty; + if constexpr (traits::GetUseRoot().value_or(false)) + { + field = item.As(); + } + else + { + constexpr auto name = + timetable_vsu_backend::openapi::traits::GetName(); + static_assert(!name.empty(), + "Object field without use_root must have name"); + field = item[name.AsStringView()].template As(); + } + }; + auto matcher_extended_object = + [&item]( + ExtendedObjectProperty& field) { + using R = ExtendedObjectProperty; + if constexpr (traits::GetUseRoot().value_or(false)) + { + field = item.As(); + } + else + { + constexpr auto name = + timetable_vsu_backend::openapi::traits::GetName(); + static_assert(!name.empty(), + "Object field without use_root must have name"); + field = item[name.AsStringView()].template As(); + } + }; + auto matcher = utils::Overloaded{std::move(matcher_common_type), + std::move(matcher_object), + std::move(matcher_extended_object)}; + + boost::pfr::for_each_field(result, matcher); + + return {std::move(result)}; +} + +} // namespace userver::formats::parse diff --git a/src/openapi/json/parse/string_property.hpp b/src/openapi/json/parse/string_property.hpp new file mode 100644 index 00000000..05aa5e97 --- /dev/null +++ b/src/openapi/json/parse/string_property.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include + +#include "openapi/base/named_traits.hpp" +#include "openapi/base/string_property.hpp" +#include "openapi/base/string_traits.hpp" +#include "userver/formats/json/value.hpp" +#include "userver/formats/parse/to.hpp" +#include "userver/logging/log.hpp" +#include "utils/constexpr_string.hpp" + +namespace userver::formats::parse +{ +template +timetable_vsu_backend::openapi::StringProperty Parse( + const json::Value& item, + To>) +{ + using namespace timetable_vsu_backend::openapi; + + constexpr traits::StringHelperTraits traits; + + auto result = item.As(); + + if constexpr (!traits.pattern.empty()) + { + std::regex reg(traits.pattern.data()); + if (!std::regex_match(result, reg)) + { + throw std::runtime_error( + fmt::format("Field doesnt satisfy pattern: {}, value: {}", + traits.pattern, result)); + } + } + return {std::move(result)}; +} + +} // namespace userver::formats::parse diff --git a/src/openapi/types/array_type.hpp b/src/openapi/types/array_type.hpp new file mode 100644 index 00000000..ec0f7540 --- /dev/null +++ b/src/openapi/types/array_type.hpp @@ -0,0 +1,123 @@ +#pragma once +#include + +#include "openapi/base/array_traits.hpp" + +namespace timetable_vsu_backend::openapi +{ +namespace detail +{ +template Min = utils::kNull, + utils::ConstexprOptional Max = utils::kNull, + utils::ConstexprOptional UniqueItems = utils::kNull> +struct ArrayTraits : NamedTraits +{ + static constexpr auto kMin = Min; + static constexpr auto kMax = Max; + static constexpr auto kUniqueItems = UniqueItems; +}; + +//вся эта структура нужна для того, чтобы работать с трейтами как с значением и +//применять поэтапно опции +//у меня нет иного выбора, кроме как стереть информацию о +//реальном размере строки, так как мне нельзя менять тип. +// 256 должно хватить +struct ArrayTraitsHolder +{ + std::array Name{}; + size_t Name_was_changed = 0; + utils::ConstexprOptional Min = utils::kNull; + size_t Min_was_changed = 0; + utils::ConstexprOptional Max = utils::kNull; + size_t Max_was_changed = 0; + utils::ConstexprOptional UniqueItems = utils::kNull; + size_t UniqueItems_was_changed = 0; +}; + +template +void consteval Apply(ArrayTraitsHolder& traits, preferences::Name name) +{ + size_t Size = name.kValue.kSize; + for (size_t index = 0; index < Size; index++) + { + traits.Name[index] = name.kValue[index]; + } + for (size_t index = Size; index < 256; index++) + { + traits.Name[index] = '\0'; + } + traits.Name_was_changed++; +} + +template +void consteval Apply(ArrayTraitsHolder&, const T&) +{ + static_assert( + ![] {}, "You are used unknown option"); +} + +template +void consteval Apply(ArrayTraitsHolder& traits, preferences::Min) +{ + traits.Min = value; + traits.Min_was_changed++; +} + +template +void consteval Apply(ArrayTraitsHolder& traits, preferences::Max) +{ + traits.Max = value; + traits.Max_was_changed++; +} + +template +void consteval Apply(ArrayTraitsHolder& traits, preferences::UniqueItems) +{ + traits.UniqueItems = value; + traits.UniqueItems_was_changed++; +} + +template +void consteval ApplyAll(ArrayTraitsHolder& traits, Option... option) +{ + (Apply(traits, option), ...); +} + +template +struct ArrayMagicHelper +{ + consteval static auto resolve_holder() + { + ArrayTraitsHolder helper{}; + ApplyAll(helper, Option{}...); + return helper; + } + consteval static auto resolve_traits() + { + constexpr ArrayTraitsHolder traits = resolve_holder(); + constexpr auto name = utils::MakeConstexprString(); + static_assert(traits.Name_was_changed <= 1, + "Don't use more 1 Name in template args"); + static_assert(traits.Max_was_changed <= 1, + "Don't use more 1 Max in template args"); + static_assert(traits.Min_was_changed <= 1, + "Don't use more 1 Min in template args"); + static_assert(traits.UniqueItems_was_changed <= 1, + "Don't use more 1 UniqueItems in template args"); + return ArrayTraits{}; + } +}; + +template +using array_traits_helper_t = + decltype(ArrayMagicHelper::resolve_traits()); +} // namespace detail + +namespace types +{ +template +using Array = ArrayProperty>; + +} +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/types/object_type.hpp b/src/openapi/types/object_type.hpp new file mode 100644 index 00000000..43ab3a27 --- /dev/null +++ b/src/openapi/types/object_type.hpp @@ -0,0 +1,126 @@ +#pragma once +#include + +#include "openapi/base/extended_object_property.hpp" +#include "openapi/base/object_property.hpp" +#include "openapi/base/preferences.hpp" +#include "openapi/base/string_property.hpp" +#include "openapi/base/string_traits.hpp" +#include "utils/constexpr_optional.hpp" + +namespace timetable_vsu_backend::openapi +{ +namespace detail +{ +template UseRoot, + utils::ConstexprOptional AdditionalProperties> +struct ObjectTraits : NamedTraits +{ + static constexpr auto kUseRoot = UseRoot; + static constexpr auto kAdditionalProperties = AdditionalProperties; +}; + +//вся эта структура нужна для того, чтобы работать с трейтами как с значением и +//применять поэтапно опции +//у меня нет иного выбора, кроме как стереть информацию о +//реальном размере строки, так как мне нельзя менять тип. +// 256 должно хватить +struct ObjectTraitsHolder +{ + utils::ConstexprOptional AdditionalProperties{utils::kNull}; + size_t AdditionalProperties_was_changed = 0; + utils::ConstexprOptional UseRoot{utils::kNull}; + size_t UseRoot_was_changed = 0; + std::array Name{}; + size_t Name_was_changed = 0; +}; + +template +void consteval Apply(ObjectTraitsHolder& traits, preferences::Name name) +{ + size_t Size = name.kValue.kSize; + for (size_t index = 0; index < Size; index++) + { + traits.Name[index] = name.kValue[index]; + } + for (size_t index = Size; index < 256; index++) + { + traits.Name[index] = '\0'; + } + traits.Name_was_changed++; +} + +template +void consteval Apply(ObjectTraitsHolder& traits, preferences::UseRoot) +{ + traits.UseRoot = value; + traits.Name_was_changed++; +} + +template +void consteval Apply(ObjectTraitsHolder& traits, + preferences::AdditionalProperties) +{ + traits.AdditionalProperties = value; + traits.AdditionalProperties_was_changed++; +} + +template +void consteval Apply(ObjectTraitsHolder&, const T&) +{ + static_assert( + ![] {}, "You are used unknown option"); +} + +template +void consteval ApplyAll(ObjectTraitsHolder& traits, Option... option) +{ + (Apply(traits, option), ...); +} + +template +struct ObjectMagicHelper +{ + consteval static auto resolve_holder() + { + ObjectTraitsHolder helper{}; + ApplyAll(helper, Option{}...); + return helper; + } + consteval static auto resolve_traits() + { + constexpr ObjectTraitsHolder traits = resolve_holder(); + constexpr auto name = utils::MakeConstexprString(); + static_assert(traits.Name_was_changed <= 1, + "Don't use more 1 Name in template args"); + static_assert(traits.UseRoot_was_changed <= 1, + "Don't use more 1 UseRoot in template args"); + static_assert(traits.AdditionalProperties_was_changed <= 1, + "Don't use more 1 AdditionalProperties in template args"); + return ObjectTraits{}; + } + consteval static auto resolve_type() + { + using Traits = decltype(resolve_traits()); + if constexpr (Traits::kAdditionalProperties.value_or(false)) + { + return ExtendedObjectProperty{}; + } + else + { + return ObjectProperty{}; + } + } +}; + +} // namespace detail + +namespace types +{ +template +using Object = + decltype(detail::ObjectMagicHelper::resolve_type()); + +} +} // namespace timetable_vsu_backend::openapi diff --git a/src/openapi/types/string_type.hpp b/src/openapi/types/string_type.hpp new file mode 100644 index 00000000..705d200c --- /dev/null +++ b/src/openapi/types/string_type.hpp @@ -0,0 +1,108 @@ +#pragma once +#include + +#include "openapi/base/preferences.hpp" +#include "openapi/base/string_property.hpp" +#include "openapi/base/string_traits.hpp" + +namespace timetable_vsu_backend::openapi +{ +namespace detail +{ +template +struct StringTraits : NamedTraits +{ + static constexpr auto kPattern = Pattern; +}; + +//вся эта структура нужна для того, чтобы работать с трейтами как с значением и +//применять поэтапно опции +//у меня нет иного выбора, кроме как стереть информацию о +//реальном размере строки, так как мне нельзя менять тип. +// 256 должно хватить +struct StringTraitsHolder +{ + std::array Pattern{}; + size_t Pattern_was_changed = 0; + std::array Name{}; + size_t Name_was_changed = 0; +}; + +template +void consteval Apply(StringTraitsHolder& traits, preferences::Name name) +{ + size_t Size = name.kValue.kSize; + for (size_t index = 0; index < Size; index++) + { + traits.Name[index] = name.kValue[index]; + } + for (size_t index = Size; index < 256; index++) + { + traits.Name[index] = '\0'; + } + traits.Name_was_changed++; +} + +template +void consteval Apply(StringTraitsHolder& traits, + preferences::Pattern name) +{ + size_t Size = name.kValue.kSize; + for (size_t index = 0; index < Size; index++) + { + traits.Pattern[index] = name.kValue[index]; + } + for (size_t index = Size; index < 256; index++) + { + traits.Pattern[index] = '\0'; + } + traits.Pattern_was_changed++; +} + +template +void consteval Apply(StringTraitsHolder&, const T&) +{ + static_assert( + ![] {}, "You are used unknown option"); +} + +template +void consteval ApplyAll(StringTraitsHolder& traits, Option... option) +{ + (Apply(traits, option), ...); +} + +template +struct StringMagicHelper +{ + consteval static auto resolve_holder() + { + StringTraitsHolder helper{}; + ApplyAll(helper, Option{}...); + return helper; + } + consteval static auto resolve_traits() + { + constexpr StringTraitsHolder traits = resolve_holder(); + constexpr auto name = utils::MakeConstexprString(); + constexpr auto pattern = utils::MakeConstexprString(); + static_assert(traits.Name_was_changed <= 1, + "Don't use more 1 Name in template args"); + static_assert(traits.Pattern_was_changed <= 1, + "Don't use more 1 Pattern in template args"); + return StringTraits{}; + } +}; + +template +using string_traits_helper_t = + decltype(StringMagicHelper::resolve_traits()); +} // namespace detail + +namespace types +{ +template +using String = StringProperty>; + +} +} // namespace timetable_vsu_backend::openapi diff --git a/src/utils/constexpr_optional.hpp b/src/utils/constexpr_optional.hpp new file mode 100644 index 00000000..88670155 --- /dev/null +++ b/src/utils/constexpr_optional.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +namespace timetable_vsu_backend::utils +{ +struct AccessToNull : public std::exception +{ + public: + AccessToNull() = default; + virtual ~AccessToNull() = default; + + const char* what() const noexcept override + { + return "Attempt access to Null"; + } +}; + +struct Null +{ +}; + +static constexpr Null kNull = {}; + +template +struct ConstexprOptional +{ + using value_type = T; + T value_; + bool has_value_; + constexpr ConstexprOptional(const T& value) noexcept + : value_(value), has_value_(true) + { + } + constexpr ConstexprOptional(Null) noexcept : value_{}, has_value_(false) + { + } + constexpr bool HasValue() const noexcept + { + return has_value_; + } + constexpr bool IsNull() const noexcept + { + return !HasValue(); + } + constexpr const T& get_value() const + { + if (!IsNull()) + { + throw AccessToNull(); + } + return value_; + } + constexpr const T& value() const + { + if (IsNull()) + { + throw AccessToNull(); + } + return value_; + } + constexpr T& value() + { + if (IsNull()) + { + throw AccessToNull(); + } + return value_; + } + constexpr ConstexprOptional& operator=(const T& t) noexcept + { + value_ = t; + has_value_ = true; + return *this; + } + constexpr ConstexprOptional& operator=(Null) noexcept + { + has_value_ = false; + return *this; + } + constexpr const T& value_or(const T& t) const noexcept + { + if (IsNull()) + { + return t; + } + else + return value(); + } +}; + +} // namespace timetable_vsu_backend::utils diff --git a/utests/openapi/json/parse/basic_array.cpp b/utests/openapi/json/parse/basic_array.cpp new file mode 100644 index 00000000..661782df --- /dev/null +++ b/utests/openapi/json/parse/basic_array.cpp @@ -0,0 +1,108 @@ +#include + +#include +#include +#include +#include + +#include "openapi/base/named_traits.hpp" +#include "openapi/base/preferences.hpp" +#include "openapi/json/parse/array_property.hpp" +#include "openapi/types/array_type.hpp" +#include "utils/constexpr_optional.hpp" +#include "utils/constexpr_string.hpp" +#include "views/hello/view.hpp" + +using namespace timetable_vsu_backend::openapi; + +UTEST(Openapi_Json_Serialize, BasicArrayProperty) +{ + using Type = types::Array; + constexpr auto jsonString = R"( + { + "test" : [1,2,2] + } + )"; + auto json = userver::formats::json::FromString(jsonString); + auto item = json["test"].As(); + std::vector expected_item{1, 2, 2}; + EXPECT_EQ(item(), expected_item); +} + +UTEST(Openapi_Json_Serialize, BasicArrayPropertyMin) +{ + using Type = types::Array>; + constexpr auto jsonString = R"( + { + "test" : [1,2,2] + } + )"; + auto json = userver::formats::json::FromString(jsonString); + bool was_exception = false; + try + { + json["test"].As(); + } + catch (std::exception& exc) + { + was_exception = true; + std::string exc_msg = exc.what(); + std::string expected = + "Field has elements less than allowed, current: 3, " + "min: 4"; + EXPECT_EQ(exc_msg, expected); + } + EXPECT_TRUE(was_exception); +} + +UTEST(Openapi_Json_Serialize, BasicArrayPropertyMax) +{ + using Type = types::Array, preferences::Max<2>>; + constexpr auto jsonString = R"( + { + "test" : [1,2,2] + } + )"; + auto json = userver::formats::json::FromString(jsonString); + bool was_exception = false; + try + { + json["test"].As(); + } + catch (std::exception& exc) + { + was_exception = true; + std::string exc_msg = exc.what(); + std::string expected = + "Field has elements more than allowed, current: 3, " + "max: 2"; + EXPECT_EQ(exc_msg, expected); + } + EXPECT_TRUE(was_exception); +} + +UTEST(Openapi_Json_Serialize, BasicArrayPropertyUnique) +{ + using Type = types::Array>; + constexpr auto jsonString = R"( + { + "test" : [1,2,2] + } + )"; + auto json = userver::formats::json::FromString(jsonString); + bool was_exception = false; + try + { + json["test"].As(); + } + catch (std::exception& exc) + { + was_exception = true; + std::string exc_msg = exc.what(); + std::string expected = + "Field has equals elements, element 2 and 3 are " + "equal"; + EXPECT_EQ(exc_msg, expected); + } + EXPECT_TRUE(was_exception); +} diff --git a/utests/openapi/json/parse/basic_object.cpp b/utests/openapi/json/parse/basic_object.cpp new file mode 100644 index 00000000..ebfbf38f --- /dev/null +++ b/utests/openapi/json/parse/basic_object.cpp @@ -0,0 +1,86 @@ +#include + +#include +#include +#include +#include + +#include "openapi/base/named_traits.hpp" +#include "openapi/base/preferences.hpp" +#include "openapi/base/property_base.hpp" +#include "openapi/base/string_traits.hpp" +#include "openapi/json/parse/array_property.hpp" +#include "openapi/json/parse/object_property.hpp" +#include "openapi/json/parse/string_property.hpp" +#include "openapi/types/array_type.hpp" +#include "openapi/types/object_type.hpp" +#include "openapi/types/string_type.hpp" +#include "utils/constexpr_optional.hpp" +#include "utils/constexpr_string.hpp" +#include "views/hello/view.hpp" + +using namespace timetable_vsu_backend::openapi; +using namespace timetable_vsu_backend::openapi::types; +using namespace timetable_vsu_backend::openapi::preferences; + +struct First +{ + String> field; + Array> field2; + using Reflective = Yes; +}; + +UTEST(Openapi_Json_Serialize, BasicObject) +{ + static_assert(checks::IsReflective); + using Type = Object; + constexpr auto jsonString = R"( + { + "field" : "test", + "field2" : [1,3,5] + } + )"; + auto json = userver::formats::json::FromString(jsonString); + auto got_object = json.As(); + auto expected_object = Type(); + expected_object().field() = "test"; + expected_object().field2() = {1, 3, 5}; + EXPECT_EQ(got_object().field(), expected_object().field()); + EXPECT_EQ(got_object().field2(), expected_object().field2()); +} + +struct SecondRootObject +{ + String> field; + Array> field2; + using Reflective = Yes; +}; + +struct SecondSubObject +{ + Object> a; + String> field3; + using Reflective = Yes; +}; + +UTEST(Openapi_Json_Serialize, BasicUseRootObject) +{ + using Type = Object; + constexpr auto jsonString = R"( + { + "field" : "test", + "field2" : [1,3,5], + "field3" : "test3" + } + )"; + auto json = userver::formats::json::FromString(jsonString); + auto got_object = json.As(); + auto expected_object = Type(); + expected_object().a().field() = "test"; + expected_object().a().field2() = {1, 3, 5}; + expected_object().field3() = "test3"; + EXPECT_EQ(expected_object().a().field(), got_object().a().field()); + EXPECT_EQ(expected_object().a().field2(), + got_object().a().field2()); + EXPECT_EQ(expected_object().field3(), got_object().field3()); +} diff --git a/utests/openapi/json/parse/basic_string.cpp b/utests/openapi/json/parse/basic_string.cpp new file mode 100644 index 00000000..b419654e --- /dev/null +++ b/utests/openapi/json/parse/basic_string.cpp @@ -0,0 +1,67 @@ +#include + +#include +#include +#include +#include + +#include "openapi/base/named_traits.hpp" +#include "openapi/base/preferences.hpp" +#include "openapi/base/string_traits.hpp" +#include "openapi/json/parse/string_property.hpp" +#include "openapi/types/string_type.hpp" +#include "utils/constexpr_optional.hpp" +#include "utils/constexpr_string.hpp" +#include "views/hello/view.hpp" + +using namespace timetable_vsu_backend::openapi; + +UTEST(Openapi_Json_Serialize, BasicStringPattern) +{ + using Type = types::String>; + constexpr auto jsonString = R"( + { + "test" : "" + } + )"; + auto json = userver::formats::json::FromString(jsonString); + bool was_exception = false; + try + { + json["test"].As(); + } + catch (std::exception& exc) + { + was_exception = true; + std::string exc_msg = exc.what(); + std::string expected = "Field doesnt satisfy pattern: dsds, value: "; + EXPECT_EQ(exc_msg, expected); + } + EXPECT_TRUE(was_exception); +} + +UTEST(Openapi_Json_Serialize, EndStringPattern) +{ + using Type = types::String>; + constexpr auto jsonString = R"( + { + "test" : "some_text_not_end" + } + )"; + auto json = userver::formats::json::FromString(jsonString); + bool was_exception = false; + try + { + json["test"].As(); + } + catch (std::exception& exc) + { + was_exception = true; + std::string exc_msg = exc.what(); + std::string expected = + "Field doesnt satisfy pattern: some_text$, value: " + "some_text_not_end"; + EXPECT_EQ(exc_msg, expected); + } + EXPECT_TRUE(was_exception); +}