diff --git a/src/openapi/base/holder.hpp b/src/openapi/base/holder.hpp new file mode 100644 index 00000000..de5d6c8d --- /dev/null +++ b/src/openapi/base/holder.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +namespace openapi::traits +{ +template +struct HolderField +{ + T value_{}; + size_t counter_changes{}; + void operator=(const T& t) + { + value_ = t; + counter_changes++; + } + T& operator()() + { + return value_; + } + const T& operator()() const + { + return value_; + } +}; + +template <> +struct HolderField +{ + utils::FixedString value_{}; + size_t counter_changes{}; + template + constexpr void operator=(const utils::ConstexprString& t) + { + for (size_t index = 0; index < Size; index++) + { + value_[index] = t[index]; + } + for (size_t index = Size; index < 256; index++) + { + value_[index] = '\0'; + } + counter_changes++; + } + constexpr const utils::FixedString& operator()() const + { + return value_; + } +}; +} // namespace openapi::traits diff --git a/src/openapi/base/reflective_uuid_fix.hpp b/src/openapi/base/reflective_uuid_fix.hpp new file mode 100644 index 00000000..6f964a78 --- /dev/null +++ b/src/openapi/base/reflective_uuid_fix.hpp @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +namespace openapi +{ +template <> +constexpr inline bool checks::is_reflective_v = false; +} diff --git a/src/openapi/base/uuid_property.hpp b/src/openapi/base/uuid_property.hpp new file mode 100644 index 00000000..6e1bac72 --- /dev/null +++ b/src/openapi/base/uuid_property.hpp @@ -0,0 +1,68 @@ +#pragma once +#include +#include +#include + +namespace openapi +{ +template +struct PropertyBase +{ + using value_type = boost::uuids::uuid; + using traits = Traits; + + template + PropertyBase(Args&&... args) : value(std::forward(args)...) + { + } + + template + PropertyBase(std::initializer_list init_list) + : value(std::move(init_list)) + { + } + + std::partial_ordering operator<=>( + const PropertyBase& r) const + { + return *this <=> r(); + } + std::partial_ordering operator<=>(const value_type& r) const + { + if (value == r) + { + return std::partial_ordering::equivalent; + } + else + return std::partial_ordering::unordered; + } + + bool operator==(const PropertyBase& r) const = default; + + 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 UuidProperty : PropertyBase +{ +}; + +template +struct Property : UuidProperty +{ +}; +} // namespace openapi diff --git a/src/openapi/base/uuid_traits.hpp b/src/openapi/base/uuid_traits.hpp new file mode 100644 index 00000000..b2502098 --- /dev/null +++ b/src/openapi/base/uuid_traits.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace openapi::traits +{ +template +struct UuidHelperTraits : NamedHelperTraits +{ +}; + +} // namespace openapi::traits diff --git a/src/openapi/doc/serialize/uuid.hpp b/src/openapi/doc/serialize/uuid.hpp new file mode 100644 index 00000000..8ac9f41e --- /dev/null +++ b/src/openapi/doc/serialize/uuid.hpp @@ -0,0 +1,18 @@ +#pragma once +#include +#include +#include + +namespace openapi +{ +template +void Append(DocHelper doc_helper, std::type_identity> = {}) +{ + constexpr traits::UuidHelperTraits traits; + auto& field_node = doc_helper.cur_place; + if (!field_node.IsObject()) + field_node = userver::formats::common::Type::kObject; + field_node["type"] = "string"; + field_node["format"] = "uuid"; +} +} // namespace openapi diff --git a/src/openapi/json/parse/base_property.hpp b/src/openapi/json/parse/base_property.hpp new file mode 100644 index 00000000..ef4f887a --- /dev/null +++ b/src/openapi/json/parse/base_property.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace userver::formats::parse +{ +template +requires openapi::IsProperty T Parse(const json::Value& item, To) +{ + return T{item.As()}; +} +} // namespace userver::formats::parse diff --git a/src/openapi/json/serialize/base_property.hpp b/src/openapi/json/serialize/base_property.hpp new file mode 100644 index 00000000..0b0fc222 --- /dev/null +++ b/src/openapi/json/serialize/base_property.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace openapi +{ +template +userver::formats::json::Value Serialize( + const PropertyBase& value, + userver::formats::serialize::To s) +{ + return Serialize(value(), s); +} +} // namespace openapi diff --git a/src/openapi/types/uuid_type.hpp b/src/openapi/types/uuid_type.hpp new file mode 100644 index 00000000..90283751 --- /dev/null +++ b/src/openapi/types/uuid_type.hpp @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace openapi +{ +namespace detail +{ +template +struct UuidTraits : NamedTraits +{ +}; + +struct UuidTraitsHolder +{ + traits::HolderField name; +}; + +template +void consteval Apply(UuidTraitsHolder& traits, preferences::Name) +{ + traits.name = value; +} + +template +void consteval Apply(UuidTraitsHolder&, const T&) +{ + STATIC_ASSERT_FALSE("You are used unknown option"); +} + +template +void consteval ApplyAll(UuidTraitsHolder& traits, Option... option) +{ + (Apply(traits, option), ...); +} + +template +struct UuidHelper +{ + consteval static auto resolve_holder() + { + UuidTraitsHolder helper{}; + ApplyAll(helper, Option{}...); + return helper; + } + consteval static auto resolve_traits() + { + constexpr UuidTraitsHolder traits = resolve_holder(); + static_assert(traits.name.counter_changes <= 1, + "Don't use more 1 Name in template args"); + constexpr auto name = utils::MakeConstexprString(); + return UuidTraits{}; + } +}; + +template +using uuid_traits_helper_t = decltype(UuidHelper::resolve_traits()); +} // namespace detail + +namespace types +{ +template +using Uuid = UuidProperty>; + +} +} // namespace openapi diff --git a/src/utils/serialize/uuid/string.cpp b/src/utils/serialize/uuid/string.cpp new file mode 100644 index 00000000..0652ab38 --- /dev/null +++ b/src/utils/serialize/uuid/string.cpp @@ -0,0 +1,12 @@ +#include "string.hpp" + +#include + +namespace userver::formats::serialize +{ +std::string Serialize(const boost::uuids::uuid& value, + userver::formats::serialize::To) +{ + return to_string(value); +} +} // namespace userver::formats::serialize diff --git a/src/utils/serialize/uuid/string.hpp b/src/utils/serialize/uuid/string.hpp new file mode 100644 index 00000000..caf42269 --- /dev/null +++ b/src/utils/serialize/uuid/string.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include +namespace userver::formats::serialize +{ +std::string Serialize(const boost::uuids::uuid& value, + userver::formats::serialize::To); +} diff --git a/utests/openapi/http/uuid_request.cpp b/utests/openapi/http/uuid_request.cpp new file mode 100644 index 00000000..f0e2332b --- /dev/null +++ b/utests/openapi/http/uuid_request.cpp @@ -0,0 +1,71 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace openapi::http; +using namespace openapi::preferences; +using namespace openapi::types; +using namespace std::literals; + +namespace +{ +struct SomeBody +{ + Uuid> some_uuid; + Array, Name<"some_array">> some_array; + auto operator<=>(const SomeBody&) const = default; +}; + +struct SomeRequest +{ + Header> some_header; + Cookie> some_cookie; + Body some_body; + auto operator<=>(const SomeRequest&) const = default; +}; + +boost::uuids::uuid UUID(std::string_view sv) +{ + return boost::lexical_cast(sv); +} + +} // namespace + +UTEST(Openapi_http_request_parse, BasicUuid) +{ + TestRequest req; + req.body = R"( +{ + "some_uuid" : "2550a976-434f-4e9c-a5da-06fe0ddbfe5d", + "some_array" : ["6ee2dc21-7229-42b9-8fb9-39a49f38d836", "f757748c-03a8-4834-ad2c-e2fe9c03f1e7"] +} + )"; + + req.headers["some_header"sv] = "6483030f-dcf7-4779-8fbe-e78113bec72e"; + req.cookies["some_cookie"] = "314e672f-687c-4001-8df9-3be5d268a0b6"; + auto info = MakeInfoFromRequest(req); + auto parsed = Parse(info, userver::formats::parse::To{}); + // clang-format off + SomeRequest expected{ + .some_header = {UUID("6483030f-dcf7-4779-8fbe-e78113bec72e")}, + .some_cookie = {UUID("314e672f-687c-4001-8df9-3be5d268a0b6")}, + .some_body = {SomeBody{ + .some_uuid = {UUID("2550a976-434f-4e9c-a5da-06fe0ddbfe5d")}, + .some_array = {} + }} + }; + expected.some_body().some_array().emplace_back(Uuid<>{UUID("6ee2dc21-7229-42b9-8fb9-39a49f38d836")}); + expected.some_body().some_array().emplace_back(Uuid<>{UUID("f757748c-03a8-4834-ad2c-e2fe9c03f1e7")}); + // clang-format on + EXPECT_EQ(parsed, expected); +} diff --git a/utests/openapi/http/uuid_response.cpp b/utests/openapi/http/uuid_response.cpp new file mode 100644 index 00000000..d97b22dd --- /dev/null +++ b/utests/openapi/http/uuid_response.cpp @@ -0,0 +1,71 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace openapi::http; +using namespace openapi::preferences; +using namespace openapi::types; +using namespace std::literals; + +namespace +{ +struct SomeBodyResponse +{ + Uuid> some_string; + Array, Name<"some_array">> some_array; + auto operator<=>(const SomeBodyResponse&) const = default; +}; + +struct SomeResponse +{ + Body some_body; + Header> some_header; + auto operator<=>(const SomeResponse&) const = default; +}; + +boost::uuids::uuid UUID(std::string_view sv) +{ + return boost::lexical_cast(sv); +} + +} // namespace + +UTEST(Openapi_http_response_serialize, BasicUuid) +{ + using Response200 = Resp; + // clang-format off + Response200 resp{ + SomeResponse{ + .some_body = { + SomeBodyResponse + { + .some_string = {UUID("6483030f-dcf7-4779-8fbe-e78113bec72e")}, + .some_array = {} + } + }, + .some_header = {UUID("314e672f-687c-4001-8df9-3be5d268a0b6")} + } + }; + resp().some_body().some_array().emplace_back(Uuid<>{UUID("6ee2dc21-7229-42b9-8fb9-39a49f38d836")}); + resp().some_body().some_array().emplace_back(Uuid<>{UUID("f757748c-03a8-4834-ad2c-e2fe9c03f1e7")}); + ResponseInfo expected{ + .userver_code = userver::server::http::HttpStatus::kOk, + .body = R"({"some_string":"6483030f-dcf7-4779-8fbe-e78113bec72e","some_array":["6ee2dc21-7229-42b9-8fb9-39a49f38d836","f757748c-03a8-4834-ad2c-e2fe9c03f1e7"]})", + .headers = {{"some_header", "314e672f-687c-4001-8df9-3be5d268a0b6"}}, + .response_body_type = ResponseBodyType::kJson + }; + // clang-format on + auto got = Serialize(resp, userver::formats::serialize::To{}); + EXPECT_TRUE(expected == got); +} diff --git a/utests/openapi/json/parse/uuid.cpp b/utests/openapi/json/parse/uuid.cpp new file mode 100644 index 00000000..95721bfb --- /dev/null +++ b/utests/openapi/json/parse/uuid.cpp @@ -0,0 +1,31 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace openapi; + +UTEST(Openapi_Json_Parse, BasicUuid) +{ + using Type = types::Uuid<>; + constexpr auto jsonString = R"( + { + "test" : "3dae9084-ec1a-4df4-92da-65f9f649e1f5" + } + )"; + auto expected = Type{boost::lexical_cast( + "3dae9084-ec1a-4df4-92da-65f9f649e1f5")}; + auto json = userver::formats::json::FromString(jsonString); + auto got = json["test"].As(); + EXPECT_EQ(expected, got); +} diff --git a/utests/openapi/json/serialize/uuid.cpp b/utests/openapi/json/serialize/uuid.cpp new file mode 100644 index 00000000..9a2df7e7 --- /dev/null +++ b/utests/openapi/json/serialize/uuid.cpp @@ -0,0 +1,31 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace openapi::preferences; +using namespace openapi::types; + +UTEST(Openapi_Json_Serialize, BasicUuud) +{ + using Serializable = Uuid<>; + Serializable serializable{boost::lexical_cast( + "3dae9084-ec1a-4df4-92da-65f9f649e1f5")}; + userver::formats::json::ValueBuilder json{serializable}; + auto json_text = ToString(json.ExtractValue()); + EXPECT_EQ(json_text, "\"3dae9084-ec1a-4df4-92da-65f9f649e1f5\""); +}