diff options
author | bulatman <bulatman@yandex-team.com> | 2024-11-25 16:28:16 +0300 |
---|---|---|
committer | bulatman <bulatman@yandex-team.com> | 2024-11-25 16:44:02 +0300 |
commit | f2d33ac94af8ac816dea2ddf44b2ecb34cba479d (patch) | |
tree | c5aa9ee73e8c57f3811b459bd8ad9429125f5ae2 | |
parent | 0986bb1e55d529590e8e309a7e6a3eb9a19532bf (diff) | |
download | ydb-f2d33ac94af8ac816dea2ddf44b2ecb34cba479d.tar.gz |
YT: Introduce global config option to convert all protobuf message fields names to snake case
commit_hash:31aeb756f05c80fc7b39c56e8e171dc16f018d13
-rw-r--r-- | yt/yt/core/yson/config.cpp | 2 | ||||
-rw-r--r-- | yt/yt/core/yson/config.h | 3 | ||||
-rw-r--r-- | yt/yt/core/yson/protobuf_interop.cpp | 17 | ||||
-rw-r--r-- | yt/yt/core/yson/protobuf_interop.h | 1 | ||||
-rw-r--r-- | yt/yt/core/yson/unittests/proto/protobuf_yson_casing_ext_ut.proto | 6 | ||||
-rw-r--r-- | yt/yt/core/yson/unittests/proto/protobuf_yson_schema_ut.proto | 24 | ||||
-rw-r--r-- | yt/yt/core/yson/unittests/protobuf_yson_schema_ut.cpp | 70 | ||||
-rw-r--r-- | yt/yt/core/yson/unittests/protobuf_yson_ut.cpp | 43 |
8 files changed, 151 insertions, 15 deletions
diff --git a/yt/yt/core/yson/config.cpp b/yt/yt/core/yson/config.cpp index 62632a5a39..eab523b4f8 100644 --- a/yt/yt/core/yson/config.cpp +++ b/yt/yt/core/yson/config.cpp @@ -10,6 +10,8 @@ void TProtobufInteropConfig::Register(TRegistrar registrar) .Default(EEnumYsonStorageType::String); registrar.Parameter("utf8_check", &TThis::Utf8Check) .Default(EUtf8Check::Disable); + registrar.Parameter("force_snake_case_names", &TThis::ForceSnakeCaseNames) + .Default(false); } TProtobufInteropConfigPtr TProtobufInteropConfig::ApplyDynamic( diff --git a/yt/yt/core/yson/config.h b/yt/yt/core/yson/config.h index 3c5817c5d2..7238e47605 100644 --- a/yt/yt/core/yson/config.h +++ b/yt/yt/core/yson/config.h @@ -21,6 +21,9 @@ public: EEnumYsonStorageType DefaultEnumYsonStorageType; // Check if string field contains actual UTF-8 string. EUtf8Check Utf8Check; + // If `true` convert all field names in camel case to snake case. + // Note: Applies to each protobuf message only on first seen, changes of flag after do not have effect. + bool ForceSnakeCaseNames; TProtobufInteropConfigPtr ApplyDynamic(const TProtobufInteropDynamicConfigPtr& dynamicConfig) const; diff --git a/yt/yt/core/yson/protobuf_interop.cpp b/yt/yt/core/yson/protobuf_interop.cpp index c23dcab2a3..eca135a620 100644 --- a/yt/yt/core/yson/protobuf_interop.cpp +++ b/yt/yt/core/yson/protobuf_interop.cpp @@ -148,11 +148,12 @@ TString ToUnderscoreCase(const TString& protobufName) TString DeriveYsonName(const TString& protobufName, const google::protobuf::FileDescriptor* fileDescriptor) { - if (fileDescriptor->options().GetExtension(NYT::NYson::NProto::derive_underscore_case_names)) { + if (fileDescriptor->options().GetExtension(NYT::NYson::NProto::derive_underscore_case_names) + || GetProtobufInteropConfig()->ForceSnakeCaseNames) + { return ToUnderscoreCase(protobufName); - } else { - return protobufName; } + return protobufName; } //////////////////////////////////////////////////////////////////////////////// @@ -167,17 +168,17 @@ TProtobufInteropConfigSingleton* GlobalProtobufInteropConfig() return LeakySingleton<TProtobufInteropConfigSingleton>(); } -TProtobufInteropConfigPtr GetProtobufInteropConfig() -{ - return GlobalProtobufInteropConfig()->Config.Acquire(); -} - //////////////////////////////////////////////////////////////////////////////// } // namespace //////////////////////////////////////////////////////////////////////////////// +TProtobufInteropConfigPtr GetProtobufInteropConfig() +{ + return GlobalProtobufInteropConfig()->Config.Acquire(); +} + void SetProtobufInteropConfig(TProtobufInteropConfigPtr config) { GlobalProtobufInteropConfig()->Config.Store(std::move(config)); diff --git a/yt/yt/core/yson/protobuf_interop.h b/yt/yt/core/yson/protobuf_interop.h index bdc06244da..9f9d5be70f 100644 --- a/yt/yt/core/yson/protobuf_interop.h +++ b/yt/yt/core/yson/protobuf_interop.h @@ -263,6 +263,7 @@ TString YsonStringToProto( //////////////////////////////////////////////////////////////////////////////// +TProtobufInteropConfigPtr GetProtobufInteropConfig(); void SetProtobufInteropConfig(TProtobufInteropConfigPtr config); //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/core/yson/unittests/proto/protobuf_yson_casing_ext_ut.proto b/yt/yt/core/yson/unittests/proto/protobuf_yson_casing_ext_ut.proto index 98ce2c4062..1eb4eeb75b 100644 --- a/yt/yt/core/yson/unittests/proto/protobuf_yson_casing_ext_ut.proto +++ b/yt/yt/core/yson/unittests/proto/protobuf_yson_casing_ext_ut.proto @@ -5,3 +5,9 @@ message TExternalProtobuf optional int32 SomeField = 1; optional int32 AnotherField123 = 2; } + +message TOtherExternalProtobuf +{ + optional int32 SomeField = 1; + optional int32 AnotherField123 = 2; +} diff --git a/yt/yt/core/yson/unittests/proto/protobuf_yson_schema_ut.proto b/yt/yt/core/yson/unittests/proto/protobuf_yson_schema_ut.proto index 0e5dbe293c..5ea8fbb320 100644 --- a/yt/yt/core/yson/unittests/proto/protobuf_yson_schema_ut.proto +++ b/yt/yt/core/yson/unittests/proto/protobuf_yson_schema_ut.proto @@ -35,3 +35,27 @@ message TTestSchemaMessage map<string, int32> string_to_int32_map = 19[(NYT.NYson.NProto.yson_map) = true]; map<int32, string> int32_to_string_map = 20[(NYT.NYson.NProto.yson_map) = true]; } + +message TCamelCaseStyleTestSchemaMessage1 +{ + enum EEnum + { + VALUE_NONE = 0; + VALUE_FIRST = 1; + } + + optional int32 SomeField = 1; + optional EEnum EnumField = 2; +} + +message TCamelCaseStyleTestSchemaMessage2 +{ + enum EEnum + { + VALUE_NONE = 0; + VALUE_FIRST = 1; + } + + optional int32 SomeField = 1; + optional EEnum EnumField = 2; +} diff --git a/yt/yt/core/yson/unittests/protobuf_yson_schema_ut.cpp b/yt/yt/core/yson/unittests/protobuf_yson_schema_ut.cpp index 0811eeba85..2076133892 100644 --- a/yt/yt/core/yson/unittests/protobuf_yson_schema_ut.cpp +++ b/yt/yt/core/yson/unittests/protobuf_yson_schema_ut.cpp @@ -2,21 +2,24 @@ #include <yt/yt/core/yson/unittests/proto/protobuf_yson_schema_ut.pb.h> +#include <yt/yt/core/yson/config.h> #include <yt/yt/core/yson/protobuf_interop.h> #include <yt/yt/core/yson/writer.h> #include <yt/yt/core/ytree/convert.h> #include <yt/yt/core/ytree/ypath_client.h> +#include <util/generic/scope.h> #include <util/stream/str.h> - namespace NYT::NYson { namespace { +using namespace NYTree; + //////////////////////////////////////////////////////////////////////////////// -TEST(TProtobufYsonSchemaTest, GetMessageSchema) +TEST(TProtobufYsonSchemaTest, WriteMessageSchema) { TStringStream outputStream; TYsonWriter ysonWriter(&outputStream, EYsonFormat::Text); @@ -48,12 +51,65 @@ TEST(TProtobufYsonSchemaTest, GetMessageSchema) ]; })"; - auto expectedNode = NYTree::ConvertToNode(TYsonStringBuf(expected), NYTree::GetEphemeralNodeFactory()); - auto actualNode = NYTree::ConvertToNode(TYsonStringBuf(outputStream.Str()), NYTree::GetEphemeralNodeFactory()); + auto expectedNode = ConvertToNode(TYsonStringBuf(expected), GetEphemeralNodeFactory()); + auto actualNode = ConvertToNode(TYsonStringBuf(outputStream.Str()), GetEphemeralNodeFactory()); + + EXPECT_TRUE(AreNodesEqual(actualNode, expectedNode)) + << "Expected: " << ConvertToYsonString(expectedNode, EYsonFormat::Pretty).AsStringBuf() << "\n\n" + << "Actual: " << ConvertToYsonString(actualNode, EYsonFormat::Text).AsStringBuf() << "\n\n"; +} + +TEST(TProtobufYsonSchemaTest, CamelCaseNames) +{ + TStringStream outputStream; + TYsonWriter ysonWriter(&outputStream, EYsonFormat::Text); + + WriteSchema(ReflectProtobufMessageType<NProto::TCamelCaseStyleTestSchemaMessage1>(), &ysonWriter); + TStringBuf expected = R"({ + type_name="struct"; + members=[ + {name="SomeField";type="int32";}; + {name="EnumField";type={type_name="enum";enum_name="EEnum";"values"=["VALUE_NONE";"VALUE_FIRST";];};}; + ]; + })"; + + auto expectedNode = ConvertToNode(TYsonStringBuf(expected), GetEphemeralNodeFactory()); + auto actualNode = ConvertToNode(TYsonStringBuf(outputStream.Str()), GetEphemeralNodeFactory()); + + EXPECT_TRUE(AreNodesEqual(actualNode, expectedNode)) + << "Expected: " << ConvertToYsonString(expectedNode, EYsonFormat::Pretty).AsStringBuf() << "\n\n" + << "Actual: " << ConvertToYsonString(actualNode, EYsonFormat::Pretty).AsStringBuf() << "\n\n"; +} + +TEST(TProtobufYsonSchemaTest, ForceSnakeCaseNames) +{ + auto oldConfig = GetProtobufInteropConfig(); + Y_DEFER { + SetProtobufInteropConfig(oldConfig); + }; + + auto newConfig = CloneYsonStruct(oldConfig); + newConfig->ForceSnakeCaseNames = true; + SetProtobufInteropConfig(newConfig); + + TStringStream outputStream; + TYsonWriter ysonWriter(&outputStream, EYsonFormat::Text); + + WriteSchema(ReflectProtobufMessageType<NProto::TCamelCaseStyleTestSchemaMessage2>(), &ysonWriter); + TStringBuf expected = R"({ + type_name="struct"; + members=[ + {name="some_field";type="int32";}; + {name="enum_field";type={type_name="enum";enum_name="EEnum";"values"=["value_none";"value_first";];};}; + ]; + })"; + + auto expectedNode = ConvertToNode(TYsonStringBuf(expected), GetEphemeralNodeFactory()); + auto actualNode = ConvertToNode(TYsonStringBuf(outputStream.Str()), GetEphemeralNodeFactory()); - EXPECT_TRUE(NYTree::AreNodesEqual(actualNode, expectedNode)) - << "Expected: " << ConvertToYsonString(expectedNode, EYsonFormat::Text, 4).AsStringBuf() << "\n\n" - << "Actual: " << ConvertToYsonString(actualNode, EYsonFormat::Text, 4).AsStringBuf() << "\n\n"; + EXPECT_TRUE(AreNodesEqual(actualNode, expectedNode)) + << "Expected: " << ConvertToYsonString(expectedNode, EYsonFormat::Pretty).AsStringBuf() << "\n\n" + << "Actual: " << ConvertToYsonString(actualNode, EYsonFormat::Pretty).AsStringBuf() << "\n\n"; } //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/core/yson/unittests/protobuf_yson_ut.cpp b/yt/yt/core/yson/unittests/protobuf_yson_ut.cpp index 7373b8d78c..f369a0a0ff 100644 --- a/yt/yt/core/yson/unittests/protobuf_yson_ut.cpp +++ b/yt/yt/core/yson/unittests/protobuf_yson_ut.cpp @@ -22,6 +22,8 @@ #include <google/protobuf/wire_format.h> +#include <util/generic/scope.h> + namespace NYT { //////////////////////////////////////////////////////////////////////////////// @@ -2792,5 +2794,46 @@ TEST(TYsonToProtobufTest, Casing) EXPECT_EQ(message.anotherfield123(), 2); } +TEST(TYsonToProtobufTest, ForceSnakeCaseNames) +{ + auto oldConfig = GetProtobufInteropConfig(); + Y_DEFER { + SetProtobufInteropConfig(oldConfig); + }; + + auto newConfig = CloneYsonStruct(oldConfig); + newConfig->ForceSnakeCaseNames = true; + SetProtobufInteropConfig(newConfig); + + auto ysonNode = BuildYsonNodeFluently() + .BeginMap() + .Item("some_field").Value(1) + .Item("another_field123").Value(2) + .EndMap(); + auto ysonString = ConvertToYsonString(ysonNode); + + NProto::TOtherExternalProtobuf message; + message.ParseFromStringOrThrow(NYson::YsonStringToProto( + ysonString, + NYson::ReflectProtobufMessageType<NProto::TOtherExternalProtobuf>(), + TProtobufWriterOptions())); + + EXPECT_EQ(message.somefield(), 1); + EXPECT_EQ(message.anotherfield123(), 2); + { + TString newYsonString; + TStringOutput newYsonOutputStream(newYsonString); + TYsonWriter ysonWriter(&newYsonOutputStream, EYsonFormat::Pretty); + WriteProtobufMessage(&ysonWriter, message); + + auto originalNode = NYTree::ConvertToNode(ysonString, NYTree::GetEphemeralNodeFactory()); + auto newNode = NYTree::ConvertToNode(TYsonStringBuf(newYsonString), NYTree::GetEphemeralNodeFactory()); + + EXPECT_TRUE(NYTree::AreNodesEqual(originalNode, newNode)) + << "Expected: " << ysonString.AsStringBuf() << "\n\n" + << "Actual: " << newYsonString << "\n\n"; + } +} + } // namespace } // namespace NYT::NYson |