diff options
author | arkady-e1ppa <arkady-e1ppa@yandex-team.com> | 2024-12-21 06:57:47 +0300 |
---|---|---|
committer | arkady-e1ppa <arkady-e1ppa@yandex-team.com> | 2024-12-21 07:15:03 +0300 |
commit | c70428eb35690f3db0034227f8b03cb8d276590e (patch) | |
tree | d1804d1a0b3a6329eae4639d81dd21f210d50fc7 | |
parent | 6a6b06be9df7960772be6acdcdf5e9507b43de18 (diff) | |
download | ydb-c70428eb35690f3db0034227f8b03cb8d276590e.tar.gz |
YT-21233: Introduce Convert(To/From)TextYsonString in library/cpp/yt/yson_string to be later used for error attribute serialization
commit_hash:f7632c9622c71e023148c9454a9889e9cac75ecd
-rw-r--r-- | library/cpp/yt/yson_string/convert.cpp | 416 | ||||
-rw-r--r-- | library/cpp/yt/yson_string/convert.h | 101 | ||||
-rw-r--r-- | yt/yt/core/yson/writer.cpp | 26 | ||||
-rw-r--r-- | yt/yt/core/ytree/unittests/text_yson_convert_ut.cpp | 273 | ||||
-rw-r--r-- | yt/yt/core/ytree/unittests/ya.make | 1 |
5 files changed, 790 insertions, 27 deletions
diff --git a/library/cpp/yt/yson_string/convert.cpp b/library/cpp/yt/yson_string/convert.cpp index 68241adb78..1beb7cc4c7 100644 --- a/library/cpp/yt/yson_string/convert.cpp +++ b/library/cpp/yt/yson_string/convert.cpp @@ -11,12 +11,43 @@ #include <array> +#include <util/string/escape.h> + #include <util/stream/mem.h> namespace NYT::NYson { //////////////////////////////////////////////////////////////////////////////// +namespace NDetail { + +size_t FloatToStringWithNanInf(double value, char* buf, size_t size) +{ + if (std::isfinite(value)) { + return FloatToString(value, buf, size); + } + + static const TStringBuf nanLiteral = "%nan"; + static const TStringBuf infLiteral = "%inf"; + static const TStringBuf negativeInfLiteral = "%-inf"; + + TStringBuf str; + if (std::isnan(value)) { + str = nanLiteral; + } else if (std::isinf(value) && value > 0) { + str = infLiteral; + } else { + str = negativeInfLiteral; + } + YT_VERIFY(str.size() + 1 <= size); + ::memcpy(buf, str.data(), str.size() + 1); + return str.size(); +} + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + template <> TYsonString ConvertToYsonString<i8>(const i8& value) { @@ -385,4 +416,389 @@ TGuid ConvertFromYsonString<TGuid>(const TYsonStringBuf& str) //////////////////////////////////////////////////////////////////////////////// +template <> +TYsonString ConvertToTextYsonString<i8>(const i8& value) +{ + return ConvertToTextYsonString(static_cast<i64>(value)); +} + +template <> +TYsonString ConvertToTextYsonString<i32>(const i32& value) +{ + return ConvertToTextYsonString(static_cast<i64>(value)); +} + +template <> +TYsonString ConvertToTextYsonString<i64>(const i64& value) +{ + return TYsonString{::ToString(value)}; +} + +template <> +TYsonString ConvertToTextYsonString<ui8>(const ui8& value) +{ + return ConvertToTextYsonString(static_cast<ui64>(value)); +} + +template <> +TYsonString ConvertToTextYsonString<ui32>(const ui32& value) +{ + return ConvertToTextYsonString(static_cast<ui64>(value)); +} + +template <> +TYsonString ConvertToTextYsonString<ui64>(const ui64& value) +{ + return TYsonString{::ToString(value) + 'u'}; +} + +template <> +TYsonString ConvertToTextYsonString<TString>(const TString& value) +{ + return ConvertToTextYsonString(TStringBuf(value)); +} + +template <> +TYsonString ConvertToTextYsonString<std::string>(const std::string& value) +{ + return ConvertToTextYsonString(TStringBuf(value)); +} + +template <> +TYsonString ConvertToTextYsonString<TStringBuf>(const TStringBuf& value) +{ + return TYsonString(NYT::Format("\"%v\"", ::EscapeC(value))); +} + +template <> +TYsonString ConvertToTextYsonString<std::string_view>(const std::string_view& value) +{ + return ConvertToTextYsonString(TStringBuf(value)); +} + +TYsonString ConvertToTextYsonString(const char* value) +{ + return ConvertToTextYsonString(TStringBuf(value)); +} + +template <> +TYsonString ConvertToTextYsonString<float>(const float& value) +{ + return ConvertToTextYsonString(static_cast<double>(value)); +} + +template <> +TYsonString ConvertToTextYsonString<double>(const double& value) +{ + char buf[256]; + auto str = TStringBuf(buf, NDetail::FloatToStringWithNanInf(value, buf, sizeof(buf))); + auto ret = NYT::Format( + "%v%v", + str, + MakeFormatterWrapper([&] (TStringBuilderBase* builder) { + if (str.find('.') == TString::npos && str.find('e') == TString::npos && std::isfinite(value)) { + builder->AppendChar('.'); + } + })); + return TYsonString(std::move(ret)); +} + +template <> +TYsonString ConvertToTextYsonString<bool>(const bool& value) +{ + return value + ? TYsonString(TStringBuf("%true")) + : TYsonString(TStringBuf("%false")); +} + +template <> +TYsonString ConvertToTextYsonString<TInstant>(const TInstant& value) +{ + return ConvertToTextYsonString(value.ToString()); +} + +template <> +TYsonString ConvertToTextYsonString<TDuration>(const TDuration& value) +{ + // ConvertTo does unchecked cast to i64 :(. + return ConvertToTextYsonString(static_cast<i64>(value.MilliSeconds())); +} + +template <> +TYsonString ConvertToTextYsonString<TGuid>(const TGuid& value) +{ + return ConvertToTextYsonString(NYT::ToString(value)); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +template <class TSomeInt> +TSomeInt ReadTextUint(TStringBuf strBuf) +{ + // Drop 'u' + return ::FromString<TSomeInt>(TStringBuf{strBuf.data(), strBuf.length() - 1}); +} + +template <class TSomeInt> +TSomeInt ReadTextInt(TStringBuf strBuf) +{ + return ::FromString<TSomeInt>(TStringBuf{strBuf.data(), strBuf.length()}); +} + +bool IsNumeric(TStringBuf strBuf) +{ + bool isNumeric = true; + bool isNegative = false; + for (int i = 0; i < std::ssize(strBuf); ++i) { + char c = strBuf[i]; + + if (!('0' <= c && c <= '9')) { + if (i == 0 && c == '-') { + isNegative = true; + continue; + } + if (i == std::ssize(strBuf) - 1 && c == 'u' && !isNegative) { + continue; + } + isNumeric = false; + break; + } + } + + return isNumeric; +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class TSomeInt> +TSomeInt ParseSomeIntFromTextYsonString(const TYsonStringBuf& str) +{ + YT_ASSERT(str.GetType() == EYsonType::Node); + auto strBuf = str.AsStringBuf(); + + if (std::ssize(strBuf) == 0 || !IsNumeric(strBuf)) { + throw TYsonLiteralParseException(NYT::Format( + "Unexpected %v\n" + "Value is not numeric", + strBuf)); + } + + if (strBuf.back() == 'u') { + // Drop 'u' + return ReadTextUint<TSomeInt>(strBuf); + } else { + return ReadTextInt<TSomeInt>(strBuf); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +TString DoParseStringFromTextYson(TStringBuf strBuf) +{ + // Remove quotation marks. + return ::UnescapeC(TStringBuf{strBuf.data() + 1, strBuf.length() - 2}); +} + +TString ParseStringFromTextYsonString(const TYsonStringBuf& str) +{ + YT_ASSERT(str.GetType() == EYsonType::Node); + auto strBuf = str.AsStringBuf(); + if (std::ssize(strBuf) < 2 || strBuf.front() != '\"' || strBuf.back() != '\"') { + throw TYsonLiteralParseException(Format( + "Unexpected %v\n" + "Text yson string must begin and end with \\\"", + strBuf)); + } + return DoParseStringFromTextYson(strBuf); +} + +//////////////////////////////////////////////////////////////////////////////// + +double ParseDoubleFromTextYsonString(const TYsonStringBuf& str) +{ + YT_ASSERT(str.GetType() == EYsonType::Node); + auto strBuf = str.AsStringBuf(); + + if (std::ssize(strBuf) < 2) { + throw TYsonLiteralParseException(Format( + "Incorrect remaining string length: expected at least 2, got %v", + std::ssize(strBuf))); + } + + // Check special values first. + // %nan + // %inf, %+inf, %-inf + if (strBuf[0] == '%') { + switch (strBuf[1]) { + case '+': + case 'i': + return std::numeric_limits<double>::infinity(); + + case '-': + return -std::numeric_limits<double>::infinity(); + + case 'n': + return std::numeric_limits<double>::quiet_NaN(); + + default: + throw TYsonLiteralParseException(Format( + "Incorrect %%-literal %v", + strBuf)); + } + } + + return ::FromString<double>(strBuf); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// + +#define PARSE_INT(type, underlyingType) \ + template <> \ + type ConvertFromTextYsonString<type>(const TYsonStringBuf& str) \ + { \ + try { \ + return CheckedIntegralCast<type>(ParseSomeIntFromTextYsonString<underlyingType>(str)); \ + } catch (const std::exception& ex) { \ + throw TYsonLiteralParseException(ex, "Error parsing \"" #type "\" value from YSON"); \ + } \ + } + +PARSE_INT(i8, i64) +PARSE_INT(i16, i64) +PARSE_INT(i32, i64) +PARSE_INT(i64, i64) +PARSE_INT(ui8, ui64) +PARSE_INT(ui16, ui64) +PARSE_INT(ui32, ui64) +PARSE_INT(ui64, ui64) + +#undef PARSE + +template <> +TString ConvertFromTextYsonString<TString>(const TYsonStringBuf& str) +{ + try { + return ParseStringFromTextYsonString(str); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"string\" value from YSON"); + } +} + +template <> +std::string ConvertFromTextYsonString<std::string>(const TYsonStringBuf& str) +{ + return std::string{ConvertFromTextYsonString<TString>(str)}; +} + +template <> +float ConvertFromTextYsonString<float>(const TYsonStringBuf& str) +{ + try { + return static_cast<float>(ParseDoubleFromTextYsonString(str)); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"float\" value from YSON"); + } +} + +template <> +double ConvertFromTextYsonString<double>(const TYsonStringBuf& str) +{ + try { + return ParseDoubleFromTextYsonString(str); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"double\" value from YSON"); + } +} + +template <> +bool ConvertFromTextYsonString<bool>(const TYsonStringBuf& str) +{ + try { + YT_ASSERT(str.GetType() == EYsonType::Node); + auto strBuf = str.AsStringBuf(); + + if (std::ssize(strBuf) == 0) { + throw TYsonLiteralParseException("Empty string"); + } + + char ch = strBuf.front(); + + if (ch == '%') { + if (strBuf != "%true" && strBuf != "%false") { + throw TYsonLiteralParseException(Format( + "Expected %%true or %%false but found %v", + strBuf)); + } + return strBuf == "%true"; + } + + if (ch == '\"') { + return ParseBool(DoParseStringFromTextYson(strBuf)); + } + + // NB(arkady-e1ppa): This check is linear in size(strBuf) + // And thus is tried as the last resort. + if (IsNumeric(strBuf)) { + auto checkValue = [&] (const auto& functor) { + auto value = functor(strBuf); + if (value != 0 && value != 1) { + throw TYsonLiteralParseException(Format( + "Expected 0 or 1 but found %v", + value)); + } + return static_cast<bool>(value); + }; + + if (strBuf.back() == 'u') { + return checkValue(&ReadTextUint<ui64>); + } else { + return checkValue(&ReadTextInt<i64>); + } + } + + throw TYsonLiteralParseException(Format( + "Unexpected %v\n" + "No known conversion to \"boolean\" value", + strBuf)); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"boolean\" value from YSON"); + } +} + +template <> +TInstant ConvertFromTextYsonString<TInstant>(const TYsonStringBuf& str) +{ + try { + return TInstant::ParseIso8601(ParseStringFromTextYsonString(str)); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"instant\" value from YSON"); + } +} + +template <> +TDuration ConvertFromTextYsonString<TDuration>(const TYsonStringBuf& str) +{ + try { + return TDuration::MilliSeconds(ParseSomeIntFromTextYsonString<i64>(str)); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"duration\" value from YSON"); + } +} + +template <> +TGuid ConvertFromTextYsonString<TGuid>(const TYsonStringBuf& str) +{ + try { + return TGuid::FromString(ParseStringFromTextYsonString(str)); + } catch (const std::exception& ex) { + throw TYsonLiteralParseException(ex, "Error parsing \"guid\" value from YSON"); + } +} + +//////////////////////////////////////////////////////////////////////////////// + } // namespace NYT::NYson diff --git a/library/cpp/yt/yson_string/convert.h b/library/cpp/yt/yson_string/convert.h index eedb0939e0..a51821fa30 100644 --- a/library/cpp/yt/yson_string/convert.h +++ b/library/cpp/yt/yson_string/convert.h @@ -13,6 +13,15 @@ namespace NYT::NYson { //////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +size_t FloatToStringWithNanInf(double value, char* buf, size_t size); + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + // Generic forward declarations. template <class T> @@ -24,6 +33,13 @@ TYsonString ConvertToYsonString(const T& value, EYsonFormat format); template <class T> T ConvertFromYsonString(const TYsonStringBuf& str); +// TODO(arkady-e1ppa): Move those to library/cpp/yt/error +// and swap to std::string(_view) to drop dep on library/cpp/yson_string. +template <class T> +TYsonString ConvertToTextYsonString(const T& value) = delete; +template <class T> +T ConvertFromTextYsonString(const TYsonStringBuf& str) = delete; + //////////////////////////////////////////////////////////////////////////////// // Basic specializations for ConvertToYsonString. @@ -44,8 +60,6 @@ TYsonString ConvertToYsonString<ui64>(const ui64& value); template <> TYsonString ConvertToYsonString<TString>(const TString& value); template <> -TYsonString ConvertToYsonString<std::string>(const std::string& value); -template <> TYsonString ConvertToYsonString<TStringBuf>(const TStringBuf& value); TYsonString ConvertToYsonString(const char* value); @@ -93,6 +107,8 @@ ui64 ConvertFromYsonString<ui64>(const TYsonStringBuf& str); template <> TString ConvertFromYsonString<TString>(const TYsonStringBuf& str); +template <> +std::string ConvertFromYsonString<std::string>(const TYsonStringBuf& str); template <> float ConvertFromYsonString<float>(const TYsonStringBuf& str); @@ -113,4 +129,85 @@ TGuid ConvertFromYsonString<TGuid>(const TYsonStringBuf& str); //////////////////////////////////////////////////////////////////////////////// +template <> +TYsonString ConvertToTextYsonString<i8>(const i8& value); +template <> +TYsonString ConvertToTextYsonString<i32>(const i32& value); +template <> +TYsonString ConvertToTextYsonString<i64>(const i64& value); + +template <> +TYsonString ConvertToTextYsonString<ui8>(const ui8& value); +template <> +TYsonString ConvertToTextYsonString<ui32>(const ui32& value); +template <> +TYsonString ConvertToTextYsonString<ui64>(const ui64& value); + +template <> +TYsonString ConvertToTextYsonString<TString>(const TString& value); +template <> +TYsonString ConvertToTextYsonString<std::string>(const std::string& value); +template <> +TYsonString ConvertToTextYsonString<TStringBuf>(const TStringBuf& value); +template <> +TYsonString ConvertToTextYsonString<std::string_view>(const std::string_view& value); +TYsonString ConvertToTextYsonString(const char* value); + +template <> +TYsonString ConvertToTextYsonString<float>(const float& value); +template <> +TYsonString ConvertToTextYsonString<double>(const double& value); + +template <> +TYsonString ConvertToTextYsonString<bool>(const bool& value); + +template <> +TYsonString ConvertToTextYsonString<TInstant>(const TInstant& value); + +template <> +TYsonString ConvertToTextYsonString<TDuration>(const TDuration& value); + +template <> +TYsonString ConvertToTextYsonString<TGuid>(const TGuid& value); + +//////////////////////////////////////////////////////////////////////////////// + +template <> +i8 ConvertFromTextYsonString<i8>(const TYsonStringBuf& str); +template <> +i32 ConvertFromTextYsonString<i32>(const TYsonStringBuf& str); +template <> +i64 ConvertFromTextYsonString<i64>(const TYsonStringBuf& str); + +template <> +ui8 ConvertFromTextYsonString<ui8>(const TYsonStringBuf& str); +template <> +ui32 ConvertFromTextYsonString<ui32>(const TYsonStringBuf& str); +template <> +ui64 ConvertFromTextYsonString<ui64>(const TYsonStringBuf& str); + +template <> +TString ConvertFromTextYsonString<TString>(const TYsonStringBuf& str); +template <> +std::string ConvertFromTextYsonString<std::string>(const TYsonStringBuf& str); + +template <> +float ConvertFromTextYsonString<float>(const TYsonStringBuf& str); +template <> +double ConvertFromTextYsonString<double>(const TYsonStringBuf& str); + +template <> +bool ConvertFromTextYsonString<bool>(const TYsonStringBuf& str); + +template <> +TInstant ConvertFromTextYsonString<TInstant>(const TYsonStringBuf& str); + +template <> +TDuration ConvertFromTextYsonString<TDuration>(const TYsonStringBuf& str); + +template <> +TGuid ConvertFromTextYsonString<TGuid>(const TYsonStringBuf& str); + +//////////////////////////////////////////////////////////////////////////////// + } // namespace NYT::NYson diff --git a/yt/yt/core/yson/writer.cpp b/yt/yt/core/yson/writer.cpp index d31abf0b46..32e3f1636c 100644 --- a/yt/yt/core/yson/writer.cpp +++ b/yt/yt/core/yson/writer.cpp @@ -132,30 +132,6 @@ void WriteUtf8String(const char* str, size_t len, IOutputStream& output) } } -size_t FloatToStringWithNanInf(double value, char* buf, size_t size) -{ - if (std::isfinite(value)) { - return FloatToString(value, buf, size); - } - - static const TStringBuf nanLiteral = "%nan"; - static const TStringBuf infLiteral = "%inf"; - static const TStringBuf negativeInfLiteral = "%-inf"; - - TStringBuf str; - if (std::isnan(value)) { - str = nanLiteral; - } else if (std::isinf(value) && value > 0) { - str = infLiteral; - } else { - str = negativeInfLiteral; - } - YT_VERIFY(str.size() + 1 <= size); - ::memcpy(buf, str.data(), str.size() + 1); - return str.size(); -} - - } // namespace //////////////////////////////////////////////////////////////////////////////// @@ -277,7 +253,7 @@ void TYsonWriter::OnDoubleScalar(double value) Stream_->Write(&value, sizeof(double)); } else { char buf[256]; - auto str = TStringBuf(buf, FloatToStringWithNanInf(value, buf, sizeof(buf))); + auto str = TStringBuf(buf, NDetail::FloatToStringWithNanInf(value, buf, sizeof(buf))); Stream_->Write(str); if (str.find('.') == TString::npos && str.find('e') == TString::npos && std::isfinite(value)) { Stream_->Write("."); diff --git a/yt/yt/core/ytree/unittests/text_yson_convert_ut.cpp b/yt/yt/core/ytree/unittests/text_yson_convert_ut.cpp new file mode 100644 index 0000000000..75913bed0b --- /dev/null +++ b/yt/yt/core/ytree/unittests/text_yson_convert_ut.cpp @@ -0,0 +1,273 @@ +#include <yt/yt/core/test_framework/framework.h> + +#include <yt/yt/core/ytree/convert.h> + +#include <library/cpp/yt/misc/source_location.h> + +#include <library/cpp/yt/yson_string/convert.h> + +namespace NYT::NYTree { +namespace { + +using namespace NYson; + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +void CheckEqualConversionToTextYson(const T& value, const TSourceLocation& loc = YT_CURRENT_SOURCE_LOCATION) +{ + EXPECT_EQ(ConvertToTextYsonString(value).AsStringBuf(), ConvertToYsonString(value, EYsonFormat::Text).AsStringBuf()) + << NYT::Format("At %v", loc); +} + +template <class T, class U> +void CheckEqualConversionToFromTextYson(const U& value, const TSourceLocation& loc = YT_CURRENT_SOURCE_LOCATION) +{ + auto yson = ConvertToTextYsonString(value); + EXPECT_EQ(ConvertFromTextYsonString<T>(yson), ConvertTo<T>(yson)) + << NYT::Format("At %v", loc); +} + +template <class T> +void CheckEqualConversionFromTextYson(TStringBuf value, const TSourceLocation& loc = YT_CURRENT_SOURCE_LOCATION) +{ + NYson::TYsonString yson(value); + EXPECT_EQ(ConvertTo<T>(yson), ConvertTo<T>(yson)) + << NYT::Format("At %v", loc); +} + +TEST(TTextYsonConvertTest, ConvertToTextIntegrals) +{ + CheckEqualConversionToTextYson<i8>(+14); + CheckEqualConversionToTextYson<i8>(0); + CheckEqualConversionToTextYson<i8>(-15); + CheckEqualConversionToTextYson<i32>(+100); + CheckEqualConversionToTextYson<i32>(0); + CheckEqualConversionToTextYson<i32>(-123); + CheckEqualConversionToTextYson<i64>(+100); + CheckEqualConversionToTextYson<i64>(0); + CheckEqualConversionToTextYson<i64>(-123); + + CheckEqualConversionToTextYson<ui8>(+100); + CheckEqualConversionToTextYson<ui8>(0); + CheckEqualConversionToTextYson<ui32>(+100); + CheckEqualConversionToTextYson<ui32>(0); + CheckEqualConversionToTextYson<ui64>(+100); + CheckEqualConversionToTextYson<ui64>(0); +} + +TEST(TTextYsonConvertTest, ConvertToTextIntegralsLimits) +{ + CheckEqualConversionToTextYson<i64>(std::numeric_limits<i64>::max()); + CheckEqualConversionToTextYson<i64>(std::numeric_limits<i64>::min()); + + CheckEqualConversionToTextYson<ui64>(std::numeric_limits<ui64>::max()); + CheckEqualConversionToTextYson<ui64>(std::numeric_limits<ui64>::min()); +} + +TEST(TTextYsonConvertTest, ConvertToTextFloats) +{ + CheckEqualConversionToTextYson<float>(0.0); + CheckEqualConversionToTextYson<float>(-0.0); + CheckEqualConversionToTextYson<float>(-7.7777); + CheckEqualConversionToTextYson<float>(+9.243); + + CheckEqualConversionToTextYson<double>(0.0); + CheckEqualConversionToTextYson<double>(-0.0); + CheckEqualConversionToTextYson<double>(-7.7777); + CheckEqualConversionToTextYson<double>(+9.243); +} + +TEST(TTextYsonConvertTest, ConvertToTextFloatsSpecialValues) +{ + CheckEqualConversionToTextYson<double>(std::numeric_limits<double>::min()); + CheckEqualConversionToTextYson<double>(std::numeric_limits<double>::max()); + CheckEqualConversionToTextYson<double>(std::numeric_limits<double>::infinity()); + CheckEqualConversionToTextYson<double>(-std::numeric_limits<double>::infinity()); + CheckEqualConversionToTextYson<double>(std::numeric_limits<double>::quiet_NaN()); +} + +TEST(TTextYsonConvertTest, ConvertToTextOtherPrimitiveTypes) +{ + CheckEqualConversionToTextYson<bool>(true); + CheckEqualConversionToTextYson<bool>(false); + + CheckEqualConversionToTextYson<TInstant>(TInstant::Now()); + CheckEqualConversionToTextYson<TInstant>(TInstant::Zero()); + CheckEqualConversionToTextYson<TInstant>(TInstant::FromValue(42)); + + CheckEqualConversionToTextYson<TDuration>(TDuration::Zero()); + CheckEqualConversionToTextYson<TDuration>(TDuration::Seconds(2)); + CheckEqualConversionToTextYson<TDuration>(TDuration::MilliSeconds(123)); + CheckEqualConversionToTextYson<TDuration>(TDuration::MicroSeconds(12)); + + CheckEqualConversionToTextYson<std::string>("Hello, world!"); + CheckEqualConversionToTextYson<std::string>("This is a so-called \"quotation marks\" test"); + CheckEqualConversionToTextYson<std::string>("This tests \r other \b hidden symbols \n"); + CheckEqualConversionToTextYson<std::string>("And this one tests special numbers numbers \x012"); + + CheckEqualConversionToTextYson<TGuid>(TGuid::Create()); +} + +TEST(TTextYsonConvertTest, ConvertFromTextIntegrals) +{ + CheckEqualConversionToFromTextYson<i8>(+15); + CheckEqualConversionToFromTextYson<i8>(0); + CheckEqualConversionToFromTextYson<i8>(-15); + CheckEqualConversionToFromTextYson<i32>(+100); + CheckEqualConversionToFromTextYson<i32>(0); + CheckEqualConversionToFromTextYson<i32>(-123); + CheckEqualConversionToFromTextYson<i64>(+100); + CheckEqualConversionToFromTextYson<i64>(0); + CheckEqualConversionToFromTextYson<i64>(-123); + + CheckEqualConversionToFromTextYson<ui8>(+100); + CheckEqualConversionToFromTextYson<ui8>(0); + CheckEqualConversionToFromTextYson<ui32>(+100); + CheckEqualConversionToFromTextYson<ui32>(0); + CheckEqualConversionToFromTextYson<ui64>(+100); + CheckEqualConversionToFromTextYson<ui64>(0); +} + +TEST(TTextYsonConvertTest, ConvertFromTextIntegralsLimits) +{ + CheckEqualConversionToFromTextYson<i64>(std::numeric_limits<i64>::max()); + CheckEqualConversionToFromTextYson<i64>(std::numeric_limits<i64>::min()); + + CheckEqualConversionToFromTextYson<ui64>(std::numeric_limits<ui64>::max()); + CheckEqualConversionToFromTextYson<ui64>(std::numeric_limits<ui64>::min()); +} + +TEST(TTextYsonConvertTest, ConvertFromTextFloats) +{ + CheckEqualConversionToFromTextYson<double>(0.0); + CheckEqualConversionToFromTextYson<double>(-0.0); + CheckEqualConversionToFromTextYson<double>(-7.7777); + CheckEqualConversionToFromTextYson<double>(+9.243); +} + +TEST(TTextYsonConvertTest, ConvertFromTextFloatsSpecialValues) +{ + CheckEqualConversionToFromTextYson<double>(std::numeric_limits<double>::min()); + CheckEqualConversionToFromTextYson<double>(std::numeric_limits<double>::max()); + CheckEqualConversionToFromTextYson<double>(std::numeric_limits<double>::infinity()); + CheckEqualConversionToFromTextYson<double>(-std::numeric_limits<double>::infinity()); + + // nans do not compare. + // CheckEqualConversionFromTextYson<double>(std::numeric_limits<double>::quiet_NaN()); +} + +TEST(TTextYsonConvertTest, ConvertFromTextOtherPrimitiveTypes) +{ + CheckEqualConversionToTextYson<bool>(true); + CheckEqualConversionToTextYson<bool>(false); + CheckEqualConversionToTextYson<bool>("true"); + CheckEqualConversionToTextYson<bool>("false"); + CheckEqualConversionToTextYson<bool>("0"); + CheckEqualConversionToTextYson<bool>("1"); + + CheckEqualConversionToTextYson<TInstant>(TInstant::Now()); + CheckEqualConversionToTextYson<TInstant>(TInstant::Zero()); + CheckEqualConversionToTextYson<TInstant>(TInstant::FromValue(42)); + + CheckEqualConversionToTextYson<TDuration>(TDuration::Zero()); + CheckEqualConversionToTextYson<TDuration>(TDuration::Seconds(2)); + CheckEqualConversionToTextYson<TDuration>(TDuration::MilliSeconds(123)); + CheckEqualConversionToTextYson<TDuration>(TDuration::MicroSeconds(12)); + + CheckEqualConversionToTextYson<std::string>("Hello, world!"); + CheckEqualConversionToTextYson<std::string>("This is a so-called \"quotation marks\" test"); + CheckEqualConversionToTextYson<std::string>("This tests \r other \b hidden symbols \n"); + CheckEqualConversionToTextYson<std::string>("And this one tests special numbers numbers \x012"); + + CheckEqualConversionToTextYson<TGuid>(TGuid::Create()); +} + +TEST(TTextYsonConvertTest, ConvertFromTextIntegralsTypeMissmatch) +{ + CheckEqualConversionToFromTextYson<i8>(static_cast<ui64>(+100)); + CheckEqualConversionToFromTextYson<i8>(static_cast<ui64>(0)); + CheckEqualConversionToFromTextYson<i32>(static_cast<ui64>(+100)); + CheckEqualConversionToFromTextYson<i32>(static_cast<ui64>(0)); + CheckEqualConversionToFromTextYson<i64>(static_cast<ui64>(+100)); + CheckEqualConversionToFromTextYson<i64>(static_cast<ui64>(0)); +} + +TEST(TTextYsonConvertTest, ConvertFromTextTypeMissmatch) +{ + CheckEqualConversionFromTextYson<bool>("%true"); + CheckEqualConversionFromTextYson<bool>("%false"); + CheckEqualConversionFromTextYson<bool>("1"); + CheckEqualConversionFromTextYson<bool>("0"); + CheckEqualConversionFromTextYson<bool>("-0"); + CheckEqualConversionFromTextYson<bool>("1u"); + CheckEqualConversionFromTextYson<bool>("0u"); + + CheckEqualConversionFromTextYson<bool>(ConvertToTextYsonString("true").AsStringBuf()); + CheckEqualConversionFromTextYson<bool>(ConvertToTextYsonString("false").AsStringBuf()); + CheckEqualConversionFromTextYson<bool>(ConvertToTextYsonString("1").AsStringBuf()); + CheckEqualConversionFromTextYson<bool>(ConvertToTextYsonString("0").AsStringBuf()); +} + +TEST(TTextYsonConvertTest, ConvertFromTextYsonStringThrowBasicCases) +{ + auto fromPayload = [] (const auto& value) { + return NYson::TYsonString(TString(value)); + }; + + // Overflow. + EXPECT_ANY_THROW(ConvertFromTextYsonString<i8>(fromPayload("123123123213"))); + EXPECT_ANY_THROW(ConvertTo<i8>(fromPayload("123123123213"))); + + // Negative. + EXPECT_ANY_THROW(ConvertFromTextYsonString<ui64>(fromPayload("-123"))); + EXPECT_ANY_THROW(ConvertTo<ui64>(fromPayload("-123"))); + + // Non-numeric. + EXPECT_ANY_THROW(ConvertFromTextYsonString<i64>(fromPayload("haha"))); + EXPECT_ANY_THROW(ConvertFromTextYsonString<i64>(fromPayload("123qq"))); + EXPECT_ANY_THROW(ConvertFromTextYsonString<i64>(fromPayload("-123u"))); + EXPECT_ANY_THROW(ConvertTo<i64>(fromPayload("haha"))); + EXPECT_ANY_THROW(ConvertTo<i64>(fromPayload("123qq"))); + EXPECT_ANY_THROW(ConvertTo<i64>(fromPayload("-123u"))); + + // Big positive to bool + EXPECT_ANY_THROW(ConvertFromTextYsonString<bool>(fromPayload("42"))); + EXPECT_ANY_THROW(ConvertTo<bool>(fromPayload("42"))); + + // Garbage to bool + EXPECT_ANY_THROW(ConvertFromTextYsonString<bool>(fromPayload("%falsse"))); + EXPECT_ANY_THROW(ConvertTo<bool>(fromPayload("%falsse"))); + + // Wrong string to bool + EXPECT_ANY_THROW(ConvertFromTextYsonString<bool>(fromPayload("\"True\""))); + EXPECT_ANY_THROW(ConvertFromTextYsonString<bool>(fromPayload("\"False\""))); + EXPECT_ANY_THROW(ConvertFromTextYsonString<bool>(fromPayload("\"1u\""))); + EXPECT_ANY_THROW(ConvertFromTextYsonString<bool>(fromPayload("\"0u\""))); + EXPECT_ANY_THROW(ConvertTo<bool>(fromPayload("\"True\""))); + EXPECT_ANY_THROW(ConvertTo<bool>(fromPayload("\"False\""))); + EXPECT_ANY_THROW(ConvertTo<bool>(fromPayload("\"1u\""))); + EXPECT_ANY_THROW(ConvertTo<bool>(fromPayload("\"0u\""))); + + // Wrong string to string + EXPECT_ANY_THROW(ConvertFromTextYsonString<std::string>(fromPayload(""))); + EXPECT_ANY_THROW(ConvertFromTextYsonString<std::string>(fromPayload("\""))); + EXPECT_ANY_THROW(ConvertFromTextYsonString<std::string>(fromPayload("haha\""))); + EXPECT_ANY_THROW(ConvertFromTextYsonString<std::string>(fromPayload("\'oops\'"))); + EXPECT_ANY_THROW(ConvertTo<std::string>(fromPayload(""))); + EXPECT_ANY_THROW(ConvertTo<std::string>(fromPayload("\""))); + EXPECT_ANY_THROW(ConvertTo<std::string>(fromPayload("haha\""))); + EXPECT_ANY_THROW(ConvertTo<std::string>(fromPayload("\'oops\'"))); + + // Wrong literal to double + EXPECT_ANY_THROW(ConvertFromTextYsonString<double>(fromPayload("%%"))); + EXPECT_ANY_THROW(ConvertFromTextYsonString<std::string>(fromPayload("%42inf"))); + EXPECT_ANY_THROW(ConvertFromTextYsonString<std::string>(fromPayload("%NaaN"))); + EXPECT_ANY_THROW(ConvertTo<double>(fromPayload("%%"))); + EXPECT_ANY_THROW(ConvertTo<std::string>(fromPayload("%42inf"))); + EXPECT_ANY_THROW(ConvertTo<std::string>(fromPayload("%NaaN"))); +} + +//////////////////////////////////////////////////////////////////////////////// +} // namespace +} // namespace NYT::NYTree diff --git a/yt/yt/core/ytree/unittests/ya.make b/yt/yt/core/ytree/unittests/ya.make index 7196cea98c..1bc54b95aa 100644 --- a/yt/yt/core/ytree/unittests/ya.make +++ b/yt/yt/core/ytree/unittests/ya.make @@ -10,6 +10,7 @@ SRCS( resolver_ut.cpp serialize_ut.cpp service_combiner_ut.cpp + text_yson_convert_ut.cpp tree_builder_ut.cpp lazy_ypath_service_ut.cpp yson_schema_ut.cpp |