Browse Source

YT: Introduce schema for TYsonStruct

bulatman 1 year ago
parent
commit
518293d201

+ 113 - 0
yt/yt/core/yson/protobuf_interop.cpp

@@ -20,6 +20,7 @@
 #include <yt/yt/core/ytree/convert.h>
 #include <yt/yt/core/ytree/ephemeral_node_factory.h>
 #include <yt/yt/core/ytree/tree_builder.h>
+#include <yt/yt/core/ytree/fluent.h>
 
 #include <yt/yt_proto/yt/core/ytree/proto/attributes.pb.h>
 
@@ -181,6 +182,8 @@ void SetProtobufInteropConfig(TProtobufInteropDynamicConfigPtr config)
     GlobalProtobufInteropConfig()->Config.Store(std::move(config));
 }
 
+void GetSchema(const TProtobufEnumType* enumType, IYsonConsumer* consumer);
+
 ////////////////////////////////////////////////////////////////////////////////
 
 class TProtobufTypeRegistry
@@ -498,6 +501,76 @@ public:
         return EnumYsonStorageType_;
     }
 
+    void GetSchema(IYsonConsumer* consumer) const
+    {
+        if (IsYsonMap()) {
+            BuildYsonFluently(consumer).BeginMap()
+                .Item("type_name").Value("dict")
+                .Item("key").Do([this] (auto&& fluent) {
+                    GetYsonMapKeyField()->GetSchema(fluent.GetConsumer());
+                })
+                .Item("value").Do([this] (auto&& fluent) {
+                    GetYsonMapValueField()->GetSchema(fluent.GetConsumer());
+                })
+                .EndMap();
+
+            return;
+        }
+        if (IsRepeated()) {
+            consumer->OnBeginMap();
+            consumer->OnKeyedItem("type_name");
+            consumer->OnStringScalar("list");
+            consumer->OnKeyedItem("item");
+        }
+
+        switch (GetType()) {
+            case FieldDescriptor::TYPE_INT32:
+            case FieldDescriptor::TYPE_FIXED32:
+            case FieldDescriptor::TYPE_UINT32:
+                consumer->OnStringScalar("uint32");
+                break;
+            case FieldDescriptor::TYPE_INT64:
+            case FieldDescriptor::TYPE_FIXED64:
+            case FieldDescriptor::TYPE_UINT64:
+                consumer->OnStringScalar("uint64");
+                break;
+            case FieldDescriptor::TYPE_SINT32:
+            case FieldDescriptor::TYPE_SFIXED32:
+                consumer->OnStringScalar("int32");
+                break;
+            case FieldDescriptor::TYPE_SINT64:
+            case FieldDescriptor::TYPE_SFIXED64:
+                consumer->OnStringScalar("int64");
+                break;
+            case FieldDescriptor::TYPE_BOOL:
+                consumer->OnStringScalar("bool");
+                break;
+            case FieldDescriptor::TYPE_FLOAT:
+                consumer->OnStringScalar("float");
+                break;
+            case FieldDescriptor::TYPE_DOUBLE:
+                consumer->OnStringScalar("double");
+                break;
+            case FieldDescriptor::TYPE_STRING:
+                consumer->OnStringScalar("utf8");
+                break;
+            case FieldDescriptor::TYPE_BYTES:
+                consumer->OnStringScalar("string");
+                break;
+            case FieldDescriptor::TYPE_ENUM:
+                NYson::GetSchema(GetEnumType(), consumer);
+                break;
+            case FieldDescriptor::TYPE_MESSAGE:
+                NYson::GetSchema(GetMessageType(), consumer);
+                break;
+            default:
+                break;
+        }
+        if (IsRepeated()) {
+            consumer->OnEndMap();
+        }
+    }
+
 private:
     const FieldDescriptor* const Underlying_;
     const TStringBuf YsonName_;
@@ -617,6 +690,25 @@ public:
         }
     }
 
+    void GetSchema(IYsonConsumer* consumer) const
+    {
+        BuildYsonFluently(consumer).BeginMap()
+            .Item("type_name").Value("struct")
+            .Item("members").DoListFor(0, Underlying_->field_count(), [this] (auto&& fluent, int index) {
+                auto* field = GetFieldByNumber(Underlying_->field(index)->number());
+                fluent.Item().BeginMap()
+                    .Item("name").Value(field->GetYsonName())
+                    .Item("type").Do([&] (auto&& fluent) {
+                        field->GetSchema(fluent.GetConsumer());
+                    })
+                    .DoIf(!field->IsYsonMap() && !field->IsRepeated() && !field->IsOptional(), [&] (auto && fluent) {
+                        fluent.Item("required").Value(true);
+                    })
+                    .EndMap();
+            })
+            .EndMap();
+    }
+
 private:
     TProtobufTypeRegistry* const Registry_;
     const Descriptor* const Underlying_;
