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/json2proto_ut.cpp | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/protobuf/json/ut/json2proto_ut.cpp')
-rw-r--r-- | library/cpp/protobuf/json/ut/json2proto_ut.cpp | 1147 |
1 files changed, 1147 insertions, 0 deletions
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 |