diff --git a/common/values/string_value.cc b/common/values/string_value.cc index 98912d32c..c14756bb2 100644 --- a/common/values/string_value.cc +++ b/common/values/string_value.cc @@ -742,7 +742,7 @@ absl::StatusOr SubstringImpl(const absl::Cord& cord, uint64_t start, } if (size_code_points == end) { return cord.Subcord(start_code_units, - size_code_points - start_code_units); + size_code_units - start_code_units); } char32_t code_point; size_t code_units; diff --git a/common/values/string_value_test.cc b/common/values/string_value_test.cc index 201724905..380746e1e 100644 --- a/common/values/string_value_test.cc +++ b/common/values/string_value_test.cc @@ -412,6 +412,35 @@ TEST_F(StringValueTest, CharAt) { ".charAt(): is less than 0"))); } +TEST_F(StringValueTest, Substring) { + using ::cel::test::ErrorValueIs; + using ::cel::test::StringValueIs; + + // Each '€' is three bytes, so the cord built here is 18 bytes and is stored + // as a large (cord-backed) value. The substring length must be measured in + // code units, not code points, otherwise the cord overload truncates a + // multi-byte character or underflows the length. + StringValue unicode_cord = StringValue(absl::Cord("€€€€€€")); + StringValue unicode_view = StringValue("€€€€€€"); + + EXPECT_THAT(unicode_cord.Substring(0, 2), StringValueIs("€€")); + EXPECT_THAT(unicode_view.Substring(0, 2), StringValueIs("€€")); + EXPECT_THAT(unicode_cord.Substring(1, 2), StringValueIs("€")); + EXPECT_THAT(unicode_view.Substring(1, 2), StringValueIs("€")); + EXPECT_THAT(unicode_cord.Substring(2, 4), StringValueIs("€€")); + EXPECT_THAT(unicode_view.Substring(2, 4), StringValueIs("€€")); + EXPECT_THAT(unicode_cord.Substring(2), StringValueIs("€€€€")); + EXPECT_THAT(unicode_view.Substring(2), StringValueIs("€€€€")); + + EXPECT_THAT(unicode_cord.Substring(0, 7), + ErrorValueIs(absl::InvalidArgumentError( + ".substring(, ): or is " + "greater than .size()"))); + EXPECT_THAT(unicode_cord.Substring(-1), + ErrorValueIs(absl::InvalidArgumentError( + ".substring(): is less than 0"))); +} + TEST_F(StringValueTest, Join) { using ::cel::runtime_internal::CreateNoMatchingOverloadError; using ::cel::test::ErrorValueIs;