Skip to content

Commit 6ecabc7

Browse files
committed
feat chaotic: support arrays of custom types, allow conversions from rvalue
Tests: протестировано CI commit_hash:58099ed12046af064213d87afe0a168ace5ca607
1 parent 53cecf2 commit 6ecabc7

File tree

10 files changed

+135
-23
lines changed

10 files changed

+135
-23
lines changed

chaotic/chaotic/back/cpp/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ def descriptor_type(self) -> str:
750750
case False:
751751
unknown_fields = f'{ch}::UnknownFields::Forbid'
752752
case _:
753-
unknown_fields = f'{ch}::UnknownFields::StoreTyped<{self.extra_type.cpp_user_name()}>'
753+
unknown_fields = f'{ch}::UnknownFields::StoreTyped<{self.extra_type.parser_type(ch, name)}>'
754754

755755
fields = [
756756
self.fields[field].descriptor_type(

chaotic/include/userver/chaotic/io/userver/utils/default_dict.hpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
// Utilitary header for chaotic for a custom type serialization/parsing support
44

5+
#include <type_traits>
6+
57
#include <userver/chaotic/convert/to.hpp>
68
#include <userver/utils/default_dict.hpp>
79
#include <userver/utils/meta.hpp>
@@ -10,22 +12,29 @@ USERVER_NAMESPACE_BEGIN
1012

1113
namespace utils {
1214

15+
// Forwards `value.extra` and `value.__default__` fields onto DefaultDict<T>
1316
template <typename T, typename U>
14-
DefaultDict<T> Convert(const U& value, chaotic::convert::To<DefaultDict<T>>) {
17+
DefaultDict<T> Convert(U&& value, chaotic::convert::To<DefaultDict<T>>) {
1518
auto& extra = value.extra;
16-
auto dict = DefaultDict<T>{{extra.begin(), extra.end()}};
19+
20+
using IteratorType = std::conditional_t<
21+
std::is_rvalue_reference_v<U&&>,
22+
decltype(std::move_iterator{extra.begin()}),
23+
decltype(extra.begin())>;
24+
auto dict = DefaultDict<T>{{IteratorType{extra.begin()}, IteratorType{extra.end()}}};
1725

1826
if constexpr (meta::kIsOptional<decltype(value.__default__)>) {
1927
if (value.__default__) {
20-
dict.SetDefault(*value.__default__);
28+
dict.SetDefault(*std::forward<U>(value).__default__);
2129
}
2230
} else {
23-
dict.SetDefault(value.__default__);
31+
dict.SetDefault(std::forward<U>(value).__default__);
2432
}
2533

2634
return dict;
2735
}
2836

37+
// Fills only `extra` and `__default__` fields of U
2938
template <typename T, typename U>
3039
U Convert(const DefaultDict<T>& value, chaotic::convert::To<U>) {
3140
U u;

chaotic/include/userver/chaotic/sax_parser.hpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,19 @@ class WithType final : private formats::json::parser::Subscriber<typename RawPar
3636

3737
private:
3838
void OnSend(typename RawParser::ResultType&& value) override {
39-
auto user_value = [this, &value] {
40-
try {
41-
return Convert(value, convert::To<UserType>{});
42-
} catch (const std::exception& e) {
43-
formats::json::parser::BaseParser& base = parser_.GetParser();
44-
chaotic::ThrowForPath<formats::json::Value>(e.what(), base.GetCurrentPath());
45-
}
46-
}();
47-
subscriber_->OnSend(std::move(user_value));
39+
if constexpr (std::is_same_v<typename RawParser::ResultType, UserType>) {
40+
subscriber_->OnSend(std::move(value));
41+
} else {
42+
auto user_value = [this, &value] {
43+
try {
44+
return Convert(std::move(value), convert::To<UserType>{});
45+
} catch (const std::exception& e) {
46+
formats::json::parser::BaseParser& base = parser_.GetParser();
47+
chaotic::ThrowForPath<formats::json::Value>(e.what(), base.GetCurrentPath());
48+
}
49+
}();
50+
subscriber_->OnSend(std::move(user_value));
51+
}
4852
}
4953

5054
RawParser parser_;

chaotic/include/userver/chaotic/sax_parser/object.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ class UnknownFieldsParser<UnknownFields::StoreTyped<T>>
127127
parser_.Subscribe(*this);
128128
}
129129

130-
void Key(std::string_view key, std::string_view) { key_ = key; }
130+
void Key(std::string_view key, std::string_view) {
131+
key_ = key;
132+
parser_.Reset();
133+
}
131134

132135
formats::json::parser::BaseParser& GetParser() { return parser_.GetParser(); }
133136

chaotic/include/userver/chaotic/with_type.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ template <typename Value, typename RawType, typename UserType>
1919
UserType Parse(const Value& value, formats::parse::To<WithType<RawType, UserType>>) {
2020
auto result = value.template As<RawType>();
2121
try {
22-
return Convert(result, convert::To<UserType>{});
22+
return Convert(std::move(result), convert::To<UserType>{});
2323
} catch (const std::exception& e) {
2424
chaotic::ThrowForValue(e.what(), value);
2525
}

chaotic/integration_tests/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ add_google_tests(${PROJECT_NAME})
1212

1313
file(GLOB_RECURSE SCHEMAS ${CMAKE_CURRENT_SOURCE_DIR}/schemas/*.yaml)
1414
set(NON_SAX_SCHEMAS
15-
${CMAKE_CURRENT_SOURCE_DIR}/schemas/array_of_xcpptype.yaml
1615
${CMAKE_CURRENT_SOURCE_DIR}/schemas/custom_cpp_type.yaml
1716
${CMAKE_CURRENT_SOURCE_DIR}/schemas/recursion.yaml
1817
)

chaotic/integration_tests/include/userver/chaotic/io/my/custom_array.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace my {
1010
template <typename T>
1111
struct CustomArray final {
1212
template <typename U>
13-
CustomArray(const U& value)
13+
explicit CustomArray(const U& value)
1414
: s(value.begin(), value.end())
1515
{}
1616
std::vector<T> s;

chaotic/integration_tests/include/userver/chaotic/io/my/custom_string.hpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,18 @@ namespace my {
88

99
// Definition of a custom user structure
1010
struct CustomString final {
11-
CustomString(const std::string& s)
11+
CustomString() = delete;
12+
13+
explicit CustomString(const std::string& s)
1214
: s(s)
1315
{}
1416

17+
CustomString(const CustomString&) = default;
18+
CustomString& operator=(const CustomString&) = default;
19+
20+
CustomString(CustomString&&) = default;
21+
CustomString& operator=(CustomString&&) = default;
22+
1523
std::string s;
1624
};
1725

@@ -29,8 +37,8 @@ inline std::string Convert(const CustomString& str, USERVER_NAMESPACE::chaotic::
2937

3038
// The std::string -> CustomString Convert() is used during parsing
3139
// (json::Value -> CustomString)
32-
inline CustomString Convert(const std::string& str, USERVER_NAMESPACE::chaotic::convert::To<CustomString>) {
33-
return CustomString(str);
40+
inline CustomString Convert(std::string&& str, USERVER_NAMESPACE::chaotic::convert::To<CustomString>) {
41+
return CustomString(std::move(str));
3442
}
3543

3644
} // namespace my

chaotic/integration_tests/include/userver/chaotic/io/my/point.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ struct Point {
1111
double lat;
1212
};
1313

14-
bool operator==(const Point& a, const Point& b) { return a.lon == b.lon && a.lat == b.lat; }
14+
inline bool operator==(const Point& a, const Point& b) noexcept { return a.lon == b.lon && a.lat == b.lat; }
1515

16-
inline Point Convert(const std::vector<double>& arr, USERVER_NAMESPACE::chaotic::convert::To<Point>) {
16+
inline Point Convert(std::vector<double>&& arr, USERVER_NAMESPACE::chaotic::convert::To<Point>) {
1717
return Point{arr.at(0), arr.at(1)};
1818
}
1919

chaotic/integration_tests/tests/render/custom.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <userver/formats/json/inline.hpp>
44
#include <userver/formats/json/value_builder.hpp>
55

6+
#include <schemas/array_of_xcpptype.hpp>
67
#include <schemas/custom_cpp_type.hpp>
78

89
USERVER_NAMESPACE_BEGIN
@@ -14,6 +15,9 @@ TEST(Custom, Int) {
1415

1516
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
1617
EXPECT_EQ(json_back, json);
18+
19+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
20+
EXPECT_EQ(custom2, custom);
1721
}
1822

1923
TEST(Custom, String) {
@@ -23,6 +27,9 @@ TEST(Custom, String) {
2327

2428
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
2529
EXPECT_EQ(json_back, json);
30+
31+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
32+
EXPECT_EQ(custom2, custom);
2633
}
2734

2835
TEST(Custom, Decimal) {
@@ -32,6 +39,9 @@ TEST(Custom, Decimal) {
3239

3340
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
3441
EXPECT_EQ(json_back, json);
42+
43+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
44+
EXPECT_EQ(custom2, custom);
3545
}
3646

3747
TEST(Custom, Boolean) {
@@ -41,6 +51,9 @@ TEST(Custom, Boolean) {
4151

4252
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
4353
EXPECT_EQ(json_back, json);
54+
55+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
56+
EXPECT_EQ(custom2, custom);
4457
}
4558

4659
TEST(Custom, Number) {
@@ -50,6 +63,9 @@ TEST(Custom, Number) {
5063

5164
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
5265
EXPECT_EQ(json_back, json);
66+
67+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
68+
EXPECT_EQ(custom2, custom);
5369
}
5470

5571
TEST(Custom, Object) {
@@ -59,6 +75,9 @@ TEST(Custom, Object) {
5975

6076
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
6177
EXPECT_EQ(json_back, json);
78+
79+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
80+
EXPECT_EQ(custom2, custom);
6281
}
6382

6483
TEST(Custom, XCppContainer) {
@@ -68,6 +87,9 @@ TEST(Custom, XCppContainer) {
6887

6988
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
7089
EXPECT_EQ(json_back, json);
90+
91+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
92+
EXPECT_EQ(custom2, custom);
7193
}
7294

7395
TEST(Custom, XCppType) {
@@ -77,6 +99,9 @@ TEST(Custom, XCppType) {
7799

78100
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
79101
EXPECT_EQ(json_back, json);
102+
103+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
104+
EXPECT_EQ(custom2, custom);
80105
}
81106

82107
TEST(Custom, OneOf) {
@@ -86,6 +111,9 @@ TEST(Custom, OneOf) {
86111

87112
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
88113
EXPECT_EQ(json_back, json) << ToString(json_back) << " " << ToString(json);
114+
115+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
116+
EXPECT_EQ(custom2, custom);
89117
}
90118

91119
TEST(Custom, OneOfWithDiscriminator) {
@@ -98,6 +126,9 @@ TEST(Custom, OneOfWithDiscriminator) {
98126

99127
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
100128
EXPECT_EQ(json_back, json);
129+
130+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
131+
EXPECT_EQ(custom2, custom);
101132
}
102133

103134
TEST(Custom, AllOf) {
@@ -107,6 +138,64 @@ TEST(Custom, AllOf) {
107138

108139
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
109140
EXPECT_EQ(json_back, json);
141+
142+
const auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjWithCustom>{});
143+
EXPECT_EQ(custom2, custom);
144+
}
145+
146+
TEST(Custom, ArrayOfXCppType) {
147+
auto json = formats::json::MakeObject(
148+
"foo",
149+
formats::json::MakeArray(formats::json::MakeArray(1, 2)),
150+
"bar",
151+
formats::json::MakeArray(-1, -2),
152+
"baz",
153+
formats::json::MakeArray(-3, 100, "test"),
154+
"additional1",
155+
formats::json::MakeArray(formats::json::MakeObject("lat", 4, "lon", 3)),
156+
"additional2",
157+
formats::json::MakeArray(
158+
formats::json::MakeObject("lon", 5, "lat", 6),
159+
formats::json::MakeObject("lon", 7, "lat", 8)
160+
),
161+
"additional3",
162+
formats::json::MakeArray()
163+
);
164+
165+
auto custom = json.As<ns::ObjectArrayOfXCppType>();
166+
ns::ObjectArrayOfXCppType ethalon;
167+
ethalon.foo = std::vector<my::Point>{
168+
my::Point{1, 2},
169+
};
170+
ethalon.bar.emplace();
171+
ethalon.bar->push_back(my::CustomNumber{-1});
172+
ethalon.bar->push_back(my::CustomNumber{-2});
173+
174+
ethalon.baz.emplace();
175+
ethalon.baz->push_back(my::CustomNumber{-3});
176+
ethalon.baz->push_back(my::CustomNumber{100});
177+
ethalon.baz->push_back(my::CustomString{"test"});
178+
179+
ethalon.extra = {
180+
{"additional1", std::vector<my::Point>{my::Point{3, 4}}},
181+
{"additional2", std::vector<my::Point>{my::Point{5, 6}, my::Point{7, 8}}},
182+
{"additional3", std::vector<my::Point>{}},
183+
};
184+
EXPECT_EQ(custom, ethalon);
185+
186+
auto json_back = formats::json::ValueBuilder{custom}.ExtractValue();
187+
EXPECT_EQ(json_back, json);
188+
189+
auto custom2 = FromJsonString(ToString(json), formats::parse::To<ns::ObjectArrayOfXCppType>{});
190+
EXPECT_EQ(custom2, ethalon);
191+
192+
auto changed_ethalon = ethalon;
193+
changed_ethalon.extra["additional2"][1].lat = 42;
194+
EXPECT_FALSE(changed_ethalon == ethalon);
195+
196+
changed_ethalon = ethalon;
197+
changed_ethalon.extra.erase("additional3");
198+
EXPECT_FALSE(changed_ethalon == ethalon);
110199
}
111200

112201
USERVER_NAMESPACE_END

0 commit comments

Comments
 (0)