@@ -729,6 +821,17 @@ public:
         return it == ValueToLiteral_.end() ? TStringBuf() : it->second;
     }
 
+    void GetSchema(IYsonConsumer* consumer) const
+    {
+        BuildYsonFluently(consumer).BeginMap()
+            .Item("type_name").Value("enum")
+            .Item("enum_name").Value(Underlying_->name())
+            .Item("values").DoListFor(0, Underlying_->value_count(), [this] (auto&& fluent, int index) {
+                fluent.Item().Value(FindLiteralByValue(Underlying_->value(index)->number()));
+            })
+            .EndMap();
+    }
+
 private:
     TProtobufTypeRegistry* const Registry_;
     const EnumDescriptor* const Underlying_;
@@ -3037,6 +3140,16 @@ TString YsonStringToProto(
     return serializedProto;
 }
 
+void GetSchema(const TProtobufEnumType* type, IYsonConsumer* consumer)
+{
+    type->GetSchema(consumer);
+}
+
+void GetSchema(const TProtobufMessageType* type, IYsonConsumer* consumer)
+{
+    type->GetSchema(consumer);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 } // namespace NYT::NYson

+ 4 - 0
yt/yt/core/yson/protobuf_interop.h

@@ -280,6 +280,10 @@ void SetProtobufInteropConfig(TProtobufInteropDynamicConfigPtr config);
 
 ////////////////////////////////////////////////////////////////////////////////
 
+//! Return type v3 schema for protobuf message type.
+//! Note: Recursive types (message has field with self type) are not supported.
+void GetSchema(const TProtobufMessageType* type, IYsonConsumer* consumer);
+
 } // namespace NYT::NYson
 
 #define PROTOBUF_INTEROP_INL_H_

+ 37 - 0
yt/yt/core/yson/unittests/proto/protobuf_yson_schema_ut.proto

@@ -0,0 +1,37 @@
+package NYT.NYson.NProto;
+
+import "yt_proto/yt/core/misc/proto/protobuf_helpers.proto";
+import "yt_proto/yt/core/yson/proto/protobuf_interop.proto";
+import "yt_proto/yt/core/ytree/proto/attributes.proto";
+
+message TTestSchemaMessage
+{
+    enum EEnum
+    {
+        Value0 = 0 [(NYT.NYson.NProto.enum_value_name) = "value0"];
+        Value1 = 1 [(NYT.NYson.NProto.enum_value_name) = "value1"];
+    }
+
+    optional int32 int32_field = 1;
+    optional uint32 uint32_field = 2;
+    optional sint32 sint32_field = 3;
+    optional int64 int64_field = 4;
+    optional uint64 uint64_field = 5;
+    optional sint64 sint64_field = 6;
+    optional fixed32 fixed32_field = 7;
+    optional fixed64 fixed64_field = 8;
+    optional sfixed32 sfixed32_field = 9;
+    optional sfixed64 sfixed64_field = 10;
+    optional bool bool_field = 11;
+    optional string string_field = 12;
+    optional bytes bytes_field = 13;
+    optional float float_field = 14;
+    optional double double_field = 15;
+    optional EEnum enum_field = 16;
+
+    required int32 required_int32_field = 17;
+    repeated int32 repeated_int32_field = 18;
+
+    map<string, int32> string_to_int32_map = 19[(NYT.NYson.NProto.yson_map) = true];
+    map<int32, string> int32_to_int32_map = 20[(NYT.NYson.NProto.yson_map) = true];
+}

+ 58 - 0
yt/yt/core/yson/unittests/protobuf_yson_schema_ut.cpp

