diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/protobuf/json/ut | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/protobuf/json/ut')
-rw-r--r-- | library/cpp/protobuf/json/ut/fields.incl | 23 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/filter_ut.cpp | 93 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/filter_ut.proto | 20 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/inline_ut.cpp | 122 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/inline_ut.proto | 29 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/json.h | 69 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/json2proto_ut.cpp | 1147 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/proto.h | 62 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/proto2json_ut.cpp | 1022 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/repeated_fields.incl | 21 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/string_transform_ut.cpp | 106 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/test.proto | 203 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/util_ut.cpp | 42 | ||||
-rw-r--r-- | library/cpp/protobuf/json/ut/ya.make | 23 |
14 files changed, 2982 insertions, 0 deletions
diff --git a/library/cpp/protobuf/json/ut/fields.incl b/library/cpp/protobuf/json/ut/fields.incl new file mode 100644 index 0000000000..4b22985836 --- /dev/null +++ b/library/cpp/protobuf/json/ut/fields.incl @@ -0,0 +1,23 @@ +// Intentionally no #pragma once + +// (Field name == JSON key, Value) +DEFINE_FIELD(I32, Min<i32>()) +DEFINE_FIELD(I64, Min<i64>()) +DEFINE_FIELD(UI32, Max<ui32>()) +DEFINE_FIELD(UI64, Max<ui64>()) +DEFINE_FIELD(SI32, Min<i32>()) +DEFINE_FIELD(SI64, Min<i64>()) +DEFINE_FIELD(FI32, Max<ui32>()) +DEFINE_FIELD(FI64, Max<ui64>()) +DEFINE_FIELD(SFI32, Min<i32>()) +DEFINE_FIELD(SFI64, Min<i64>()) +DEFINE_FIELD(Bool, true) +DEFINE_FIELD(String, "Lorem ipsum") +DEFINE_FIELD(Bytes, "מחשב") +DEFINE_FIELD(Enum, E_1) +DEFINE_FIELD(Float, 1.123f) +DEFINE_FIELD(Double, 1.123456789012) +DEFINE_FIELD(OneString, "Lorem ipsum dolor") +DEFINE_FIELD(OneTwoString, "Lorem ipsum dolor sit") +DEFINE_FIELD(ABC, "abc") +DEFINE_FIELD(UserID, "some_id")
\ No newline at end of file diff --git a/library/cpp/protobuf/json/ut/filter_ut.cpp b/library/cpp/protobuf/json/ut/filter_ut.cpp new file mode 100644 index 0000000000..95c227666f --- /dev/null +++ b/library/cpp/protobuf/json/ut/filter_ut.cpp @@ -0,0 +1,93 @@ +#include <library/cpp/protobuf/json/ut/filter_ut.pb.h> + +#include <library/cpp/protobuf/json/filter.h> +#include <library/cpp/protobuf/json/field_option.h> +#include <library/cpp/protobuf/json/proto2json.h> +#include <library/cpp/testing/unittest/registar.h> + +using namespace NProtobufJson; + +static NProtobufJsonUt::TFilterTest GetTestMsg() { + NProtobufJsonUt::TFilterTest msg; + msg.SetOptFiltered("1"); + msg.SetNotFiltered("23"); + msg.AddRepFiltered(45); + msg.AddRepFiltered(67); + msg.MutableInner()->AddNumber(100); + msg.MutableInner()->AddNumber(200); + msg.MutableInner()->SetInnerFiltered(235); + return msg; +} + +Y_UNIT_TEST_SUITE(TProto2JsonFilterTest){ + Y_UNIT_TEST(TestFilterPrinter){ + NProtobufJsonUt::TFilterTest msg = GetTestMsg(); +{ + TString expected = R"({"OptFiltered":"1","NotFiltered":"23","RepFiltered":[45,67],)" + R"("Inner":{"Number":[100,200],"InnerFiltered":235}})"; + TString my = Proto2Json(msg); + UNIT_ASSERT_STRINGS_EQUAL(my, expected); +} + +{ + TString expected = R"({"NotFiltered":"23",)" + R"("Inner":{"Number":[100,200]}})"; + TString my = PrintWithFilter(msg, MakeFieldOptionFunctor(NProtobufJsonUt::filter_test, false)); + UNIT_ASSERT_STRINGS_EQUAL(my, expected); +} + +{ + TString expected = R"({"OptFiltered":"1","RepFiltered":[45,67]})"; + TString my = PrintWithFilter(msg, MakeFieldOptionFunctor(NProtobufJsonUt::filter_test)); + UNIT_ASSERT_STRINGS_EQUAL(my, expected); +} + +{ + TString expected = R"({"OptFiltered":"1","NotFiltered":"23",)" + R"("Inner":{"Number":[100,200]}})"; + TString my; + PrintWithFilter(msg, MakeFieldOptionFunctor(NProtobufJsonUt::export_test), *CreateJsonMapOutput(my)); + UNIT_ASSERT_STRINGS_EQUAL(my, expected); +} + +{ + TString expected = R"({"NotFiltered":"23",)" + R"("Inner":{"Number":[100,200]}})"; + auto functor = [](const NProtoBuf::Message&, const NProtoBuf::FieldDescriptor* field) { + return field->name() == "NotFiltered" || field->name() == "Number" || field->name() == "Inner"; + }; + TString my = PrintWithFilter(msg, functor); + UNIT_ASSERT_STRINGS_EQUAL(my, expected); +} +} + +Y_UNIT_TEST(NoUnnecessaryCopyFunctor) { + size_t CopyCount = 0; + struct TFunctorMock { + TFunctorMock(size_t* copyCount) + : CopyCount(copyCount) + { + UNIT_ASSERT(*CopyCount <= 1); + } + + TFunctorMock(const TFunctorMock& f) + : CopyCount(f.CopyCount) + { + ++*CopyCount; + } + + TFunctorMock(TFunctorMock&& f) = default; + + bool operator()(const NProtoBuf::Message&, const NProtoBuf::FieldDescriptor*) const { + return false; + } + + size_t* CopyCount; + }; + + TProto2JsonConfig cfg; + TFilteringPrinter<> printer(TFunctorMock(&CopyCount), cfg); + UNIT_ASSERT(CopyCount <= 1); +} +} +; diff --git a/library/cpp/protobuf/json/ut/filter_ut.proto b/library/cpp/protobuf/json/ut/filter_ut.proto new file mode 100644 index 0000000000..29d630ade4 --- /dev/null +++ b/library/cpp/protobuf/json/ut/filter_ut.proto @@ -0,0 +1,20 @@ +import "google/protobuf/descriptor.proto"; + +package NProtobufJsonUt; + +extend google.protobuf.FieldOptions { + optional bool filter_test = 58255; + optional bool export_test = 58256; +} + +message TFilterTest { + optional string OptFiltered = 1 [(filter_test) = true, (export_test) = true]; + optional string NotFiltered = 2 [(export_test) = true]; + repeated uint64 RepFiltered = 3 [(filter_test) = true]; + + message TInner { + repeated uint32 Number = 1 [(export_test) = true]; + optional int32 InnerFiltered = 2 [(filter_test) = true]; + } + optional TInner Inner = 4 [(export_test) = true]; +} diff --git a/library/cpp/protobuf/json/ut/inline_ut.cpp b/library/cpp/protobuf/json/ut/inline_ut.cpp new file mode 100644 index 0000000000..c29ad32e7d --- /dev/null +++ b/library/cpp/protobuf/json/ut/inline_ut.cpp @@ -0,0 +1,122 @@ +#include <library/cpp/protobuf/json/ut/inline_ut.pb.h> + +#include <library/cpp/protobuf/json/inline.h> +#include <library/cpp/protobuf/json/field_option.h> +#include <library/cpp/protobuf/json/proto2json.h> +#include <library/cpp/testing/unittest/registar.h> + +#include <util/generic/string.h> + +using namespace NProtobufJson; + +static NProtobufJsonUt::TInlineTest GetTestMsg() { + NProtobufJsonUt::TInlineTest msg; + msg.SetOptJson(R"({"a":1,"b":"000"})"); + msg.SetNotJson("12{}34"); + msg.AddRepJson("{}"); + msg.AddRepJson("[1,2]"); + msg.MutableInner()->AddNumber(100); + msg.MutableInner()->AddNumber(200); + msg.MutableInner()->SetInnerJson(R"({"xxx":[]})"); + return msg; +} + +Y_UNIT_TEST_SUITE(TProto2JsonInlineTest){ + Y_UNIT_TEST(TestNormalPrint){ + NProtobufJsonUt::TInlineTest msg = GetTestMsg(); +// normal print should output these fields as just string values +TString expRaw = R"({"OptJson":"{\"a\":1,\"b\":\"000\"}","NotJson":"12{}34","RepJson":["{}","[1,2]"],)" + R"("Inner":{"Number":[100,200],"InnerJson":"{\"xxx\":[]}"}})"; +TString myRaw; +Proto2Json(msg, myRaw); +UNIT_ASSERT_STRINGS_EQUAL(myRaw, expRaw); + +myRaw = PrintInlined(msg, [](const NProtoBuf::Message&, const NProtoBuf::FieldDescriptor*) { return false; }); +UNIT_ASSERT_STRINGS_EQUAL(myRaw, expRaw); // result is the same +} + +Y_UNIT_TEST(TestInliningPrinter) { + NProtobufJsonUt::TInlineTest msg = GetTestMsg(); + // inlined print should output these fields as inlined json sub-objects + TString expInlined = R"({"OptJson":{"a":1,"b":"000"},"NotJson":"12{}34","RepJson":[{},[1,2]],)" + R"("Inner":{"Number":[100,200],"InnerJson":{"xxx":[]}}})"; + + { + TString myInlined = PrintInlined(msg, MakeFieldOptionFunctor(NProtobufJsonUt::inline_test)); + UNIT_ASSERT_STRINGS_EQUAL(myInlined, expInlined); + } + { + auto functor = [](const NProtoBuf::Message&, const NProtoBuf::FieldDescriptor* field) { + return field->name() == "OptJson" || field->name() == "RepJson" || field->name() == "InnerJson"; + }; + TString myInlined = PrintInlined(msg, functor); + UNIT_ASSERT_STRINGS_EQUAL(myInlined, expInlined); + } +} + +Y_UNIT_TEST(TestNoValues) { + // no values - no printing + NProtobufJsonUt::TInlineTest msg; + msg.MutableInner()->AddNumber(100); + msg.MutableInner()->AddNumber(200); + + TString expInlined = R"({"Inner":{"Number":[100,200]}})"; + + TString myInlined = PrintInlined(msg, MakeFieldOptionFunctor(NProtobufJsonUt::inline_test)); + UNIT_ASSERT_STRINGS_EQUAL(myInlined, expInlined); +} + +Y_UNIT_TEST(TestMissingKeyModeNull) { + NProtobufJsonUt::TInlineTest msg; + msg.MutableInner()->AddNumber(100); + msg.MutableInner()->AddNumber(200); + + TString expInlined = R"({"OptJson":null,"NotJson":null,"RepJson":null,"Inner":{"Number":[100,200],"InnerJson":null}})"; + + TProto2JsonConfig cfg; + cfg.SetMissingSingleKeyMode(TProto2JsonConfig::MissingKeyNull).SetMissingRepeatedKeyMode(TProto2JsonConfig::MissingKeyNull); + TString myInlined = PrintInlined(msg, MakeFieldOptionFunctor(NProtobufJsonUt::inline_test), cfg); + UNIT_ASSERT_STRINGS_EQUAL(myInlined, expInlined); +} + +Y_UNIT_TEST(TestMissingKeyModeDefault) { + NProtobufJsonUt::TInlineTestDefaultValues msg; + + TString expInlined = R"({"OptJson":{"default":1},"Number":0,"RepJson":[],"Inner":{"OptJson":{"default":2}}})"; + + TProto2JsonConfig cfg; + cfg.SetMissingSingleKeyMode(TProto2JsonConfig::MissingKeyDefault).SetMissingRepeatedKeyMode(TProto2JsonConfig::MissingKeyDefault); + TString myInlined = PrintInlined(msg, MakeFieldOptionFunctor(NProtobufJsonUt::inline_test), cfg); + UNIT_ASSERT_STRINGS_EQUAL(myInlined, expInlined); +} + +Y_UNIT_TEST(NoUnnecessaryCopyFunctor) { + size_t CopyCount = 0; + struct TFunctorMock { + TFunctorMock(size_t* copyCount) + : CopyCount(copyCount) + { + UNIT_ASSERT(*CopyCount <= 1); + } + + TFunctorMock(const TFunctorMock& f) + : CopyCount(f.CopyCount) + { + ++*CopyCount; + } + + TFunctorMock(TFunctorMock&& f) = default; + + bool operator()(const NProtoBuf::Message&, const NProtoBuf::FieldDescriptor*) const { + return false; + } + + size_t* CopyCount; + }; + + TProto2JsonConfig cfg; + TInliningPrinter<> printer(TFunctorMock(&CopyCount), cfg); + UNIT_ASSERT(CopyCount <= 1); +} +} +; diff --git a/library/cpp/protobuf/json/ut/inline_ut.proto b/library/cpp/protobuf/json/ut/inline_ut.proto new file mode 100644 index 0000000000..76bd10232d --- /dev/null +++ b/library/cpp/protobuf/json/ut/inline_ut.proto @@ -0,0 +1,29 @@ +import "google/protobuf/descriptor.proto"; + +package NProtobufJsonUt; + +extend google.protobuf.FieldOptions { + optional bool inline_test = 58253; +} + +message TInlineTest { + optional string OptJson = 1 [(inline_test) = true]; + optional string NotJson = 2; + repeated string RepJson = 3 [(inline_test) = true]; + + message TInner { + repeated uint32 Number = 1; + optional string InnerJson = 2 [(inline_test) = true]; + } + optional TInner Inner = 4; +} + +message TInlineTestDefaultValues { + optional string OptJson = 1 [(inline_test) = true, default = "{\"default\":1}"]; + optional uint32 Number = 2; + repeated string RepJson = 3 [(inline_test) = true]; + message TInner { + optional string OptJson = 1 [(inline_test) = true, default = "{\"default\":2}"]; + } + optional TInner Inner = 4; +} diff --git a/library/cpp/protobuf/json/ut/json.h b/library/cpp/protobuf/json/ut/json.h new file mode 100644 index 0000000000..c1f108e6e4 --- /dev/null +++ b/library/cpp/protobuf/json/ut/json.h @@ -0,0 +1,69 @@ +#pragma once + +#include <library/cpp/protobuf/json/ut/test.pb.h> + +#include <library/cpp/json/json_value.h> + +#include <cstdarg> + +#include <util/generic/hash_set.h> +#include <util/generic/string.h> + +#include <util/system/defaults.h> + +namespace NProtobufJsonTest { + inline NJson::TJsonValue + CreateFlatJson(const THashSet<TString>& skippedKeys = THashSet<TString>()) { + NJson::TJsonValue json; + +#define DEFINE_FIELD(name, value) \ + if (skippedKeys.find(#name) == skippedKeys.end()) \ + json.InsertValue(#name, value); +#include "fields.incl" +#undef DEFINE_FIELD + + return json; + } + + inline NJson::TJsonValue + CreateRepeatedFlatJson(const THashSet<TString>& skippedKeys = THashSet<TString>()) { + NJson::TJsonValue json; + +#define DEFINE_REPEATED_FIELD(name, type, ...) \ + if (skippedKeys.find(#name) == skippedKeys.end()) { \ + type values[] = {__VA_ARGS__}; \ + NJson::TJsonValue array(NJson::JSON_ARRAY); \ + for (size_t i = 0, end = Y_ARRAY_SIZE(values); i < end; ++i) { \ + array.AppendValue(values[i]); \ + } \ + json.InsertValue(#name, array); \ + } +#include "repeated_fields.incl" +#undef DEFINE_REPEATED_FIELD + + return json; + } + + inline NJson::TJsonValue + CreateCompositeJson(const THashSet<TString>& skippedKeys = THashSet<TString>()) { + const NJson::TJsonValue& part = CreateFlatJson(skippedKeys); + NJson::TJsonValue json; + json.InsertValue("Part", part); + + return json; + } + +#define UNIT_ASSERT_JSONS_EQUAL(lhs, rhs) \ + if (lhs != rhs) { \ + UNIT_ASSERT_STRINGS_EQUAL(lhs.GetStringRobust(), rhs.GetStringRobust()); \ + } + +#define UNIT_ASSERT_JSON_STRINGS_EQUAL(lhs, rhs) \ + if (lhs != rhs) { \ + NJson::TJsonValue _lhs_json, _rhs_json; \ + UNIT_ASSERT(NJson::ReadJsonTree(lhs, &_lhs_json)); \ + UNIT_ASSERT(NJson::ReadJsonTree(rhs, &_rhs_json)); \ + UNIT_ASSERT_JSONS_EQUAL(_lhs_json, _rhs_json); \ + } + +} diff --git a/library/cpp/protobuf/json/ut/json2proto_ut.cpp b/library/cpp/protobuf/json/ut/json2proto_ut.cpp new file mode 100644 index 0000000000..0dfe57bc7a --- /dev/null +++ b/library/cpp/protobuf/json/ut/json2proto_ut.cpp @@ -0,0 +1,1147 @@ +#include "json.h" +#include "proto.h" +#include "proto2json.h" + +#include <library/cpp/protobuf/json/ut/test.pb.h> + +#include <library/cpp/json/json_value.h> +#include <library/cpp/json/json_reader.h> +#include <library/cpp/json/json_writer.h> + +#include <library/cpp/protobuf/json/json2proto.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <util/generic/hash_set.h> +#include <util/generic/string.h> +#include <util/generic/ylimits.h> +#include <util/stream/str.h> +#include <util/string/cast.h> +#include <util/system/defaults.h> +#include <util/system/yassert.h> + +using namespace NProtobufJson; +using namespace NProtobufJsonTest; + +namespace google { + namespace protobuf { + namespace internal { + void MapTestForceDeterministic() { + google::protobuf::io::CodedOutputStream::SetDefaultSerializationDeterministic(); + } + } + } // namespace protobuf +} + +namespace { + class TInit { + public: + TInit() { + ::google::protobuf::internal::MapTestForceDeterministic(); + } + } Init; + + template <typename T> + TString ConvertToString(T value) { + return ToString(value); + } + + // default ToString<double>() implementation loses precision + TString ConvertToString(double value) { + return FloatToString(value); + } + + TString JsonValueToString(const NJson::TJsonValue& json) { + NJsonWriter::TBuf buf(NJsonWriter::HEM_UNSAFE); + return buf.WriteJsonValue(&json).Str(); + } + + void TestComplexMapAsObject(std::function<void(TComplexMapType&)>&& init, const TString& json, const TJson2ProtoConfig& config = TJson2ProtoConfig().SetMapAsObject(true)) { + TComplexMapType modelProto; + + init(modelProto); + + TString modelStr(json); + + TComplexMapType proto; + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TComplexMapType>(modelStr, config)); + + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); + } +} + +Y_UNIT_TEST_SUITE(TJson2ProtoTest) { + Y_UNIT_TEST(TestFlatOptional){ + {const NJson::TJsonValue& json = CreateFlatJson(); + TFlatOptional proto; + Json2Proto(json, proto); + TFlatOptional modelProto; + FillFlatProto(&modelProto); + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} + + // Try to skip each field +#define DEFINE_FIELD(name, value) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + const NJson::TJsonValue& json = CreateFlatJson(skippedField); \ + TFlatOptional proto; \ + Json2Proto(json, proto); \ + TFlatOptional modelProto; \ + FillFlatProto(&modelProto, skippedField); \ + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); \ + } +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD +} // TestFlatOptional + +Y_UNIT_TEST(TestFlatRequired){ + {const NJson::TJsonValue& json = CreateFlatJson(); +TFlatRequired proto; +Json2Proto(json, proto); +TFlatRequired modelProto; +FillFlatProto(&modelProto); +UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} + +// Try to skip each field +#define DEFINE_FIELD(name, value) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + const NJson::TJsonValue& json = CreateFlatJson(skippedField); \ + TFlatRequired proto; \ + UNIT_ASSERT_EXCEPTION(Json2Proto(json, proto), yexception); \ + } +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD +} // TestFlatRequired + +Y_UNIT_TEST(TestNameGenerator) { + TJson2ProtoConfig cfg; + cfg.SetNameGenerator([](const NProtoBuf::FieldDescriptor&) { return "42"; }); + + TNameGeneratorType proto; + Json2Proto(TStringBuf(R"({"42":42})"), proto, cfg); + + TNameGeneratorType expected; + expected.SetField(42); + + UNIT_ASSERT_PROTOS_EQUAL(expected, proto); +} + +Y_UNIT_TEST(TestFlatNoCheckRequired) { + { + const NJson::TJsonValue& json = CreateFlatJson(); + TFlatRequired proto; + Json2Proto(json, proto); + TFlatRequired modelProto; + FillFlatProto(&modelProto); + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); + } + + TJson2ProtoConfig cfg; + cfg.CheckRequiredFields = false; + + // Try to skip each field +#define DEFINE_FIELD(name, value) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + const NJson::TJsonValue& json = CreateFlatJson(skippedField); \ + TFlatRequired proto; \ + UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto, cfg)); \ + } +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD +} // TestFlatNoCheckRequired + +Y_UNIT_TEST(TestFlatRepeated){ + {const NJson::TJsonValue& json = CreateRepeatedFlatJson(); +TFlatRepeated proto; +Json2Proto(json, proto); +TFlatRepeated modelProto; +FillRepeatedProto(&modelProto); +UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} + +// Try to skip each field +#define DEFINE_REPEATED_FIELD(name, ...) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + const NJson::TJsonValue& json = CreateRepeatedFlatJson(skippedField); \ + TFlatRepeated proto; \ + Json2Proto(json, proto); \ + TFlatRepeated modelProto; \ + FillRepeatedProto(&modelProto, skippedField); \ + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); \ + } +#include <library/cpp/protobuf/json/ut/repeated_fields.incl> +#undef DEFINE_REPEATED_FIELD +} // TestFlatRepeated + +Y_UNIT_TEST(TestCompositeOptional){ + {const NJson::TJsonValue& json = CreateCompositeJson(); +TCompositeOptional proto; +Json2Proto(json, proto); +TCompositeOptional modelProto; +FillCompositeProto(&modelProto); +UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} + +// Try to skip each field +#define DEFINE_FIELD(name, value) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + const NJson::TJsonValue& json = CreateCompositeJson(skippedField); \ + TCompositeOptional proto; \ + Json2Proto(json, proto); \ + TCompositeOptional modelProto; \ + FillCompositeProto(&modelProto, skippedField); \ + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); \ + } +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD +} // TestCompositeOptional + +Y_UNIT_TEST(TestCompositeOptionalStringBuf){ + {NJson::TJsonValue json = CreateCompositeJson(); +json["Part"]["Double"] = 42.5; +TCompositeOptional proto; +Json2Proto(JsonValueToString(json), proto); +TCompositeOptional modelProto; +FillCompositeProto(&modelProto); +modelProto.MutablePart()->SetDouble(42.5); +UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} + +// Try to skip each field +#define DEFINE_FIELD(name, value) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + NJson::TJsonValue json = CreateCompositeJson(skippedField); \ + if (json["Part"].Has("Double")) { \ + json["Part"]["Double"] = 42.5; \ + } \ + TCompositeOptional proto; \ + Json2Proto(JsonValueToString(json), proto); \ + TCompositeOptional modelProto; \ + FillCompositeProto(&modelProto, skippedField); \ + if (modelProto.GetPart().HasDouble()) { \ + modelProto.MutablePart()->SetDouble(42.5); \ + } \ + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); \ + } +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD +} // TestCompositeOptionalStringBuf + +Y_UNIT_TEST(TestCompositeRequired) { + { + const NJson::TJsonValue& json = CreateCompositeJson(); + TCompositeRequired proto; + Json2Proto(json, proto); + TCompositeRequired modelProto; + FillCompositeProto(&modelProto); + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); + } + + { + NJson::TJsonValue json; + TCompositeRequired proto; + UNIT_ASSERT_EXCEPTION(Json2Proto(json, proto), yexception); + } +} // TestCompositeRequired + +Y_UNIT_TEST(TestCompositeRepeated) { + { + NJson::TJsonValue json; + NJson::TJsonValue array; + array.AppendValue(CreateFlatJson()); + json.InsertValue("Part", array); + + TCompositeRepeated proto; + Json2Proto(json, proto); + + TFlatOptional partModelProto; + FillFlatProto(&partModelProto); + TCompositeRepeated modelProto; + modelProto.AddPart()->CopyFrom(partModelProto); + + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); + } + + { + // Array of messages with each field skipped + TCompositeRepeated modelProto; + NJson::TJsonValue array; + +#define DEFINE_REPEATED_FIELD(name, ...) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + TFlatOptional partModelProto; \ + FillFlatProto(&partModelProto, skippedField); \ + modelProto.AddPart()->CopyFrom(partModelProto); \ + array.AppendValue(CreateFlatJson(skippedField)); \ + } +#include <library/cpp/protobuf/json/ut/repeated_fields.incl> +#undef DEFINE_REPEATED_FIELD + + NJson::TJsonValue json; + json.InsertValue("Part", array); + + TCompositeRepeated proto; + Json2Proto(json, proto); + + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); + } +} // TestCompositeRepeated + +Y_UNIT_TEST(TestInvalidEnum) { + { + NJson::TJsonValue json; + json.InsertValue("Enum", "E_100"); + TFlatOptional proto; + UNIT_ASSERT_EXCEPTION(Json2Proto(json, proto), yexception); + } + + { + NJson::TJsonValue json; + json.InsertValue("Enum", 100); + TFlatOptional proto; + UNIT_ASSERT_EXCEPTION(Json2Proto(json, proto), yexception); + } +} + +Y_UNIT_TEST(TestFieldNameMode) { + // Original case 1 + { + TString modelStr(R"_({"String":"value"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetString() == "value"); + } + + // Original case 2 + { + TString modelStr(R"_({"String":"value"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameOriginalCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetString() == "value"); + } + + // Lowercase + { + TString modelStr(R"_({"string":"value"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameLowerCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetString() == "value"); + } + + // Uppercase + { + TString modelStr(R"_({"STRING":"value"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameUpperCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetString() == "value"); + } + + // Camelcase + { + TString modelStr(R"_({"string":"value"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameCamelCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetString() == "value"); + } + { + TString modelStr(R"_({"oneString":"value"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameCamelCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetOneString() == "value"); + } + { + TString modelStr(R"_({"oneTwoString":"value"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameCamelCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetOneTwoString() == "value"); + } + + // snake_case + { + TString modelStr(R"_({"string":"value"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameSnakeCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetString() == "value"); + } + { + TString modelStr(R"_({"one_string":"value"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameSnakeCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetOneString() == "value"); + } + { + TString modelStr(R"_({"one_two_string":"value"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameSnakeCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetOneTwoString() == "value"); + } + + // Original case, repeated + { + TString modelStr(R"_({"I32":[1,2]})_"); + + TFlatRepeated proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameOriginalCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatRepeated>(modelStr, config)); + UNIT_ASSERT(proto.I32Size() == 2); + UNIT_ASSERT(proto.GetI32(0) == 1); + UNIT_ASSERT(proto.GetI32(1) == 2); + } + + // Lower case, repeated + { + TString modelStr(R"_({"i32":[1,2]})_"); + + TFlatRepeated proto; + TJson2ProtoConfig config; + config.FieldNameMode = TJson2ProtoConfig::FieldNameLowerCase; + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatRepeated>(modelStr, config)); + UNIT_ASSERT(proto.I32Size() == 2); + UNIT_ASSERT(proto.GetI32(0) == 1); + UNIT_ASSERT(proto.GetI32(1) == 2); + } + + // UseJsonName + { + // FIXME(CONTRIB-139): since protobuf 3.1, Def_upper json name is + // "DefUpper", but until kernel/ugc/schema and yweb/yasap/pdb are + // updated, library/cpp/protobuf/json preserves compatibility with + // protobuf 3.0 by lowercasing default names, making it "defUpper". + TString modelStr(R"_({"My-Upper":1,"my-lower":2,"defUpper":3,"defLower":4})_"); + + TWithJsonName proto; + TJson2ProtoConfig config; + config.SetUseJsonName(true); + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TWithJsonName>(modelStr, config)); + UNIT_ASSERT_EQUAL(proto.Getmy_upper(), 1); + UNIT_ASSERT_EQUAL(proto.GetMy_lower(), 2); + UNIT_ASSERT_EQUAL(proto.GetDef_upper(), 3); + UNIT_ASSERT_EQUAL(proto.Getdef_lower(), 4); + } + + // FieldNameMode with UseJsonName + { + TJson2ProtoConfig config; + config.SetFieldNameMode(TJson2ProtoConfig::FieldNameLowerCase); + UNIT_ASSERT_EXCEPTION_CONTAINS( + config.SetUseJsonName(true), yexception, "mutually exclusive"); + } + { + TJson2ProtoConfig config; + config.SetUseJsonName(true); + UNIT_ASSERT_EXCEPTION_CONTAINS( + config.SetFieldNameMode(TJson2ProtoConfig::FieldNameLowerCase), yexception, "mutually exclusive"); + } +} // TestFieldNameMode + +class TStringTransform: public IStringTransform { +public: + int GetType() const override { + return 0; + } + void Transform(TString& str) const override { + str = "transformed_any"; + } +}; + +class TBytesTransform: public IStringTransform { +public: + int GetType() const override { + return 0; + } + void Transform(TString&) const override { + } + void TransformBytes(TString& str) const override { + str = "transformed_bytes"; + } +}; + +Y_UNIT_TEST(TestInvalidJson) { + NJson::TJsonValue val{"bad value"}; + TFlatOptional proto; + UNIT_ASSERT_EXCEPTION(Json2Proto(val, proto), yexception); +} + +Y_UNIT_TEST(TestInvalidRepeatedFieldWithMapAsObject) { + TCompositeRepeated proto; + TJson2ProtoConfig config; + config.MapAsObject = true; + UNIT_ASSERT_EXCEPTION(Json2Proto(TStringBuf(R"({"Part":{"Boo":{}}})"), proto, config), yexception); +} + +Y_UNIT_TEST(TestStringTransforms) { + // Check that strings and bytes are transformed + { + TString modelStr(R"_({"String":"value_str", "Bytes": "value_bytes"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.AddStringTransform(new TStringTransform); + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetString() == "transformed_any"); + UNIT_ASSERT(proto.GetBytes() == "transformed_any"); + } + + // Check that bytes are transformed, strings are left intact + { + TString modelStr(R"_({"String":"value_str", "Bytes": "value_bytes"})_"); + + TFlatOptional proto; + TJson2ProtoConfig config; + config.AddStringTransform(new TBytesTransform); + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetString() == "value_str"); + UNIT_ASSERT(proto.GetBytes() == "transformed_bytes"); + } + + // Check that repeated bytes are transformed, repeated strings are left intact + { + TString modelStr(R"_({"String":["value_str", "str2"], "Bytes": ["value_bytes", "bytes2"]})_"); + + TFlatRepeated proto; + TJson2ProtoConfig config; + config.AddStringTransform(new TBytesTransform); + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TFlatRepeated>(modelStr, config)); + UNIT_ASSERT(proto.StringSize() == 2); + UNIT_ASSERT(proto.GetString(0) == "value_str"); + UNIT_ASSERT(proto.GetString(1) == "str2"); + UNIT_ASSERT(proto.BytesSize() == 2); + UNIT_ASSERT(proto.GetBytes(0) == "transformed_bytes"); + UNIT_ASSERT(proto.GetBytes(1) == "transformed_bytes"); + } + + // Check that bytes are transformed, strings are left intact in composed messages + { + TString modelStr(R"_({"Part": {"String":"value_str", "Bytes": "value_bytes"}})_"); + + TCompositeOptional proto; + TJson2ProtoConfig config; + config.AddStringTransform(new TBytesTransform); + + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TCompositeOptional>(modelStr, config)); + UNIT_ASSERT(proto.GetPart().GetString() == "value_str"); + UNIT_ASSERT(proto.GetPart().GetBytes() == "transformed_bytes"); + } +} // TestStringTransforms + +Y_UNIT_TEST(TestCastFromString) { + // single fields + { + NJson::TJsonValue json; +#define DEFINE_FIELD(name, value) \ + json.InsertValue(#name, ConvertToString(value)); +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD + + TFlatOptional proto; + UNIT_ASSERT_EXCEPTION_CONTAINS(Json2Proto(json, proto), yexception, "Invalid type"); + + TJson2ProtoConfig config; + config.SetCastFromString(true); + Json2Proto(json, proto, config); + + TFlatOptional modelProto; + FillFlatProto(&modelProto); + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); + } + + // repeated fields + { + NJson::TJsonValue json; +#define DEFINE_REPEATED_FIELD(name, type, ...) \ + { \ + type values[] = {__VA_ARGS__}; \ + NJson::TJsonValue array(NJson::JSON_ARRAY); \ + for (size_t i = 0, end = Y_ARRAY_SIZE(values); i < end; ++i) { \ + array.AppendValue(ConvertToString(values[i])); \ + } \ + json.InsertValue(#name, array); \ + } +#include <library/cpp/protobuf/json/ut/repeated_fields.incl> +#undef DEFINE_REPEATED_FIELD + + TFlatRepeated proto; + UNIT_ASSERT_EXCEPTION_CONTAINS(Json2Proto(json, proto), yexception, "Invalid type"); + + TJson2ProtoConfig config; + config.SetCastFromString(true); + Json2Proto(json, proto, config); + + TFlatRepeated modelProto; + FillRepeatedProto(&modelProto); + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); + } +} // TestCastFromString + +Y_UNIT_TEST(TestMap) { + TMapType modelProto; + + auto& items = *modelProto.MutableItems(); + items["key1"] = "value1"; + items["key2"] = "value2"; + items["key3"] = "value3"; + + TString modelStr(R"_({"Items":[{"key":"key3","value":"value3"},{"key":"key2","value":"value2"},{"key":"key1","value":"value1"}]})_"); + + TJson2ProtoConfig config; + TMapType proto; + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TMapType>(modelStr, config)); + + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} // TestMap + +Y_UNIT_TEST(TestCastRobust) { + NJson::TJsonValue json; + json["I32"] = "5"; + json["Bool"] = 1; + json["String"] = 6; + json["Double"] = 8; + TFlatOptional proto; + UNIT_ASSERT_EXCEPTION_CONTAINS(Json2Proto(json, proto), yexception, "Invalid type"); + + TJson2ProtoConfig config; + config.SetCastRobust(true); + Json2Proto(json, proto, config); + + TFlatOptional expected; + expected.SetI32(5); + expected.SetBool(true); + expected.SetString("6"); + expected.SetDouble(8); + UNIT_ASSERT_PROTOS_EQUAL(proto, expected); +} + +Y_UNIT_TEST(TestVectorizeScalars) { + NJson::TJsonValue json; +#define DEFINE_FIELD(name, value) \ + json.InsertValue(#name, value); +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD + + TFlatRepeated proto; + TJson2ProtoConfig config; + config.SetVectorizeScalars(true); + Json2Proto(json, proto, config); + +#define DEFINE_FIELD(name, value) \ + UNIT_ASSERT_VALUES_EQUAL(proto.Get ## name(0), value); +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD +} + +Y_UNIT_TEST(TestValueVectorizer) { + { + // No ValueVectorizer + NJson::TJsonValue json; + json["RepeatedString"] = "123"; + TJson2ProtoConfig config; + TSingleRepeatedString expected; + UNIT_ASSERT_EXCEPTION(Json2Proto(json, expected, config), yexception); + } + { + // ValueVectorizer replace original value by array + NJson::TJsonValue json; + json["RepeatedString"] = "123"; + TJson2ProtoConfig config; + + TSingleRepeatedString expected; + expected.AddRepeatedString("4"); + expected.AddRepeatedString("5"); + expected.AddRepeatedString("6"); + + config.ValueVectorizer = [](const NJson::TJsonValue& val) -> NJson::TJsonValue::TArray { + Y_UNUSED(val); + return {NJson::TJsonValue("4"), NJson::TJsonValue("5"), NJson::TJsonValue("6")}; + }; + TSingleRepeatedString actual; + Json2Proto(json, actual, config); + UNIT_ASSERT_PROTOS_EQUAL(expected, actual); + } + { + // ValueVectorizer replace original value by array and cast + NJson::TJsonValue json; + json["RepeatedInt"] = 123; + TJson2ProtoConfig config; + + TSingleRepeatedInt expected; + expected.AddRepeatedInt(4); + expected.AddRepeatedInt(5); + expected.AddRepeatedInt(6); + + config.ValueVectorizer = [](const NJson::TJsonValue& val) -> NJson::TJsonValue::TArray { + Y_UNUSED(val); + return {NJson::TJsonValue("4"), NJson::TJsonValue(5), NJson::TJsonValue("6")}; + }; + config.CastFromString = true; + + TSingleRepeatedInt actual; + Json2Proto(json, actual, config); + UNIT_ASSERT_PROTOS_EQUAL(expected, actual); + } +} + +Y_UNIT_TEST(TestMapAsObject) { + TMapType modelProto; + + auto& items = *modelProto.MutableItems(); + items["key1"] = "value1"; + items["key2"] = "value2"; + items["key3"] = "value3"; + + TString modelStr(R"_({"Items":{"key1":"value1","key2":"value2","key3":"value3"}})_"); + + TJson2ProtoConfig config; + config.MapAsObject = true; + TMapType proto; + UNIT_ASSERT_NO_EXCEPTION(proto = Json2Proto<TMapType>(modelStr, config)); + + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} // TestMapAsObject + +Y_UNIT_TEST(TestComplexMapAsObject_I32) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableI32(); + items[1] = 1; + items[-2] = -2; + items[3] = 3; + }, + R"_({"I32":{"1":1,"-2":-2,"3":3}})_"); +} // TestComplexMapAsObject_I32 + +Y_UNIT_TEST(TestComplexMapAsObject_I64) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableI64(); + items[2147483649L] = 2147483649L; + items[-2147483650L] = -2147483650L; + items[2147483651L] = 2147483651L; + }, + R"_({"I64":{"2147483649":2147483649,"-2147483650":-2147483650,"2147483651":2147483651}})_"); +} // TestComplexMapAsObject_I64 + +Y_UNIT_TEST(TestComplexMapAsObject_UI32) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableUI32(); + items[1073741825U] = 1073741825U; + items[1073741826U] = 1073741826U; + items[1073741827U] = 1073741827U; + }, + R"_({"UI32":{"1073741825":1073741825,"1073741826":1073741826,"1073741827":1073741827}})_"); +} // TestComplexMapAsObject_UI32 + +Y_UNIT_TEST(TestComplexMapAsObject_UI64) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableUI64(); + items[9223372036854775809UL] = 9223372036854775809UL; + items[9223372036854775810UL] = 9223372036854775810UL; + items[9223372036854775811UL] = 9223372036854775811UL; + }, + R"_({"UI64":{"9223372036854775809":9223372036854775809,"9223372036854775810":9223372036854775810,"9223372036854775811":9223372036854775811}})_"); +} // TestComplexMapAsObject_UI64 + +Y_UNIT_TEST(TestComplexMapAsObject_SI32) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableSI32(); + items[1] = 1; + items[-2] = -2; + items[3] = 3; + }, + R"_({"SI32":{"1":1,"-2":-2,"3":3}})_"); +} // TestComplexMapAsObject_SI32 + +Y_UNIT_TEST(TestComplexMapAsObject_SI64) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableSI64(); + items[2147483649L] = 2147483649L; + items[-2147483650L] = -2147483650L; + items[2147483651L] = 2147483651L; + }, + R"_({"SI64":{"2147483649":2147483649,"-2147483650":-2147483650,"2147483651":2147483651}})_"); +} // TestComplexMapAsObject_SI64 + +Y_UNIT_TEST(TestComplexMapAsObject_FI32) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableFI32(); + items[1073741825U] = 1073741825U; + items[1073741826U] = 1073741826U; + items[1073741827U] = 1073741827U; + }, + R"_({"FI32":{"1073741825":1073741825,"1073741826":1073741826,"1073741827":1073741827}})_"); +} // TestComplexMapAsObject_FI32 + +Y_UNIT_TEST(TestComplexMapAsObject_FI64) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableFI64(); + items[9223372036854775809UL] = 9223372036854775809UL; + items[9223372036854775810UL] = 9223372036854775810UL; + items[9223372036854775811UL] = 9223372036854775811UL; + }, + R"_({"FI64":{"9223372036854775809":9223372036854775809,"9223372036854775810":9223372036854775810,"9223372036854775811":9223372036854775811}})_"); +} // TestComplexMapAsObject_FI64 + +Y_UNIT_TEST(TestComplexMapAsObject_SFI32) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableSFI32(); + items[1] = 1; + items[-2] = -2; + items[3] = 3; + }, + R"_({"SFI32":{"1":1,"-2":-2,"3":3}})_"); +} // TestComplexMapAsObject_SFI32 + +Y_UNIT_TEST(TestComplexMapAsObject_SFI64) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableSFI64(); + items[2147483649L] = 2147483649L; + items[-2147483650L] = -2147483650L; + items[2147483651L] = 2147483651L; + }, + R"_({"SFI64":{"2147483649":2147483649,"-2147483650":-2147483650,"2147483651":2147483651}})_"); +} // TestComplexMapAsObject_SFI64 + +Y_UNIT_TEST(TestComplexMapAsObject_Bool) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableBool(); + items[true] = true; + items[false] = false; + }, + R"_({"Bool":{"true":true,"false":false}})_"); +} // TestComplexMapAsObject_Bool + +Y_UNIT_TEST(TestComplexMapAsObject_String) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableString(); + items["key1"] = "value1"; + items["key2"] = "value2"; + items["key3"] = "value3"; + items[""] = "value4"; + }, + R"_({"String":{"key1":"value1","key2":"value2","key3":"value3","":"value4"}})_"); +} // TestComplexMapAsObject_String + +Y_UNIT_TEST(TestComplexMapAsObject_Enum) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableEnum(); + items["key1"] = EEnum::E_1; + items["key2"] = EEnum::E_2; + items["key3"] = EEnum::E_3; + }, + R"_({"Enum":{"key1":1,"key2":2,"key3":3}})_"); +} // TestComplexMapAsObject_Enum + +Y_UNIT_TEST(TestComplexMapAsObject_EnumString) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableEnum(); + items["key1"] = EEnum::E_1; + items["key2"] = EEnum::E_2; + items["key3"] = EEnum::E_3; + }, + R"_({"Enum":{"key1":"E_1","key2":"E_2","key3":"E_3"}})_"); +} // TestComplexMapAsObject_EnumString + +Y_UNIT_TEST(TestComplexMapAsObject_EnumStringCaseInsensetive) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableEnum(); + items["key1"] = EEnum::E_1; + items["key2"] = EEnum::E_2; + items["key3"] = EEnum::E_3; + }, + R"_({"Enum":{"key1":"e_1","key2":"E_2","key3":"e_3"}})_", + TJson2ProtoConfig() + .SetMapAsObject(true) + .SetEnumValueMode(NProtobufJson::TJson2ProtoConfig::EnumCaseInsensetive) + ); +} // TestComplexMapAsObject_EnumStringCaseInsensetive + +Y_UNIT_TEST(TestComplexMapAsObject_EnumStringSnakeCaseInsensitive) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableEnum(); + items["key1"] = EEnum::E_1; + items["key2"] = EEnum::E_2; + items["key3"] = EEnum::E_3; + }, + R"_({"Enum":{"key1":"e1","key2":"_E_2_","key3":"e_3"}})_", + TJson2ProtoConfig() + .SetMapAsObject(true) + .SetEnumValueMode(NProtobufJson::TJson2ProtoConfig::EnumSnakeCaseInsensitive) + ); +} // TestComplexMapAsObject_EnumStringCaseInsensetive + +Y_UNIT_TEST(TestComplexMapAsObject_Float) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableFloat(); + items["key1"] = 0.1f; + items["key2"] = 0.2f; + items["key3"] = 0.3f; + }, + R"_({"Float":{"key1":0.1,"key2":0.2,"key3":0.3}})_"); +} // TestComplexMapAsObject_Float + +Y_UNIT_TEST(TestComplexMapAsObject_Double) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + auto& items = *proto.MutableDouble(); + items["key1"] = 0.1L; + items["key2"] = 0.2L; + items["key3"] = 0.3L; + }, + R"_({"Double":{"key1":0.1,"key2":0.2,"key3":0.3}})_"); +} // TestComplexMapAsObject_Double + +Y_UNIT_TEST(TestComplexMapAsObject_Nested) { + TestComplexMapAsObject( + [](TComplexMapType& proto) { + TComplexMapType inner; + auto& innerItems = *inner.MutableString(); + innerItems["key"] = "value"; + auto& items = *proto.MutableNested(); + items["key1"] = inner; + items["key2"] = inner; + items["key3"] = inner; + }, + R"_({"Nested":{"key1":{"String":{"key":"value"}},"key2":{"String":{"key":"value"}},"key3":{"String":{"key":"value"}}}})_"); +} // TestComplexMapAsObject_Nested + +Y_UNIT_TEST(TestMapAsObjectConfigNotSet) { + TString modelStr(R"_({"Items":{"key":"value"}})_"); + + TJson2ProtoConfig config; + UNIT_ASSERT_EXCEPTION_CONTAINS( + Json2Proto<TMapType>(modelStr, config), yexception, + "Map as object representation is not allowed"); +} // TestMapAsObjectNotSet + +Y_UNIT_TEST(TestMergeFlatOptional) { + const NJson::TJsonValue& json = CreateFlatJson(); + + NJson::TJsonValue patch; + patch["I32"] = 5; + patch["Bool"] = false; + patch["String"] = "abacaba"; + patch["Double"] = 0.123; + + TFlatOptional proto; + UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto)); + UNIT_ASSERT_NO_EXCEPTION(MergeJson2Proto(patch, proto)); + + TFlatRequired modelProto; + FillFlatProto(&modelProto); + modelProto.SetI32(5); + modelProto.SetBool(false); + modelProto.SetString("abacaba"); + modelProto.SetDouble(0.123); + + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} // TestMergeFlatOptional + +Y_UNIT_TEST(TestMergeFlatRequired) { + const NJson::TJsonValue& json = CreateFlatJson(); + + NJson::TJsonValue patch; + patch["I32"] = 5; + patch["Bool"] = false; + patch["String"] = "abacaba"; + patch["Double"] = 0.123; + + TFlatRequired proto; + UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto)); + UNIT_ASSERT_NO_EXCEPTION(MergeJson2Proto(patch, proto)); + + TFlatRequired modelProto; + FillFlatProto(&modelProto); + modelProto.SetI32(5); + modelProto.SetBool(false); + modelProto.SetString("abacaba"); + modelProto.SetDouble(0.123); + + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} // TestMergeFlatRequired + +Y_UNIT_TEST(TestMergeComposite) { + const NJson::TJsonValue& json = CreateCompositeJson(); + + NJson::TJsonValue patch; + patch["Part"]["I32"] = 5; + patch["Part"]["Bool"] = false; + patch["Part"]["String"] = "abacaba"; + patch["Part"]["Double"] = 0.123; + + TCompositeOptional proto; + UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto)); + UNIT_ASSERT_NO_EXCEPTION(MergeJson2Proto(patch, proto)); + + TCompositeOptional modelProto; + FillCompositeProto(&modelProto); + modelProto.MutablePart()->SetI32(5); + modelProto.MutablePart()->SetBool(false); + modelProto.MutablePart()->SetString("abacaba"); + modelProto.MutablePart()->SetDouble(0.123); + + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} // TestMergeComposite + +Y_UNIT_TEST(TestMergeRepeatedReplace) { + const NJson::TJsonValue& json = CreateRepeatedFlatJson(); + + NJson::TJsonValue patch; + patch["I32"].AppendValue(5); + patch["I32"].AppendValue(6); + patch["String"].AppendValue("abacaba"); + + TFlatRepeated proto; + TJson2ProtoConfig config; + config.ReplaceRepeatedFields = true; + UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto)); + UNIT_ASSERT_NO_EXCEPTION(MergeJson2Proto(patch, proto, config)); + + TFlatRepeated modelProto; + FillRepeatedProto(&modelProto); + modelProto.ClearI32(); + modelProto.AddI32(5); + modelProto.AddI32(6); + modelProto.ClearString(); + modelProto.AddString("abacaba"); + + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} // TestMergeRepeatedReplace + +Y_UNIT_TEST(TestMergeRepeatedAppend) { + const NJson::TJsonValue& json = CreateRepeatedFlatJson(); + + NJson::TJsonValue patch; + patch["I32"].AppendValue(5); + patch["I32"].AppendValue(6); + patch["String"].AppendValue("abacaba"); + + TFlatRepeated proto; + UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto)); + UNIT_ASSERT_NO_EXCEPTION(MergeJson2Proto(patch, proto)); + + TFlatRepeated modelProto; + FillRepeatedProto(&modelProto); + modelProto.AddI32(5); + modelProto.AddI32(6); + modelProto.AddString("abacaba"); + + UNIT_ASSERT_PROTOS_EQUAL(proto, modelProto); +} // TestMergeRepeatedAppend + +Y_UNIT_TEST(TestEmptyStringForCastFromString) { + NJson::TJsonValue json; + json["I32"] = ""; + json["Bool"] = ""; + json["OneString"] = ""; + + TJson2ProtoConfig config; + config.SetCastFromString(true); + config.SetDoNotCastEmptyStrings(true); + TFlatOptional proto; + UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto, config)); + UNIT_ASSERT(!proto.HasBool()); + UNIT_ASSERT(!proto.HasI32()); + UNIT_ASSERT(proto.HasOneString()); + UNIT_ASSERT_EQUAL("", proto.GetOneString()); +} // TestEmptyStringForCastFromString + +Y_UNIT_TEST(TestAllowComments) { + constexpr TStringBuf json = R"( +{ + "I32": 4, // comment1 +/* + comment2 + {} + qwer +*/ + "I64": 3423 +} + +)"; + + TJson2ProtoConfig config; + TFlatOptional proto; + UNIT_ASSERT_EXCEPTION_CONTAINS(Json2Proto(json, proto, config), yexception, "Error: Missing a name for object member"); + + config.SetAllowComments(true); + UNIT_ASSERT_NO_EXCEPTION(Json2Proto(json, proto, config)); + UNIT_ASSERT_VALUES_EQUAL(proto.GetI32(), 4); + UNIT_ASSERT_VALUES_EQUAL(proto.GetI64(), 3423); +} // TestAllowComments + +} // TJson2ProtoTest diff --git a/library/cpp/protobuf/json/ut/proto.h b/library/cpp/protobuf/json/ut/proto.h new file mode 100644 index 0000000000..8183bfc8e1 --- /dev/null +++ b/library/cpp/protobuf/json/ut/proto.h @@ -0,0 +1,62 @@ +#pragma once + +#include <util/generic/hash_set.h> +#include <util/generic/string.h> + +#include <util/system/defaults.h> + +namespace NProtobufJsonTest { + template <typename TProto> + inline void + FillFlatProto(TProto* proto, + const THashSet<TString>& skippedFields = THashSet<TString>()) { +#define DEFINE_FIELD(name, value) \ + if (skippedFields.find(#name) == skippedFields.end()) \ + proto->Set##name(value); +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD + } + + template <typename TRepeatedField, typename TValue> + inline void + AddValue(TRepeatedField* field, TValue value) { + field->Add(value); + } + + inline void + AddValue(google::protobuf::RepeatedPtrField<TString>* field, const TString& value) { + *(field->Add()) = value; + } + + inline void + FillRepeatedProto(TFlatRepeated* proto, + const THashSet<TString>& skippedFields = THashSet<TString>()) { +#define DEFINE_REPEATED_FIELD(name, type, ...) \ + if (skippedFields.find(#name) == skippedFields.end()) { \ + type values[] = {__VA_ARGS__}; \ + for (size_t i = 0, end = Y_ARRAY_SIZE(values); i < end; ++i) { \ + AddValue(proto->Mutable##name(), values[i]); \ + } \ + } +#include <library/cpp/protobuf/json/ut/repeated_fields.incl> +#undef DEFINE_REPEATED_FIELD + } + + template <typename TProto> + inline void + FillCompositeProto(TProto* proto, const THashSet<TString>& skippedFields = THashSet<TString>()) { + FillFlatProto(proto->MutablePart(), skippedFields); + } + +#define UNIT_ASSERT_PROTOS_EQUAL(lhs, rhs) \ + do { \ + if (lhs.SerializeAsString() != rhs.SerializeAsString()) { \ + Cerr << ">>>>>>>>>> lhs != rhs:" << Endl; \ + Cerr << lhs.DebugString() << Endl; \ + Cerr << rhs.DebugString() << Endl; \ + UNIT_ASSERT_STRINGS_EQUAL(lhs.DebugString(), rhs.DebugString()); \ + UNIT_ASSERT_STRINGS_EQUAL(lhs.SerializeAsString(), rhs.SerializeAsString()); \ + } \ + } while (false); + +} diff --git a/library/cpp/protobuf/json/ut/proto2json_ut.cpp b/library/cpp/protobuf/json/ut/proto2json_ut.cpp new file mode 100644 index 0000000000..07e52d7f2f --- /dev/null +++ b/library/cpp/protobuf/json/ut/proto2json_ut.cpp @@ -0,0 +1,1022 @@ +#include "json.h" +#include "proto.h" + +#include <library/cpp/protobuf/json/ut/test.pb.h> + +#include <library/cpp/json/json_value.h> +#include <library/cpp/json/json_reader.h> +#include <library/cpp/json/json_writer.h> + +#include <library/cpp/protobuf/json/proto2json.h> + +#include <library/cpp/testing/unittest/registar.h> + +#include <util/generic/hash_set.h> +#include <util/generic/string.h> +#include <util/generic/ylimits.h> + +#include <util/stream/str.h> + +#include <util/system/defaults.h> +#include <util/system/yassert.h> + +#include <limits> + +using namespace NProtobufJson; +using namespace NProtobufJsonTest; + +Y_UNIT_TEST_SUITE(TProto2JsonFlatTest) { + Y_UNIT_TEST(TestFlatDefault) { + using namespace ::google::protobuf; + TFlatDefault proto; + NJson::TJsonValue json; + TProto2JsonConfig cfg; + cfg.SetMissingSingleKeyMode(TProto2JsonConfig::MissingKeyDefault); + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, cfg)); +#define DEFINE_FIELD(name, value) \ + { \ + auto descr = proto.GetMetadata().descriptor->FindFieldByName(#name); \ + UNIT_ASSERT(descr); \ + UNIT_ASSERT(json.Has(#name)); \ + switch (descr->cpp_type()) { \ + case FieldDescriptor::CPPTYPE_INT32: \ + UNIT_ASSERT(descr->default_value_int32() == json[#name].GetIntegerRobust()); \ + break; \ + case FieldDescriptor::CPPTYPE_INT64: \ + UNIT_ASSERT(descr->default_value_int64() == json[#name].GetIntegerRobust()); \ + break; \ + case FieldDescriptor::CPPTYPE_UINT32: \ + UNIT_ASSERT(descr->default_value_uint32() == json[#name].GetUIntegerRobust()); \ + break; \ + case FieldDescriptor::CPPTYPE_UINT64: \ + UNIT_ASSERT(descr->default_value_uint32() == json[#name].GetUIntegerRobust()); \ + break; \ + case FieldDescriptor::CPPTYPE_DOUBLE: \ + UNIT_ASSERT(descr->default_value_double() == json[#name].GetDoubleRobust()); \ + break; \ + case FieldDescriptor::CPPTYPE_FLOAT: \ + UNIT_ASSERT(descr->default_value_float() == json[#name].GetDoubleRobust()); \ + break; \ + case FieldDescriptor::CPPTYPE_BOOL: \ + UNIT_ASSERT(descr->default_value_bool() == json[#name].GetBooleanRobust()); \ + break; \ + case FieldDescriptor::CPPTYPE_ENUM: \ + UNIT_ASSERT(descr->default_value_enum()->number() == json[#name].GetIntegerRobust()); \ + break; \ + case FieldDescriptor::CPPTYPE_STRING: \ + UNIT_ASSERT(descr->default_value_string() == json[#name].GetStringRobust()); \ + break; \ + default: \ + break; \ + } \ + } +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD + } + + Y_UNIT_TEST(TestNameGenerator) { + TNameGeneratorType proto; + proto.SetField(42); + + TProto2JsonConfig cfg; + cfg.SetNameGenerator([](const NProtoBuf::FieldDescriptor&) { return "42"; }); + + TStringStream str; + Proto2Json(proto, str, cfg); + + UNIT_ASSERT_STRINGS_EQUAL(R"({"42":42})", str.Str()); + } + + Y_UNIT_TEST(TestEnumValueGenerator) { + TEnumValueGeneratorType proto; + proto.SetEnum(TEnumValueGeneratorType::ENUM_42); + + TProto2JsonConfig cfg; + cfg.SetEnumValueGenerator([](const NProtoBuf::EnumValueDescriptor&) { return "42"; }); + + TStringStream str; + Proto2Json(proto, str, cfg); + + UNIT_ASSERT_STRINGS_EQUAL(R"({"Enum":"42"})", str.Str()); + } + + Y_UNIT_TEST(TestFlatOptional){ + {TFlatOptional proto; + FillFlatProto(&proto); + const NJson::TJsonValue& modelJson = CreateFlatJson(); + { + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + + { + TStringStream jsonStream; + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } // streamed +} + + // Try to skip each field +#define DEFINE_FIELD(name, value) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + TFlatOptional proto; \ + FillFlatProto(&proto, skippedField); \ + const NJson::TJsonValue& modelJson = CreateFlatJson(skippedField); \ + NJson::TJsonValue json; \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); \ + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \ + { \ + TStringStream jsonStream; \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); \ + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); \ + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \ + } \ + } +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD +} // TestFlatOptional + +Y_UNIT_TEST(TestFlatRequired){ + {TFlatRequired proto; +FillFlatProto(&proto); +const NJson::TJsonValue& modelJson = CreateFlatJson(); +{ + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); +} + +{ + TStringStream jsonStream; + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); +} // streamed +} + +// Try to skip each field +#define DEFINE_FIELD(name, value) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + TFlatRequired proto; \ + FillFlatProto(&proto, skippedField); \ + const NJson::TJsonValue& modelJson = CreateFlatJson(skippedField); \ + NJson::TJsonValue json; \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); \ + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \ + { \ + TStringStream jsonStream; \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); \ + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); \ + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \ + } \ + } +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD +} // TestFlatRequired + +Y_UNIT_TEST(TestFlatRepeated) { + { + TFlatRepeated proto; + FillRepeatedProto(&proto); + const NJson::TJsonValue& modelJson = CreateRepeatedFlatJson(); + { + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + + { + TStringStream jsonStream; + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } // streamed + } + + TProto2JsonConfig config; + config.SetMissingRepeatedKeyMode(TProto2JsonConfig::MissingKeySkip); + + // Try to skip each field +#define DEFINE_REPEATED_FIELD(name, ...) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + TFlatRepeated proto; \ + FillRepeatedProto(&proto, skippedField); \ + const NJson::TJsonValue& modelJson = CreateRepeatedFlatJson(skippedField); \ + NJson::TJsonValue json; \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); \ + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \ + { \ + TStringStream jsonStream; \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream, config)); \ + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); \ + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \ + } \ + } +#include <library/cpp/protobuf/json/ut/repeated_fields.incl> +#undef DEFINE_REPEATED_FIELD +} // TestFlatRepeated + +Y_UNIT_TEST(TestCompositeOptional){ + {TCompositeOptional proto; +FillCompositeProto(&proto); +const NJson::TJsonValue& modelJson = CreateCompositeJson(); +{ + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); +} + +{ + TStringStream jsonStream; + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); +} // streamed +} + +// Try to skip each field +#define DEFINE_FIELD(name, value) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + TCompositeOptional proto; \ + FillCompositeProto(&proto, skippedField); \ + const NJson::TJsonValue& modelJson = CreateCompositeJson(skippedField); \ + NJson::TJsonValue json; \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); \ + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \ + { \ + TStringStream jsonStream; \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); \ + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); \ + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \ + } \ + } +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD +} // TestCompositeOptional + +Y_UNIT_TEST(TestCompositeRequired){ + {TCompositeRequired proto; +FillCompositeProto(&proto); +const NJson::TJsonValue& modelJson = CreateCompositeJson(); +{ + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); +} + +{ + TStringStream jsonStream; + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); +} // streamed +} + +// Try to skip each field +#define DEFINE_FIELD(name, value) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + TCompositeRequired proto; \ + FillCompositeProto(&proto, skippedField); \ + const NJson::TJsonValue& modelJson = CreateCompositeJson(skippedField); \ + NJson::TJsonValue json; \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); \ + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \ + { \ + TStringStream jsonStream; \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); \ + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); \ + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); \ + } \ + } +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD +} // TestCompositeRequired + +Y_UNIT_TEST(TestCompositeRepeated) { + { + TFlatOptional partProto; + FillFlatProto(&partProto); + TCompositeRepeated proto; + proto.AddPart()->CopyFrom(partProto); + + NJson::TJsonValue modelJson; + NJson::TJsonValue modelArray; + modelArray.AppendValue(CreateFlatJson()); + modelJson.InsertValue("Part", modelArray); + { + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + + { + TStringStream jsonStream; + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } // streamed + } + + { + // Array of messages with each field skipped + TCompositeRepeated proto; + NJson::TJsonValue modelArray; + +#define DEFINE_REPEATED_FIELD(name, ...) \ + { \ + THashSet<TString> skippedField; \ + skippedField.insert(#name); \ + TFlatOptional partProto; \ + FillFlatProto(&partProto, skippedField); \ + proto.AddPart()->CopyFrom(partProto); \ + modelArray.AppendValue(CreateFlatJson(skippedField)); \ + } +#include <library/cpp/protobuf/json/ut/repeated_fields.incl> +#undef DEFINE_REPEATED_FIELD + + NJson::TJsonValue modelJson; + modelJson.InsertValue("Part", modelArray); + + { + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + + { + TStringStream jsonStream; + NJson::TJsonValue json; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStream)); + UNIT_ASSERT(ReadJsonTree(&jsonStream, &json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } // streamed + } +} // TestCompositeRepeated + +Y_UNIT_TEST(TestEnumConfig) { + { + TFlatOptional proto; + proto.SetEnum(E_1); + NJson::TJsonValue modelJson; + modelJson.InsertValue("Enum", 1); + NJson::TJsonValue json; + TProto2JsonConfig config; + config.EnumMode = TProto2JsonConfig::EnumNumber; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + + { + TFlatOptional proto; + proto.SetEnum(E_1); + NJson::TJsonValue modelJson; + modelJson.InsertValue("Enum", "E_1"); + NJson::TJsonValue json; + TProto2JsonConfig config; + config.EnumMode = TProto2JsonConfig::EnumName; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + + { + TFlatOptional proto; + proto.SetEnum(E_1); + NJson::TJsonValue modelJson; + modelJson.InsertValue("Enum", "NProtobufJsonTest.E_1"); + NJson::TJsonValue json; + TProto2JsonConfig config; + config.EnumMode = TProto2JsonConfig::EnumFullName; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + + { + TFlatOptional proto; + proto.SetEnum(E_1); + NJson::TJsonValue modelJson; + modelJson.InsertValue("Enum", "e_1"); + NJson::TJsonValue json; + TProto2JsonConfig config; + config.EnumMode = TProto2JsonConfig::EnumNameLowerCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + + { + TFlatOptional proto; + proto.SetEnum(E_1); + NJson::TJsonValue modelJson; + modelJson.InsertValue("Enum", "nprotobufjsontest.e_1"); + NJson::TJsonValue json; + TProto2JsonConfig config; + config.EnumMode = TProto2JsonConfig::EnumFullNameLowerCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } +} // TestEnumConfig + +Y_UNIT_TEST(TestMissingSingleKeyConfig) { + { + TFlatOptional proto; + NJson::TJsonValue modelJson(NJson::JSON_MAP); + NJson::TJsonValue json; + TProto2JsonConfig config; + config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeySkip; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + + { + NJson::TJsonValue modelJson; +#define DEFINE_FIELD(name, value) \ + modelJson.InsertValue(#name, NJson::TJsonValue(NJson::JSON_NULL)); +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD + + TFlatOptional proto; + NJson::TJsonValue json; + TProto2JsonConfig config; + config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyNull; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + { + // Test MissingKeyExplicitDefaultThrowRequired for non explicit default values. + TFlatOptional proto; + NJson::TJsonValue modelJson(NJson::JSON_MAP); + NJson::TJsonValue json; + TProto2JsonConfig config; + config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + { + // Test MissingKeyExplicitDefaultThrowRequired for explicit default values. + NJson::TJsonValue modelJson; + modelJson["String"] = "value"; + + TSingleDefaultString proto; + NJson::TJsonValue json; + TProto2JsonConfig config; + config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + { + // Test MissingKeyExplicitDefaultThrowRequired for empty required values. + TFlatRequired proto; + NJson::TJsonValue json; + TProto2JsonConfig config; + config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired; + UNIT_ASSERT_EXCEPTION_CONTAINS(Proto2Json(proto, json, config), yexception, "Empty required protobuf field"); + } + { + // Test MissingKeyExplicitDefaultThrowRequired for required value. + TSingleRequiredString proto; + NJson::TJsonValue json; + TProto2JsonConfig config; + config.MissingSingleKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired; + + UNIT_ASSERT_EXCEPTION_CONTAINS(Proto2Json(proto, json, config), yexception, "Empty required protobuf field"); + + NJson::TJsonValue modelJson; + modelJson["String"] = "value"; + proto.SetString("value"); + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } +} // TestMissingSingleKeyConfig + +Y_UNIT_TEST(TestMissingRepeatedKeyNoConfig) { + { + TFlatRepeated proto; + NJson::TJsonValue modelJson(NJson::JSON_MAP); + NJson::TJsonValue json; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } +} // TestMissingRepeatedKeyNoConfig + +Y_UNIT_TEST(TestMissingRepeatedKeyConfig) { + { + TFlatRepeated proto; + NJson::TJsonValue modelJson(NJson::JSON_MAP); + NJson::TJsonValue json; + TProto2JsonConfig config; + config.MissingRepeatedKeyMode = TProto2JsonConfig::MissingKeySkip; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + + { + NJson::TJsonValue modelJson; +#define DEFINE_FIELD(name, value) \ + modelJson.InsertValue(#name, NJson::TJsonValue(NJson::JSON_NULL)); +#include <library/cpp/protobuf/json/ut/fields.incl> +#undef DEFINE_FIELD + + TFlatRepeated proto; + NJson::TJsonValue json; + TProto2JsonConfig config; + config.MissingRepeatedKeyMode = TProto2JsonConfig::MissingKeyNull; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } + { + TFlatRepeated proto; + NJson::TJsonValue modelJson(NJson::JSON_MAP); + NJson::TJsonValue json; + TProto2JsonConfig config; + config.MissingRepeatedKeyMode = TProto2JsonConfig::MissingKeyExplicitDefaultThrowRequired; + + // SHould be same as MissingKeySkip + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, json, config)); + UNIT_ASSERT_JSONS_EQUAL(json, modelJson); + } +} // TestMissingRepeatedKeyConfig + +Y_UNIT_TEST(TestEscaping) { + // No escape + { + TString modelStr(R"_({"String":"value\""})_"); + + TFlatOptional proto; + proto.SetString(R"_(value")_"); + TStringStream jsonStr; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // TEscapeJTransform + { + TString modelStr(R"_({"String":"value\""})_"); + + TFlatOptional proto; + proto.SetString(R"_(value")_"); + TProto2JsonConfig config; + config.StringTransforms.push_back(new TEscapeJTransform<false, true>()); + TStringStream jsonStr; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(modelStr, jsonStr.Str()); + } + + // TCEscapeTransform + { + TString modelStr(R"_({"String":"value\""})_"); + + TFlatOptional proto; + proto.SetString(R"_(value")_"); + TProto2JsonConfig config; + config.StringTransforms.push_back(new TCEscapeTransform()); + TStringStream jsonStr; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // TSafeUtf8CEscapeTransform + { + TString modelStr(R"_({"String":"value\""})_"); + + TFlatOptional proto; + proto.SetString(R"_(value")_"); + TProto2JsonConfig config; + config.StringTransforms.push_back(new TSafeUtf8CEscapeTransform()); + TStringStream jsonStr; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } +} // TestEscaping + +class TBytesTransform: public IStringTransform { +public: + int GetType() const override { + return 0; + } + void Transform(TString&) const override { + } + void TransformBytes(TString& str) const override { + str = "bytes"; + } +}; + +Y_UNIT_TEST(TestBytesTransform) { + // Test that string field is not changed + { + TString modelStr(R"_({"String":"value"})_"); + + TFlatOptional proto; + proto.SetString(R"_(value)_"); + TProto2JsonConfig config; + config.StringTransforms.push_back(new TBytesTransform()); + TStringStream jsonStr; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // Test that bytes field is changed + { + TString modelStr(R"_({"Bytes":"bytes"})_"); + + TFlatOptional proto; + proto.SetBytes(R"_(value)_"); + TProto2JsonConfig config; + config.StringTransforms.push_back(new TBytesTransform()); + TStringStream jsonStr; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } +} + +Y_UNIT_TEST(TestFieldNameMode) { + // Original case 1 + { + TString modelStr(R"_({"String":"value"})_"); + + TFlatOptional proto; + proto.SetString("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // Original case 2 + { + TString modelStr(R"_({"String":"value"})_"); + + TFlatOptional proto; + proto.SetString("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameOriginalCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // Lowercase + { + TString modelStr(R"_({"string":"value"})_"); + + TFlatOptional proto; + proto.SetString("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameLowerCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // Uppercase + { + TString modelStr(R"_({"STRING":"value"})_"); + + TFlatOptional proto; + proto.SetString("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameUpperCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // Camelcase + { + TString modelStr(R"_({"string":"value"})_"); + + TFlatOptional proto; + proto.SetString("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameCamelCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + { + TString modelStr(R"_({"oneString":"value"})_"); + + TFlatOptional proto; + proto.SetOneString("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameCamelCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + { + TString modelStr(R"_({"oneTwoString":"value"})_"); + + TFlatOptional proto; + proto.SetOneTwoString("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameCamelCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // snake_case + { + TString modelStr(R"_({"string":"value"})_"); + + TFlatOptional proto; + proto.SetString("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + { + TString modelStr(R"_({"one_string":"value"})_"); + + TFlatOptional proto; + proto.SetOneString("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + { + TString modelStr(R"_({"one_two_string":"value"})_"); + + TFlatOptional proto; + proto.SetOneTwoString("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + { + TString modelStr(R"_({"a_b_c":"value","user_i_d":"value"})_"); + + TFlatOptional proto; + proto.SetABC("value"); + proto.SetUserID("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // snake_case_dense + { + TString modelStr(R"_({"abc":"value","user_id":"value"})_"); + + TFlatOptional proto; + proto.SetABC("value"); + proto.SetUserID("value"); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameSnakeCaseDense; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // Original case, repeated + { + TString modelStr(R"_({"I32":[1,2]})_"); + + TFlatRepeated proto; + proto.AddI32(1); + proto.AddI32(2); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameOriginalCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // Lower case, repeated + { + TString modelStr(R"_({"i32":[1,2]})_"); + + TFlatRepeated proto; + proto.AddI32(1); + proto.AddI32(2); + TStringStream jsonStr; + TProto2JsonConfig config; + config.FieldNameMode = TProto2JsonConfig::FieldNameLowerCase; + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // UseJsonName + { + // FIXME(CONTRIB-139): see the comment about UseJsonName in json2proto_ut.cpp: + // Def_upper json name should be "DefUpper". + TString modelStr(R"_({"My-Upper":1,"my-lower":2,"defUpper":3,"defLower":4})_"); + + TWithJsonName proto; + proto.Setmy_upper(1); + proto.SetMy_lower(2); + proto.SetDef_upper(3); + proto.Setdef_lower(4); + TStringStream jsonStr; + TProto2JsonConfig config; + config.SetUseJsonName(true); + + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + UNIT_ASSERT_STRINGS_EQUAL(jsonStr.Str(), modelStr); + } + + // FieldNameMode with UseJsonName + { + TProto2JsonConfig config; + config.SetFieldNameMode(TProto2JsonConfig::FieldNameLowerCase); + UNIT_ASSERT_EXCEPTION_CONTAINS( + config.SetUseJsonName(true), yexception, "mutually exclusive"); + } + { + TProto2JsonConfig config; + config.SetUseJsonName(true); + UNIT_ASSERT_EXCEPTION_CONTAINS( + config.SetFieldNameMode(TProto2JsonConfig::FieldNameLowerCase), yexception, "mutually exclusive"); + } + + /// TODO: test missing keys +} // TestFieldNameMode + +Y_UNIT_TEST(TestNan) { + TFlatOptional proto; + proto.SetDouble(std::numeric_limits<double>::quiet_NaN()); + + UNIT_ASSERT_EXCEPTION(Proto2Json(proto, TProto2JsonConfig()), yexception); +} // TestNan + +Y_UNIT_TEST(TestInf) { + TFlatOptional proto; + proto.SetFloat(std::numeric_limits<float>::infinity()); + + UNIT_ASSERT_EXCEPTION(Proto2Json(proto, TProto2JsonConfig()), yexception); +} // TestInf + +Y_UNIT_TEST(TestMap) { + TMapType proto; + + auto& items = *proto.MutableItems(); + items["key1"] = "value1"; + items["key2"] = "value2"; + items["key3"] = "value3"; + + TString modelStr(R"_({"Items":[{"key":"key3","value":"value3"},{"key":"key2","value":"value2"},{"key":"key1","value":"value1"}]})_"); + + TStringStream jsonStr; + TProto2JsonConfig config; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + + NJson::TJsonValue jsonValue, modelValue; + NJson::TJsonValue::TArray jsonItems, modelItems; + UNIT_ASSERT(NJson::ReadJsonTree(jsonStr.Str(), &jsonValue)); + UNIT_ASSERT(NJson::ReadJsonTree(modelStr, &modelValue)); + UNIT_ASSERT(jsonValue.Has("Items")); + jsonValue["Items"].GetArray(&jsonItems); + modelValue["Items"].GetArray(&modelItems); + auto itemKey = [](const NJson::TJsonValue& v) { + return v["key"].GetString(); + }; + SortBy(jsonItems, itemKey); + SortBy(modelItems, itemKey); + UNIT_ASSERT_EQUAL(jsonItems, modelItems); +} // TestMap + +Y_UNIT_TEST(TestMapAsObject) { + TMapType proto; + + auto& items = *proto.MutableItems(); + items["key1"] = "value1"; + items["key2"] = "value2"; + items["key3"] = "value3"; + + TString modelStr(R"_({"Items":{"key3":"value3","key2":"value2","key1":"value1"}})_"); + + TStringStream jsonStr; + TProto2JsonConfig config; + config.MapAsObject = true; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); + + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); +} // TestMapAsObject + +Y_UNIT_TEST(TestMapWTF) { + TMapType proto; + + auto& items = *proto.MutableItems(); + items["key1"] = "value1"; + items["key2"] = "value2"; + items["key3"] = "value3"; + + TString modelStr(R"_({"Items":{"key3":"value3","key2":"value2","key1":"value1"}})_"); + + TStringStream jsonStr; + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr)); + + UNIT_ASSERT_JSON_STRINGS_EQUAL(jsonStr.Str(), modelStr); +} // TestMapWTF + +Y_UNIT_TEST(TestStringifyLongNumbers) { +#define TEST_SINGLE(flag, value, expectString) \ + do { \ + TFlatOptional proto; \ + proto.SetSI64(value); \ + \ + TStringStream jsonStr; \ + TProto2JsonConfig config; \ + config.SetStringifyLongNumbers(flag); \ + UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); \ + if (expectString) { \ + UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"SI64\":\"" #value "\"}"); \ + } else { \ + UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"SI64\":" #value "}"); \ + } \ + } while (false) + + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, 1, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, 1000000000, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, 10000000000000000, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, -1, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, -1000000000, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, -10000000000000000, false); + + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, 1, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, 1000000000, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, 10000000000000000, true); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, -1, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, -1000000000, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, -10000000000000000, true); + + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, 1, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, 1000000000, true); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, 10000000000000000, true); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, -1, false); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, -1000000000, true); + TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, -10000000000000000, true); + +#undef TEST_SINGLE +} // TestStringifyLongNumbers + +Y_UNIT_TEST(TestExtension) { + TExtensionField proto; + proto.SetExtension(bar, 1); + + Y_ASSERT(proto.HasExtension(bar)); + UNIT_ASSERT_EQUAL(Proto2Json(proto, TProto2JsonConfig()), "{\"NProtobufJsonTest.bar\":1}"); + + + TProto2JsonConfig cfg; + cfg.SetExtensionFieldNameMode(TProto2JsonConfig::ExtFldNameShort); + UNIT_ASSERT_EQUAL(Proto2Json(proto, cfg), "{\"bar\":1}"); +} // TestExtension + +} // TProto2JsonTest diff --git a/library/cpp/protobuf/json/ut/repeated_fields.incl b/library/cpp/protobuf/json/ut/repeated_fields.incl new file mode 100644 index 0000000000..e9548917d8 --- /dev/null +++ b/library/cpp/protobuf/json/ut/repeated_fields.incl @@ -0,0 +1,21 @@ +// Intentionally no #pragma once + +// (Field name == JSON key, Type, Values...) +DEFINE_REPEATED_FIELD(I32, i32, Min<i32>(), -1, 0, 1, Max<i32>()) +DEFINE_REPEATED_FIELD(I64, i64, Min<i64>(), -1ll, 0ll, 1ll, Max<i64>()) +DEFINE_REPEATED_FIELD(UI32, ui32, 0ul, 1ul, Max<ui32>()) +DEFINE_REPEATED_FIELD(UI64, ui64, 0ull, 1ull, Max<ui64>()) +DEFINE_REPEATED_FIELD(SI32, i32, Min<i32>(), -1, 0, 1, Max<i32>()) +DEFINE_REPEATED_FIELD(SI64, i64, Min<i64>(), -1ll, 0ll, 1ll, Max<i64>()) +DEFINE_REPEATED_FIELD(FI32, ui32, 0, 1, Max<ui32>()) +DEFINE_REPEATED_FIELD(FI64, ui64, 0ull, 1ull, Max<ui64>()) +DEFINE_REPEATED_FIELD(SFI32, i32, Min<i32>(), -1, 0, 1, Max<i32>()) +DEFINE_REPEATED_FIELD(SFI64, i64, Min<i64>(), -1ll, 0ll, 1ll, Max<i64>()) +DEFINE_REPEATED_FIELD(Bool, bool, false, true) +DEFINE_REPEATED_FIELD(String, TString, "", "Lorem ipsum", "123123") +DEFINE_REPEATED_FIELD(Bytes, TString, "", "מחשב", "\x1") +DEFINE_REPEATED_FIELD(Enum, EEnum, E_1, E_2, E_3) +DEFINE_REPEATED_FIELD(Float, float, 0.0f, 1.0f, 1.123f) +DEFINE_REPEATED_FIELD(Double, double, 0.0, 1.0, 1.123456789012) +DEFINE_REPEATED_FIELD(OneString, TString, "", "Lorem ipsum dolor", "1231231") +DEFINE_REPEATED_FIELD(OneTwoString, TString, "", "Lorem ipsum dolor sit", "12312312") diff --git a/library/cpp/protobuf/json/ut/string_transform_ut.cpp b/library/cpp/protobuf/json/ut/string_transform_ut.cpp new file mode 100644 index 0000000000..a31dabcb0f --- /dev/null +++ b/library/cpp/protobuf/json/ut/string_transform_ut.cpp @@ -0,0 +1,106 @@ +#include "json.h" + +#include <library/cpp/testing/unittest/registar.h> +#include <library/cpp/protobuf/json/proto2json.h> + +Y_UNIT_TEST_SUITE(TDoubleEscapeTransform) { + Y_UNIT_TEST(TestEmptyString) { + const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleEscapeTransform(); + TString s; + s = ""; + transform.Transform(s); + UNIT_ASSERT_EQUAL(s, ""); + } + + Y_UNIT_TEST(TestAlphabeticString) { + const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleEscapeTransform(); + TString s; + s = "abacaba"; + transform.Transform(s); + UNIT_ASSERT_EQUAL(s, "abacaba"); + } + + Y_UNIT_TEST(TestRussianSymbols) { + const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleEscapeTransform(); + TString s; + s = "тест"; + transform.Transform(s); + UNIT_ASSERT_EQUAL(s, "\\\\321\\\\202\\\\320\\\\265\\\\321\\\\201\\\\321\\\\202"); + } + + Y_UNIT_TEST(TestEscapeSpecialSymbols) { + const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleEscapeTransform(); + TString s; + s = "aba\\ca\"ba"; + transform.Transform(s); + Cerr << "###" << s << Endl; + UNIT_ASSERT_EQUAL(s, "aba\\\\\\\\ca\\\\\\\"ba"); + } +} + +Y_UNIT_TEST_SUITE(TDoubleUnescapeTransform) { + Y_UNIT_TEST(TestEmptyString) { + const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleUnescapeTransform(); + TString s; + s = ""; + transform.Transform(s); + UNIT_ASSERT_EQUAL("", s); + } + + Y_UNIT_TEST(TestAlphabeticString) { + const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleUnescapeTransform(); + TString s; + s = "abacaba"; + transform.Transform(s); + Cerr << "###" << s << Endl; + UNIT_ASSERT_EQUAL("abacaba", s); + } + + Y_UNIT_TEST(TestRussianSymbols) { + const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleUnescapeTransform(); + TString s; + s = "\\\\321\\\\202\\\\320\\\\265\\\\321\\\\201\\\\321\\\\202"; + transform.Transform(s); + UNIT_ASSERT_EQUAL("тест", s); + } + + Y_UNIT_TEST(TestEscapeSpecialSymbols) { + const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleUnescapeTransform(); + TString s; + s = "aba\\\\\\\\ca\\\\\\\"ba"; + transform.Transform(s); + UNIT_ASSERT_EQUAL("aba\\ca\"ba", s); + } + + Y_UNIT_TEST(TestEscapeSpecialSymbolsDifficultCases) { + const NProtobufJson::IStringTransform& transform = NProtobufJson::TDoubleUnescapeTransform(); + TString s; + s = "\\\\\\\\\\\\\\\\"; + transform.Transform(s); + UNIT_ASSERT_EQUAL("\\\\", s); + + s = "\\\\\\\\\\\\\\\""; + transform.Transform(s); + UNIT_ASSERT_EQUAL("\\\"", s); + + s = "\\\\\\\"\\\\\\\\"; + transform.Transform(s); + UNIT_ASSERT_EQUAL("\"\\", s); + + s = "\\\\\\\"\\\\\\\""; + transform.Transform(s); + UNIT_ASSERT_EQUAL("\"\"", s); + + s = "\\\\\\\\\\\\\\\\\\\\\\\\"; + transform.Transform(s); + UNIT_ASSERT_EQUAL("\\\\\\", s); + + s = "\\\\\\\\\\\\\\\\\\\\\\\\abacaba\\\\"; + transform.Transform(s); + UNIT_ASSERT_EQUAL("\\\\\\abacaba", s); + + s = "\\\\\\\\\\\\\\\\\\\\\\\\abacaba\\\""; + transform.Transform(s); + UNIT_ASSERT_EQUAL("\\\\\\abacaba\"", s); + } +} diff --git a/library/cpp/protobuf/json/ut/test.proto b/library/cpp/protobuf/json/ut/test.proto new file mode 100644 index 0000000000..0fa996fd41 --- /dev/null +++ b/library/cpp/protobuf/json/ut/test.proto @@ -0,0 +1,203 @@ +package NProtobufJsonTest; + +enum EEnum { + E_0 = 0; + E_1 = 1; + E_2 = 2; + E_3 = 3; +}; + +message TFlatOptional { + optional int32 I32 = 1; + optional int64 I64 = 2; + optional uint32 UI32 = 3; + optional uint64 UI64 = 4; + optional sint32 SI32 = 5; + optional sint64 SI64 = 6; + optional fixed32 FI32 = 7; + optional fixed64 FI64 = 8; + optional sfixed32 SFI32 = 9; + optional sfixed64 SFI64 = 10; + + optional bool Bool = 11; + + optional string String = 12; + optional bytes Bytes = 13; + + optional EEnum Enum = 14; + + optional float Float = 15; + optional double Double = 16; + + optional string OneString = 17; + optional string OneTwoString = 18; + optional string ABC = 19; + optional string UserID = 20; +}; + +message TFlatRequired { + required int32 I32 = 1; + required int64 I64 = 2; + required uint32 UI32 = 3; + required uint64 UI64 = 4; + required sint32 SI32 = 5; + required sint64 SI64 = 6; + required fixed32 FI32 = 7; + required fixed64 FI64 = 8; + required sfixed32 SFI32 = 9; + required sfixed64 SFI64 = 10; + + required bool Bool = 11; + + required string String = 12; + required bytes Bytes = 13; + + required EEnum Enum = 14; + + required float Float = 15; + required double Double = 16; + + required string OneString = 17; + required string OneTwoString = 18; + required string ABC = 19; + required string UserID = 20; +}; + +message TFlatRepeated { + repeated int32 I32 = 1; + repeated int64 I64 = 2; + repeated uint32 UI32 = 3; + repeated uint64 UI64 = 4; + repeated sint32 SI32 = 5; + repeated sint64 SI64 = 6; + repeated fixed32 FI32 = 7; + repeated fixed64 FI64 = 8; + repeated sfixed32 SFI32 = 9; + repeated sfixed64 SFI64 = 10; + + repeated bool Bool = 11; + + repeated string String = 12; + repeated bytes Bytes = 13; + + repeated EEnum Enum = 14; + + repeated float Float = 15; + repeated double Double = 16; + + repeated string OneString = 17; + repeated string OneTwoString = 18; + repeated string ABC = 19; + repeated string UserID = 20; +}; + +message TFlatDefault { + optional int32 I32 = 1 [default = 132]; + optional int64 I64 = 2 [default = 164]; + optional uint32 UI32 = 3 [default = 232]; + optional uint64 UI64 = 4 [default = 264]; + optional sint32 SI32 = 5 [default = 332]; + optional sint64 SI64 = 6 [default = 364]; + optional fixed32 FI32 = 7 [default = 432]; + optional fixed64 FI64 = 8 [default = 464]; + optional sfixed32 SFI32 = 9 [default = 532]; + optional sfixed64 SFI64 = 10 [default = 564]; + + optional bool Bool = 11 [default = true]; + + optional string String = 12 [default = "string"]; + optional bytes Bytes = 13 [default = "bytes"]; + + optional EEnum Enum = 14 [default = E_2]; + + optional float Float = 15 [default = 0.123]; + optional double Double = 16 [default = 0.456]; + + optional string OneString = 17 [default = "string"]; + optional string OneTwoString = 18 [default = "string"]; + optional string ABC = 19 [default = "abc"]; + optional string UserID = 20 [default = "some_id"]; +}; + +message TCompositeOptional { + optional TFlatOptional Part = 1; +}; + +message TCompositeRequired { + required TFlatRequired Part = 1; +}; + +message TCompositeRepeated { + repeated TFlatOptional Part = 1; +}; + +message TMapType { + map<string, string> Items = 1; +}; + +message TNameGeneratorType { + optional int32 Field = 1; +}; + +message TEnumValueGeneratorType { + enum EEnum { + ENUM_42 = 1; + }; + + optional EEnum Enum = 1; +}; + +message TComplexMapType { + map<int32, int32> I32 = 1; + map<int64, int64> I64 = 2; + map<uint32, uint32> UI32 = 3; + map<uint64, uint64> UI64 = 4; + map<sint32, sint32> SI32 = 5; + map<sint64, sint64> SI64 = 6; + map<fixed32, fixed32> FI32 = 7; + map<fixed64, fixed64> FI64 = 8; + map<sfixed32, sfixed32> SFI32 = 9; + map<sfixed64, sfixed64> SFI64 = 10; + + map<bool, bool> Bool = 11; + + map<string, string> String = 12; + + map<string, EEnum> Enum = 13; + + map<string, float> Float = 14; + map<string, double> Double = 15; + + map<string, TComplexMapType> Nested = 16; +}; + +message TWithJsonName { + optional int32 my_upper = 1 [json_name = "My-Upper"]; + optional int32 My_lower = 2 [json_name = "my-lower"]; + optional int32 Def_upper = 3; // json_name = "DefUpper" + optional int32 def_lower = 4; // json_name = "defLower" +} + +message TSingleRequiredString { + required string String = 1; +} + +message TSingleDefaultString { + optional string String = 1 [default = "value"]; +} + +message TSingleRepeatedString { + repeated string RepeatedString = 1; +} + +message TSingleRepeatedInt { + repeated int32 RepeatedInt = 1; +} + +message TExtensionField { + extensions 100 to 199; +} + +extend TExtensionField { + optional int32 bar = 123; +}
\ No newline at end of file diff --git a/library/cpp/protobuf/json/ut/util_ut.cpp b/library/cpp/protobuf/json/ut/util_ut.cpp new file mode 100644 index 0000000000..05101dca28 --- /dev/null +++ b/library/cpp/protobuf/json/ut/util_ut.cpp @@ -0,0 +1,42 @@ +#include <library/cpp/protobuf/json/util.h> + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NProtobufJson; + +Y_UNIT_TEST_SUITE(TEqualsTest) { + Y_UNIT_TEST(TestEmpty) { + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("", "")); + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("", "_")); + UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("f", "")); + } + + Y_UNIT_TEST(TestTrivial) { + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("f", "f")); + UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("f", "o")); + UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("fo", "f")); + UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("f", "fo")); + UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("bar", "baz")); + } + + Y_UNIT_TEST(TestUnderscores) { + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("foo_bar", "foobar")); + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("foo_bar_", "foobar")); + UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("foo_bar_z", "foobar")); + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("foo__bar__", "foobar")); + UNIT_ASSERT(!EqualsIgnoringCaseAndUnderscores("foo__bar__z", "foobar")); + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("_foo_bar", "foobar")); + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("_foo_bar_", "foobar")); + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("_foo_bar_", "foo___bar")); + } + + Y_UNIT_TEST(TestCase) { + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("foo_bar", "FOO_BAR")); + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("foobar", "fooBar")); + } + + Y_UNIT_TEST(TestCaseAndUnderscores) { + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("fooBar", "FOO_BAR")); + UNIT_ASSERT(EqualsIgnoringCaseAndUnderscores("FOO_BAR_BAZ", "fooBar_BAZ")); + } +} diff --git a/library/cpp/protobuf/json/ut/ya.make b/library/cpp/protobuf/json/ut/ya.make new file mode 100644 index 0000000000..b60a6d3c17 --- /dev/null +++ b/library/cpp/protobuf/json/ut/ya.make @@ -0,0 +1,23 @@ +UNITTEST_FOR(library/cpp/protobuf/json) + +OWNER(avitella) + +SRCS( + filter_ut.cpp + json2proto_ut.cpp + proto2json_ut.cpp + inline_ut.proto + inline_ut.cpp + string_transform_ut.cpp + filter_ut.proto + test.proto + util_ut.cpp +) + +GENERATE_ENUM_SERIALIZATION(test.pb.h) + +PEERDIR( + library/cpp/protobuf/json +) + +END() |