aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbulatman <bulatman@yandex-team.com>2024-11-25 16:28:16 +0300
committerbulatman <bulatman@yandex-team.com>2024-11-25 16:44:02 +0300
commitf2d33ac94af8ac816dea2ddf44b2ecb34cba479d (patch)
treec5aa9ee73e8c57f3811b459bd8ad9429125f5ae2
parent0986bb1e55d529590e8e309a7e6a3eb9a19532bf (diff)
downloadydb-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.cpp2
-rw-r--r--yt/yt/core/yson/config.h3
-rw-r--r--yt/yt/core/yson/protobuf_interop.cpp17
-rw-r--r--yt/yt/core/yson/protobuf_interop.h1
-rw-r--r--yt/yt/core/yson/unittests/proto/protobuf_yson_casing_ext_ut.proto6
-rw-r--r--yt/yt/core/yson/unittests/proto/protobuf_yson_schema_ut.proto24
-rw-r--r--yt/yt/core/yson/unittests/protobuf_yson_schema_ut.cpp70
-rw-r--r--yt/yt/core/yson/unittests/protobuf_yson_ut.cpp43
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