@@ -0,0 +1,58 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/yson/unittests/proto/protobuf_yson_schema_ut.pb.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/stream/str.h>
+
+
+namespace NYT::NYson {
+namespace {
+
+
+TEST(TProtobufYsonSchemaTest, GetMessageSchema)
+{
+    TStringStream outputStream;
+    TYsonWriter ysonWriter(&outputStream, EYsonFormat::Text);
+
+    GetSchema(ReflectProtobufMessageType<NProto::TTestSchemaMessage>(), &ysonWriter);
+    TStringBuf expected = R"({
+        type_name="struct";
+        members=[
+            {name="int32_field";type="uint32";};
+            {name="uint32_field";type="uint32";};
+            {name="sint32_field";type="int32";};
+            {name="int64_field";type="uint64";};
+            {name="uint64_field";type="uint64";};
+            {name="sint64_field";type="int64";};
+            {name="fixed32_field";type="uint32";};
+            {name="fixed64_field";type="uint64";};
+            {name="sfixed32_field";type="int32";};
+            {name="sfixed64_field";type="int64";};
+            {name="bool_field";type="bool";};
+            {name="string_field";type="utf8";};
+            {name="bytes_field";type="string";};
+            {name="float_field";type="float";};
+            {name="double_field";type="double";};
+            {name="enum_field";type={type_name="enum";enum_name="EEnum";"values"=["value0";"value1";];};};
+            {name="required_int32_field";type="uint32";required=%true;};
+            {name="repeated_int32_field";type={type_name="list";item="uint32";};};
+            {name="string_to_int32_map";type={type_name="dict";key="utf8";value="uint32";};};
+            {name="int32_to_int32_map";type={type_name="dict";key="uint32";value="utf8";};};];}
+    )";
+
+    auto expectedNode = NYTree::ConvertToNode(TYsonStringBuf(expected), NYTree::GetEphemeralNodeFactory());
+    auto actualNode = NYTree::ConvertToNode(TYsonStringBuf(outputStream.Str()), NYTree::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";
+}
+
+} // namespace
+} // namespace NYT::NYson

+ 2 - 0
yt/yt/core/yson/unittests/ya.make

@@ -13,6 +13,7 @@ SRCS(
     filter_ut.cpp
     lexer_ut.cpp
     protobuf_scalar_type_ut.cpp
+    protobuf_yson_schema_ut.cpp
     protobuf_yson_ut.cpp
     ypath_designated_yson_consumer_ut.cpp
     yson_parser_ut.cpp
@@ -25,6 +26,7 @@ SRCS(
     proto/protobuf_yson_ut.proto
     proto/protobuf_yson_casing_ut.proto
     proto/protobuf_yson_casing_ext_ut.proto
+    proto/protobuf_yson_schema_ut.proto
 )
 
 INCLUDE(${ARCADIA_ROOT}/yt/opensource.inc)

+ 2 - 1
yt/yt/core/ytree/public.h

@@ -23,6 +23,8 @@ class TYsonSerializableLite;
 class TYsonSerializable;
 
 struct IYsonStructMeta;
+class TYsonStructBase;
+class TYsonStructLite;
 
 DECLARE_REFCOUNTED_STRUCT(INode)
 using IConstNodePtr = TIntrusivePtr<const INode>;
@@ -76,7 +78,6 @@ constexpr int MaxYPathResolveIterations = 256;
 
 DECLARE_REFCOUNTED_CLASS(TYsonSerializable)
 DECLARE_REFCOUNTED_CLASS(TYsonStruct)
-DECLARE_REFCOUNTED_CLASS(TYsonStructBase)
 
 DECLARE_REFCOUNTED_CLASS(TYPathServiceContextWrapper)
 

+ 1 - 0
yt/yt/core/ytree/unittests/ya.make

@@ -16,6 +16,7 @@ SRCS(
     service_combiner_ut.cpp
     tree_builder_ut.cpp
     lazy_ypath_service_ut.cpp
+    yson_schema_ut.cpp
     yson_serializable_ut.cpp
     yson_struct_ut.cpp
     ytree_fluent_ut.cpp

+ 247 - 0
yt/yt/core/ytree/unittests/yson_schema_ut.cpp

@@ -0,0 +1,247 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/ytree/convert.h>
+#include <yt/yt/core/ytree/ephemeral_node_factory.h>
+#include <yt/yt/core/ytree/fluent.h>
+#include <yt/yt/core/ytree/tree_builder.h>
+#include <yt/yt/core/ytree/yson_struct.h>
+
+#include <yt/yt/core/ytree/unittests/proto/test.pb.h>
+
+
+namespace NYT::NYTree {
+namespace {
+
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+DEFINE_ENUM(ETestEnum,
+    (Value0)
+    (Value1)
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTestSubStruct
+    : public TYsonStruct
+{
+    ui32 MyUint;
+
+    REGISTER_YSON_STRUCT(TTestSubStruct);
+
+    static void Register(TRegistrar registrar)
+    {
+        registrar.Parameter("my_uint", &TThis::MyUint)
+            .Default();
+    }
+};
+
+using TTestSubStructPtr = TIntrusivePtr<TTestSubStruct>;
+
+struct TTestSubStructLite
+    : public TYsonStructLite
+{
+    i32 MyInt;
+
+    REGISTER_YSON_STRUCT_LITE(TTestSubStructLite);
+
+    static void Register(TRegistrar registrar)
+    {
+        registrar.Parameter("my_int", &TThis::MyInt)
+            .Default();
+    }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTestYsonStruct
+    : public TYsonStruct
+{
+    TString MyString;
+    TTestSubStructPtr Sub;
+    std::vector<TTestSubStructLite> SubList;
+    std::vector<TString> MyStringList;
+    std::unordered_map<TString, int> IntMap;
+    std::optional<i64> NullableInt;
+    ETestEnum MyEnum;
+    unsigned int MyUint;
+    bool MyBool;
+    char MyChar;
+    i8 MyByte;
+    ui8 MyUbyte;
+    i16 MyShort;
+    ui16 MyUshort;
+
+    REGISTER_YSON_STRUCT(TTestYsonStruct);
+
+    static void Register(TRegistrar registrar)
+    {
+        registrar.Parameter("my_string", &TThis::MyString);
+        registrar.Parameter("sub", &TThis::Sub)
+            .DefaultNew();
+        registrar.Parameter("sub_list", &TThis::SubList)
+            .Default();
+        registrar.Parameter("int_map", &TThis::IntMap)
+            .Default();
+        registrar.Parameter("my_string_list", &TThis::MyStringList)
+                .Default();
+        registrar.Parameter("nullable_int", &TThis::NullableInt)
+            .Default();
+        registrar.Parameter("my_uint", &TThis::MyUint)
+            .Default();
+        registrar.Parameter("my_bool", &TThis::MyBool)
+            .Default();
+        registrar.Parameter("my_char", &TThis::MyChar)
+            .Default();
+        registrar.Parameter("my_byte", &TThis::MyByte)
+            .Default();
+        registrar.Parameter("my_ubyte", &TThis::MyUbyte)
+            .Default();
+        registrar.Parameter("my_short", &TThis::MyShort)
+            .Default();
+        registrar.Parameter("my_ushort", &TThis::MyUshort)
+            .Default();
+        registrar.Parameter("my_enum", &TThis::MyEnum)
+            .Default(ETestEnum::Value1);
+    }
+};
+
+using TTestYsonStructPtr = TIntrusivePtr<TTestYsonStruct>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TTestStructWithProtobuf
+    : public TYsonStruct
+{
+    std::optional<NProto::TTestMessage> MyMessage;
+
+    REGISTER_YSON_STRUCT(TTestStructWithProtobuf);
+
+    static void Register(TRegistrar registrar)
+    {
+        registrar.Parameter("my_message", &TThis::MyMessage)
+            .Optional();
+    }
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TCustomType
+{
+    int Value;
+};
+
+void Serialize(TCustomType value, NYT::NYson::IYsonConsumer* consumer)
+{
+    consumer->OnInt64Scalar(value.Value);
+}
+
+void Deserialize(TCustomType& value, NYT::NYTree::INodePtr node)
+{
+    value.Value = node->GetValue<int>();
+}
+
+void Deserialize(TCustomType& codec, NYT::NYson::TYsonPullParserCursor* cursor)
+{
+    Deserialize(codec, NYT::NYson::ExtractTo<NYT::NYTree::INodePtr>(cursor));
+}
+
+struct TTestStructWithCustomType
+    : public TYsonStruct
+{
+    TCustomType MyType;
+
+    REGISTER_YSON_STRUCT(TTestStructWithCustomType);
+
+    static void Register(TRegistrar registrar)
+    {
+        registrar.Parameter("my_type", &TThis::MyType)
+            .Optional();
+    }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void CheckSchema(const TYsonStructPtr& ysonStruct, TStringBuf expected)
+{
+    auto* factory = GetEphemeralNodeFactory();
+    auto builder = CreateBuilderFromFactory(factory);
+    builder->BeginTree();
+    ysonStruct->GetSchema(builder.get());
+    auto actualNode = builder->EndTree();
+    auto expectedNode = ConvertToNode(TYsonStringBuf(expected), factory);
+    EXPECT_TRUE(AreNodesEqual(expectedNode, actualNode))
+        << "Expected: " << ConvertToYsonString(expectedNode, EYsonFormat::Text, 4).AsStringBuf() << "\n\n"
+        << "Actual: " << ConvertToYsonString(actualNode, EYsonFormat::Text, 4).AsStringBuf() << "\n\n";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TYsonStructSchemaTest, TestYsonStruct)
+{
+    CheckSchema(
+        New<TTestYsonStruct>(),
+        R"({type_name="struct";
+            members=[
+                {name="my_enum";type={type_name="enum";enum_name="ETestEnum";values=["value0";"value1";]}};
+                {name="my_char";type="int8";};
+                {name="my_ushort";type="uint16";};
+                {name="nullable_int";type={type_name="optional";item="int64";}};
+                {
+                    name="sub_list";
+                    type={type_name="list";item={type_name="struct";members=[{name="my_int";type="int32";}]}}
+                };
+                {name="my_byte";type="int8";};
+                {name="my_string";type="string";required=%true;};
+                {name="int_map";type={type_name="dict";key="string";value="int32";}};
+                {
+                    name="sub";
+                    type={type_name="optional";item={type_name="struct";members=[{name="my_uint";type="uint32";}]}};
+                };
+                {name="my_uint";type="uint32";};
+                {name="my_ubyte";type="uint8";};
+                {name="my_bool";type="bool";};
+                {name="my_short";type="int16";};
+                {name="my_string_list";type={type_name="list";item="string";}};
+            ];})");
+}
+
+TEST(TYsonStructSchemaTest, TestSchemaForProtobufMessage)
+{
+    CheckSchema(
+        New<TTestStructWithProtobuf>(),
+        R"({
+            type_name="struct";
+            members=[
+                {
+                    name="my_message";
+                    type={
+                        type_name="optional";
+                        item={
+                            type_name="struct";
+                            members=[
+                                {name="int32_field";type="uint32";};
+                                {name="string_field";"type"="utf8";};
+                            ];
+                        };
+                    };
+                };
+            ];})");
+}
+
+TEST(TYsonStructSchemaTest, TestYsonStructWithCustomType)
+{
+    CheckSchema(
+        New<TTestStructWithCustomType>(),
+        R"({type_name="struct";
+            members=[
+                {name="my_type";type="int64";};
+            ]})");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NYTree

+ 166 - 0
yt/yt/core/ytree/yson_schema-inl.h

@@ -0,0 +1,166 @@
+#ifndef YSON_SCHEMA_INL_H_
+#error "Direct inclusion of this file is not allowed, include yson_schema.h"
+// For the sake of sane code completion.
+#include "yson_schema.h"
+#endif
+
+#include "fluent.h"
+
+#include <yt/yt/core/yson/protobuf_interop.h>
+
+#include <library/cpp/yt/misc/enum.h>
+
+#include <optional>
+#include <type_traits>
+
+namespace NYT::NYTree::NPrivate {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+concept CIsEnum = TEnumTraits<T>::IsEnum;
+
+template <class T>
+concept CIsYsonStruct = std::is_base_of_v<TYsonStructBase, T>;
+
+template <class T>
+concept CIsProtobufMessage = std::is_base_of_v<google::protobuf::Message, std::decay_t<T>>;
+
+template <class T>
+concept CIsNullable = std::is_same_v<T, std::unique_ptr<typename T::value_type>> ||
+    std::is_same_v<T, std::shared_ptr<typename T::value_type>> ||
+    std::is_same_v<T, std::optional<typename T::value_type>>;
+
+template <class T>
+concept CIsArray = std::ranges::range<T>;
+
+template <class T>
+concept CIsMapping = requires(T) {
+    typename T::key_type;
+    typename T::mapped_type;
+};
+
+template <class T>
+concept CIsAssociativeArray = CIsArray<T> && CIsMapping<T>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define DEFINE_SCHEMA_FOR_SIMPLE_TYPE(type, name) \
+inline void GetSchema(type, NYson::IYsonConsumer* consumer) \
+{ \
+    BuildYsonFluently(consumer) \
+        .Value(#name); \
+}
+
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(bool, bool)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(char, int8)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(signed char, int8)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(unsigned char, uint8)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(short, int16)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(unsigned short, uint16)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(int, int32)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(unsigned, uint32)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(long, int64)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(unsigned long, uint64)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(long long, int64)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(unsigned long long, uint64)
+
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(double, double)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(float, float)
+
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(TString, string)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(TStringBuf, string)
+
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(TInstant, datetime)
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(TDuration, interval)
+
+DEFINE_SCHEMA_FOR_SIMPLE_TYPE(TGuid, guid)
+
+#undef DEFINE_SCHEMA_FOR_SIMPLE_TYPE
+
+template <CIsEnum T>
+void GetSchema(const T&, NYson::IYsonConsumer* consumer)
+{
+    BuildYsonFluently(consumer).BeginMap()
+        .Item("type_name").Value("enum")
+        .Item("enum_name").Value(TEnumTraits<T>::GetTypeName())
+        .Item("values").DoListFor(
+            TEnumTraits<T>::GetDomainNames(), [] (auto&& fluent, TStringBuf name) {
+                fluent.Item().Value(EncodeEnumValue(name));
+            })
+        .EndMap();
+}
+
+template <CIsYsonStruct T>
+void GetSchema(const NYT::TIntrusivePtr<T>& value, NYson::IYsonConsumer* consumer)
+{
+    BuildYsonFluently(consumer) .BeginMap()
+        .Item("type_name").Value("optional")
+        .Item("item").Do([&] (auto&& fluent) {
+            (value ? value : New<T>())->GetSchema(fluent.GetConsumer());
+        })
+        .EndMap();
+}
+
+template <CIsYsonStruct T>
+void GetSchema(const T& value, NYson::IYsonConsumer* consumer)
+{
+    return value.GetSchema(consumer);
+}
+
+template <CIsProtobufMessage T>
+void GetSchema(const T&, NYson::IYsonConsumer* consumer)
+{
+    return NYson::GetSchema(NYson::ReflectProtobufMessageType<T>(), consumer);
+}
+
+template <CIsArray T>
+void GetSchema(const T& value, NYson::IYsonConsumer* consumer)
+{
+    BuildYsonFluently(consumer).BeginMap()
+        .Item("type_name").Value("list")
+        .Item("item").Do([&] (auto&& fluent) {
+            GetSchema(
+                std::begin(value) != std::end(value)
+                    ? *std::begin(value)
+                    : std::decay_t<decltype(*std::begin(value))>{},
+                fluent.GetConsumer());
+        })
+        .EndMap();
+}
+
+template <CIsAssociativeArray T>
+void GetSchema(const T& value, NYson::IYsonConsumer* consumer)
+{
+    BuildYsonFluently(consumer).BeginMap()
+        .Item("type_name").Value("dict")
+        .Item("key").Do([&] (auto&& fluent) {
+            GetSchema(value.empty() ? typename T::key_type{} : value.begin()->first, fluent.GetConsumer());
+        })
+        .Item("value").Do([&] (auto&& fluent) {
+            GetSchema(value.empty() ? typename T::mapped_type{} : value.begin()->second, fluent.GetConsumer());
+        })
+        .EndMap();
+}
+
+template <CIsNullable T>
+void GetSchema(const T& value, NYson::IYsonConsumer* consumer)
+{
+    BuildYsonFluently(consumer) .BeginMap()
+        .Item("type_name").Value("optional")
+        .Item("item").Do([&] (auto&& fluent) {
+            GetSchema(value ? *value : std::decay_t<decltype(*value)>{}, fluent.GetConsumer());
+        })
+        .EndMap();
+}
+
+template <class T>
+void GetSchema(const T& value, NYson::IYsonConsumer* consumer)
+{
+    auto node = ConvertToNode(value);
+    BuildYsonFluently(consumer).Value(FormatEnum(node->GetType()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NYTree::NPrivate

+ 18 - 0
yt/yt/core/ytree/yson_schema.h

@@ -0,0 +1,18 @@
+#pragma once
+
+#include <yt/yt/core/yson/public.h>
+
+namespace NYT::NYTree::NPrivate {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T>
+void GetSchema(const T& value, NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NYTree::NPrivate
+
+#define YSON_SCHEMA_INL_H_
+#include "yson_schema-inl.h"
+#undef YSON_SCHEMA_INL_H_

Some files were not shown because too many files changed in this diff