diff --git a/conformance/BUILD b/conformance/BUILD index 01cb6dc94..09ff3acaf 100644 --- a/conformance/BUILD +++ b/conformance/BUILD @@ -30,6 +30,7 @@ ALL_TESTS = [ "@com_google_cel_spec//tests/simple:testdata/lists.textproto", "@com_google_cel_spec//tests/simple:testdata/logic.textproto", "@com_google_cel_spec//tests/simple:testdata/macros.textproto", + "@com_google_cel_spec//tests/simple:testdata/math_ext.textproto", "@com_google_cel_spec//tests/simple:testdata/namespace.textproto", "@com_google_cel_spec//tests/simple:testdata/optionals.textproto", "@com_google_cel_spec//tests/simple:testdata/parse.textproto", @@ -37,6 +38,7 @@ ALL_TESTS = [ "@com_google_cel_spec//tests/simple:testdata/proto2.textproto", "@com_google_cel_spec//tests/simple:testdata/proto3.textproto", "@com_google_cel_spec//tests/simple:testdata/string.textproto", + "@com_google_cel_spec//tests/simple:testdata/string_ext.textproto", "@com_google_cel_spec//tests/simple:testdata/timestamps.textproto", "@com_google_cel_spec//tests/simple:testdata/unknowns.textproto", "@com_google_cel_spec//tests/simple:testdata/wrappers.textproto", @@ -65,6 +67,9 @@ cc_binary( "//eval/public:transform_utility", "//extensions:bindings_ext", "//extensions:encoders", + "//extensions:math_ext", + "//extensions:math_ext_macros", + "//extensions:strings", "//extensions/protobuf:enum_adapter", "//extensions/protobuf:memory_manager", "//extensions/protobuf:runtime_adapter", @@ -139,6 +144,20 @@ cc_binary( # Legacy value does not support optional_type. "--skip_test=optionals/optionals", + + # Not yet implemented. + "--skip_test=string_ext/char_at", + "--skip_test=string_ext/index_of", + "--skip_test=string_ext/last_index_of", + "--skip_test=string_ext/ascii_casing/upperascii", + "--skip_test=string_ext/ascii_casing/upperascii_unicode", + "--skip_test=string_ext/ascii_casing/upperascii_unicode_with_space", + "--skip_test=string_ext/replace", + "--skip_test=string_ext/substring", + "--skip_test=string_ext/trim", + "--skip_test=string_ext/quote", + "--skip_test=string_ext/value_errors", + "--skip_test=string_ext/type_errors", ] + ["$(location " + test + ")" for test in ALL_TESTS], data = [ ":server", @@ -187,6 +206,20 @@ cc_binary( # TODO(issues/119): Strong typing support for enums, specified but not implemented. "--skip_test=enums/strong_proto2", "--skip_test=enums/strong_proto3", + + # Not yet implemented. + "--skip_test=string_ext/char_at", + "--skip_test=string_ext/index_of", + "--skip_test=string_ext/last_index_of", + "--skip_test=string_ext/ascii_casing/upperascii", + "--skip_test=string_ext/ascii_casing/upperascii_unicode", + "--skip_test=string_ext/ascii_casing/upperascii_unicode_with_space", + "--skip_test=string_ext/replace", + "--skip_test=string_ext/substring", + "--skip_test=string_ext/trim", + "--skip_test=string_ext/quote", + "--skip_test=string_ext/value_errors", + "--skip_test=string_ext/type_errors", ] + ["$(location " + test + ")" for test in ALL_TESTS], data = [ ":server", diff --git a/conformance/server.cc b/conformance/server.cc index 09789d844..7ca2fd64f 100644 --- a/conformance/server.cc +++ b/conformance/server.cc @@ -34,10 +34,13 @@ #include "eval/public/transform_utility.h" #include "extensions/bindings_ext.h" #include "extensions/encoders.h" +#include "extensions/math_ext.h" +#include "extensions/math_ext_macros.h" #include "extensions/protobuf/enum_adapter.h" #include "extensions/protobuf/memory_manager.h" #include "extensions/protobuf/runtime_adapter.h" #include "extensions/protobuf/type_reflector.h" +#include "extensions/strings.h" #include "internal/status_macros.h" #include "parser/macro_registry.h" #include "parser/options.h" @@ -123,6 +126,7 @@ absl::Status LegacyParse(const conformance::v1alpha1::ParseRequest& request, cel::MacroRegistry macros; CEL_RETURN_IF_ERROR(cel::RegisterStandardMacros(macros, options)); CEL_RETURN_IF_ERROR(cel::extensions::RegisterBindingsMacros(macros, options)); + CEL_RETURN_IF_ERROR(cel::extensions::RegisterMathMacros(macros, options)); CEL_ASSIGN_OR_RETURN(auto source, cel::NewSource(request.cel_source(), request.source_location())); CEL_ASSIGN_OR_RETURN(auto parsed_expr, @@ -174,6 +178,10 @@ class LegacyConformanceServiceImpl : public ConformanceServiceInterface { RegisterBuiltinFunctions(builder->GetRegistry(), options)); CEL_RETURN_IF_ERROR(cel::extensions::RegisterEncodersFunctions( builder->GetRegistry(), options)); + CEL_RETURN_IF_ERROR(cel::extensions::RegisterStringsFunctions( + builder->GetRegistry(), options)); + CEL_RETURN_IF_ERROR(cel::extensions::RegisterMathExtensionFunctions( + builder->GetRegistry(), options)); return absl::WrapUnique( new LegacyConformanceServiceImpl(std::move(builder))); @@ -314,6 +322,10 @@ class ModernConformanceServiceImpl : public ConformanceServiceInterface { CEL_RETURN_IF_ERROR(cel::extensions::EnableOptionalTypes(builder)); CEL_RETURN_IF_ERROR(cel::extensions::RegisterEncodersFunctions( builder.function_registry(), options)); + CEL_RETURN_IF_ERROR(cel::extensions::RegisterStringsFunctions( + builder.function_registry(), options)); + CEL_RETURN_IF_ERROR(cel::extensions::RegisterMathExtensionFunctions( + builder.function_registry(), options)); return std::move(builder).Build(); } diff --git a/eval/public/BUILD b/eval/public/BUILD index f8f88bc37..f4e158403 100644 --- a/eval/public/BUILD +++ b/eval/public/BUILD @@ -1265,16 +1265,10 @@ cc_library( srcs = ["string_extension_func_registrar.cc"], hdrs = ["string_extension_func_registrar.h"], deps = [ - ":cel_function", - ":cel_function_adapter", ":cel_function_registry", ":cel_options", - ":cel_value", - "//eval/public/containers:container_backed_list_impl", - "//internal:status_macros", + "//extensions:strings", "@com_google_absl//absl/status", - "@com_google_absl//absl/strings", - "@com_google_protobuf//:protobuf", ], ) diff --git a/eval/public/string_extension_func_registrar.cc b/eval/public/string_extension_func_registrar.cc index ff66dafd2..9bccfe6d1 100644 --- a/eval/public/string_extension_func_registrar.cc +++ b/eval/public/string_extension_func_registrar.cc @@ -14,136 +14,16 @@ #include "eval/public/string_extension_func_registrar.h" -#include -#include -#include -#include - #include "absl/status/status.h" -#include "absl/strings/ascii.h" -#include "absl/strings/str_join.h" -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" -#include "eval/public/cel_function_adapter.h" #include "eval/public/cel_function_registry.h" #include "eval/public/cel_options.h" -#include "eval/public/cel_value.h" -#include "eval/public/containers/container_backed_list_impl.h" -#include "internal/status_macros.h" -#include "google/protobuf/arena.h" +#include "extensions/strings.h" namespace google::api::expr::runtime { -using google::protobuf::Arena; - -constexpr char kEmptySeparator[] = ""; - -CelValue SplitWithLimit(Arena* arena, const CelValue::StringHolder value, - const CelValue::StringHolder delimiter, int64_t limit) { - // As per specifications[1]. return empty list in case limit is set to 0. - // 1. https://pkg.go.dev/github.com/google/cel-go/ext#Strings - std::vector string_split = {}; - if (limit < 0) { - // perform regular split operation in case of limit < 0 - string_split = absl::StrSplit(value.value(), delimiter.value()); - } else if (limit > 0) { - // The absl::MaxSplits generate at max limit + 1 number of elements where as - // it is suppose to return limit nunmber of elements as per - // specifications[1]. - // To resolve the inconsistency passing limit-1 as input to absl::MaxSplits - // 1. https://pkg.go.dev/github.com/google/cel-go/ext#Strings - string_split = absl::StrSplit( - value.value(), absl::MaxSplits(delimiter.value(), limit - 1)); - } - std::vector cel_list; - cel_list.reserve(string_split.size()); - for (const std::string& substring : string_split) { - cel_list.push_back( - CelValue::CreateString(Arena::Create(arena, substring))); - } - auto result = CelValue::CreateList( - Arena::Create(arena, cel_list)); - return result; -} - -CelValue Split(Arena* arena, CelValue::StringHolder value, - CelValue::StringHolder delimiter) { - return SplitWithLimit(arena, value, delimiter, -1); -} - -CelValue::StringHolder JoinWithSeparator(Arena* arena, const CelValue& value, - absl::string_view separator) { - const CelList* cel_list = value.ListOrDie(); - std::vector string_list; - string_list.reserve(cel_list->size()); - for (int i = 0; i < cel_list->size(); i++) { - string_list.push_back(cel_list->Get(arena, i).StringOrDie().value()); - } - auto result = - Arena::Create(arena, absl::StrJoin(string_list, separator)); - return CelValue::StringHolder(result); -} - -CelValue::StringHolder Join(Arena* arena, const CelValue& value) { - return JoinWithSeparator(arena, value, kEmptySeparator); -} - -CelValue::StringHolder LowerAscii(Arena* arena, - const CelValue::StringHolder value) { - auto result = - Arena::Create(arena, absl::AsciiStrToLower(value.value())); - return CelValue::StringHolder(result); -} - absl::Status RegisterStringExtensionFunctions( CelFunctionRegistry* registry, const InterpreterOptions& options) { - if (options.enable_string_concat) { - CEL_RETURN_IF_ERROR( - (FunctionAdapter::CreateAndRegister( - "join", true, - [](Arena* arena, CelValue value) -> CelValue::StringHolder { - return Join(arena, value); - }, - registry))); - CEL_RETURN_IF_ERROR(( - FunctionAdapter:: - CreateAndRegister( - "join", true, - [](Arena* arena, CelValue value, - CelValue::StringHolder separator) -> CelValue::StringHolder { - return JoinWithSeparator(arena, value, separator.value()); - }, - registry))); - } - CEL_RETURN_IF_ERROR( - (FunctionAdapter:: - CreateAndRegister( - "split", true, - [](Arena* arena, CelValue::StringHolder str, - CelValue::StringHolder delimiter) -> CelValue { - return Split(arena, str, delimiter); - }, - registry))); - - CEL_RETURN_IF_ERROR( - (FunctionAdapter:: - CreateAndRegister( - "split", true, - [](Arena* arena, CelValue::StringHolder str, - CelValue::StringHolder delimiter, int64_t limit) -> CelValue { - return SplitWithLimit(arena, str, delimiter, limit); - }, - registry))); - CEL_RETURN_IF_ERROR( - (FunctionAdapter:: - CreateAndRegister( - "lowerAscii", true, - [](Arena* arena, CelValue::StringHolder str) - -> CelValue::StringHolder { return LowerAscii(arena, str); }, - registry))); - return absl::OkStatus(); + return cel::extensions::RegisterStringsFunctions(registry, options); } + } // namespace google::api::expr::runtime diff --git a/eval/public/string_extension_func_registrar.h b/eval/public/string_extension_func_registrar.h index 9772092e1..98c296745 100644 --- a/eval/public/string_extension_func_registrar.h +++ b/eval/public/string_extension_func_registrar.h @@ -15,14 +15,13 @@ #ifndef THIRD_PARTY_CEL_CPP_EVAL_PUBLIC_STRING_EXTENSION_FUNC_REGISTRAR_H_ #define THIRD_PARTY_CEL_CPP_EVAL_PUBLIC_STRING_EXTENSION_FUNC_REGISTRAR_H_ -#include "eval/public/cel_function.h" +#include "absl/status/status.h" #include "eval/public/cel_function_registry.h" +#include "eval/public/cel_options.h" namespace google::api::expr::runtime { // Register string related widely used extension functions. -// TODO(uncreated-issue/22): Move String extension function to -// extensions absl::Status RegisterStringExtensionFunctions( CelFunctionRegistry* registry, const InterpreterOptions& options = InterpreterOptions()); diff --git a/extensions/BUILD b/extensions/BUILD index 41fea1d1b..56b49c3f6 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -306,3 +306,48 @@ cc_test( "@com_google_protobuf//:protobuf", ], ) + +cc_library( + name = "strings", + srcs = ["strings.cc"], + hdrs = ["strings.h"], + deps = [ + "//common:casting", + "//common:type", + "//common:value", + "//eval/public:cel_function_registry", + "//eval/public:cel_options", + "//internal:status_macros", + "//internal:utf8", + "//runtime:function_adapter", + "//runtime:function_registry", + "//runtime:runtime_options", + "//runtime/internal:errors", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:cord", + "@com_google_absl//absl/strings:string_view", + ], +) + +cc_test( + name = "strings_test", + srcs = ["strings_test.cc"], + deps = [ + ":strings", + "//common:memory", + "//common:value", + "//extensions/protobuf:runtime_adapter", + "//internal:testing", + "//parser", + "//parser:options", + "//runtime", + "//runtime:activation", + "//runtime:runtime_builder", + "//runtime:runtime_options", + "//runtime:standard_runtime_builder_factory", + "@com_google_absl//absl/strings:cord", + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_cc_proto", + ], +) diff --git a/extensions/strings.cc b/extensions/strings.cc new file mode 100644 index 000000000..84b0dcdd6 --- /dev/null +++ b/extensions/strings.cc @@ -0,0 +1,262 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "extensions/strings.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/ascii.h" +#include "absl/strings/cord.h" +#include "absl/strings/string_view.h" +#include "common/casting.h" +#include "common/type.h" +#include "common/value.h" +#include "common/value_manager.h" +#include "eval/public/cel_function_registry.h" +#include "eval/public/cel_options.h" +#include "internal/status_macros.h" +#include "internal/utf8.h" +#include "runtime/function_adapter.h" +#include "runtime/function_registry.h" +#include "runtime/internal/errors.h" +#include "runtime/runtime_options.h" + +namespace cel::extensions { + +namespace { + +struct AppendToStringVisitor { + std::string& append_to; + + void operator()(absl::string_view string) const { append_to.append(string); } + + void operator()(const absl::Cord& cord) const { + append_to.append(static_cast(cord)); + } +}; + +absl::StatusOr Join2(ValueManager& value_manager, const ListValue& value, + const StringValue& separator) { + std::string result; + CEL_ASSIGN_OR_RETURN(auto iterator, value.NewIterator(value_manager)); + Value element_scratch; + if (iterator->HasNext()) { + CEL_ASSIGN_OR_RETURN(auto element, + iterator->Next(value_manager, element_scratch)); + if (auto string_element = As(element); string_element) { + string_element->NativeValue(AppendToStringVisitor{result}); + } else { + return ErrorValue{ + runtime_internal::CreateNoMatchingOverloadError("join")}; + } + } + std::string separator_scratch; + absl::string_view separator_view = separator.NativeString(separator_scratch); + while (iterator->HasNext()) { + result.append(separator_view); + CEL_ASSIGN_OR_RETURN(auto element, + iterator->Next(value_manager, element_scratch)); + if (auto string_element = As(element); string_element) { + string_element->NativeValue(AppendToStringVisitor{result}); + } else { + return ErrorValue{ + runtime_internal::CreateNoMatchingOverloadError("join")}; + } + } + result.shrink_to_fit(); + // We assume the original string was well-formed. + return value_manager.CreateUncheckedStringValue(std::move(result)); +} + +absl::StatusOr Join1(ValueManager& value_manager, + const ListValue& value) { + return Join2(value_manager, value, StringValue{}); +} + +struct SplitWithEmptyDelimiter { + ValueManager& value_manager; + int64_t& limit; + ListValueBuilder& builder; + + absl::StatusOr operator()(absl::string_view string) const { + char32_t rune; + size_t count; + std::string buffer; + buffer.reserve(4); + while (!string.empty() && limit > 1) { + std::tie(rune, count) = internal::Utf8Decode(string); + buffer.clear(); + internal::Utf8Encode(buffer, rune); + CEL_RETURN_IF_ERROR(builder.Add( + value_manager.CreateUncheckedStringValue(absl::string_view(buffer)))); + --limit; + string.remove_prefix(count); + } + if (!string.empty()) { + CEL_RETURN_IF_ERROR( + builder.Add(value_manager.CreateUncheckedStringValue(string))); + } + return std::move(builder).Build(); + } + + absl::StatusOr operator()(const absl::Cord& string) const { + auto begin = string.char_begin(); + auto end = string.char_end(); + char32_t rune; + size_t count; + std::string buffer; + while (begin != end && limit > 1) { + std::tie(rune, count) = internal::Utf8Decode(begin); + buffer.clear(); + internal::Utf8Encode(buffer, rune); + CEL_RETURN_IF_ERROR(builder.Add( + value_manager.CreateUncheckedStringValue(absl::string_view(buffer)))); + --limit; + absl::Cord::Advance(&begin, count); + } + if (begin != end) { + buffer.clear(); + while (begin != end) { + auto chunk = absl::Cord::ChunkRemaining(begin); + buffer.append(chunk); + absl::Cord::Advance(&begin, chunk.size()); + } + buffer.shrink_to_fit(); + CEL_RETURN_IF_ERROR(builder.Add( + value_manager.CreateUncheckedStringValue(std::move(buffer)))); + } + return std::move(builder).Build(); + } +}; + +absl::StatusOr Split3(ValueManager& value_manager, + const StringValue& string, + const StringValue& delimiter, int64_t limit) { + if (limit == 0) { + // Per spec, when limit is 0 return an empty list. + return ListValue{}; + } + if (limit < 0) { + // Per spec, when limit is negative treat is as unlimited. + limit = std::numeric_limits::max(); + } + CEL_ASSIGN_OR_RETURN(auto builder, + value_manager.NewListValueBuilder(ListTypeView{})); + if (string.IsEmpty()) { + // If string is empty, it doesn't matter what the delimiter is or the limit. + // We just return a list with a single empty string. + builder->Reserve(1); + CEL_RETURN_IF_ERROR(builder->Add(StringValue{})); + return std::move(*builder).Build(); + } + if (delimiter.IsEmpty()) { + // If the delimiter is empty, we split between every code point. + return string.NativeValue( + SplitWithEmptyDelimiter{value_manager, limit, *builder}); + } + // At this point we know the string is not empty and the delimiter is not + // empty. + std::string delimiter_scratch; + absl::string_view delimiter_view = delimiter.NativeString(delimiter_scratch); + std::string content_scratch; + absl::string_view content_view = string.NativeString(content_scratch); + while (limit > 1 && !content_view.empty()) { + auto pos = content_view.find(delimiter_view); + if (pos == absl::string_view::npos) { + break; + } + // We assume the original string was well-formed. + CEL_RETURN_IF_ERROR(builder->Add( + value_manager.CreateUncheckedStringValue(content_view.substr(0, pos)))); + --limit; + content_view.remove_prefix(pos + delimiter_view.size()); + if (content_view.empty()) { + // We found the delimiter at the end of the string. Add an empty string + // to the end of the list. + CEL_RETURN_IF_ERROR(builder->Add(StringValue{})); + return std::move(*builder).Build(); + } + } + // We have one left in the limit or do not have any more matches. Add + // whatever is left as the remaining entry. + // + // We assume the original string was well-formed. + CEL_RETURN_IF_ERROR( + builder->Add(value_manager.CreateUncheckedStringValue(content_view))); + return std::move(*builder).Build(); +} + +absl::StatusOr Split2(ValueManager& value_manager, + const StringValue& string, + const StringValue& delimiter) { + return Split3(value_manager, string, delimiter, -1); +} + +absl::StatusOr LowerAscii(ValueManager& value_manager, + const StringValue& string) { + std::string content = string.NativeString(); + absl::AsciiStrToLower(&content); + // We assume the original string was well-formed. + return value_manager.CreateUncheckedStringValue(std::move(content)); +} + +} // namespace + +absl::Status RegisterStringsFunctions(FunctionRegistry& registry, + const RuntimeOptions& options) { + CEL_RETURN_IF_ERROR(registry.Register( + UnaryFunctionAdapter, ListValue>::CreateDescriptor( + "join", /*receiver_style=*/true), + UnaryFunctionAdapter, ListValue>::WrapFunction( + Join1))); + CEL_RETURN_IF_ERROR(registry.Register( + BinaryFunctionAdapter, ListValue, StringValue>:: + CreateDescriptor("join", /*receiver_style=*/true), + BinaryFunctionAdapter, ListValue, + StringValue>::WrapFunction(Join2))); + CEL_RETURN_IF_ERROR(registry.Register( + BinaryFunctionAdapter, StringValue, StringValue>:: + CreateDescriptor("split", /*receiver_style=*/true), + BinaryFunctionAdapter, StringValue, + StringValue>::WrapFunction(Split2))); + CEL_RETURN_IF_ERROR(registry.Register( + VariadicFunctionAdapter< + absl::StatusOr, StringValue, StringValue, + int64_t>::CreateDescriptor("split", /*receiver_style=*/true), + VariadicFunctionAdapter, StringValue, StringValue, + int64_t>::WrapFunction(Split3))); + CEL_RETURN_IF_ERROR(registry.Register( + UnaryFunctionAdapter, StringValue>:: + CreateDescriptor("lowerAscii", /*receiver_style=*/true), + UnaryFunctionAdapter, StringValue>::WrapFunction( + LowerAscii))); + return absl::OkStatus(); +} + +absl::Status RegisterStringsFunctions( + google::api::expr::runtime::CelFunctionRegistry* registry, + const google::api::expr::runtime::InterpreterOptions& options) { + return RegisterStringsFunctions( + registry->InternalGetRegistry(), + google::api::expr::runtime::ConvertToRuntimeOptions(options)); +} + +} // namespace cel::extensions diff --git a/extensions/strings.h b/extensions/strings.h new file mode 100644 index 000000000..4db2ab4ab --- /dev/null +++ b/extensions/strings.h @@ -0,0 +1,36 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_CEL_CPP_EXTENSIONS_STRINGS_H_ +#define THIRD_PARTY_CEL_CPP_EXTENSIONS_STRINGS_H_ + +#include "absl/status/status.h" +#include "eval/public/cel_function_registry.h" +#include "eval/public/cel_options.h" +#include "runtime/function_registry.h" +#include "runtime/runtime_options.h" + +namespace cel::extensions { + +// Register extension functions for strings. +absl::Status RegisterStringsFunctions(FunctionRegistry& registry, + const RuntimeOptions& options); + +absl::Status RegisterStringsFunctions( + google::api::expr::runtime::CelFunctionRegistry* registry, + const google::api::expr::runtime::InterpreterOptions& options); + +} // namespace cel::extensions + +#endif // THIRD_PARTY_CEL_CPP_EXTENSIONS_STRINGS_H_ diff --git a/extensions/strings_test.cc b/extensions/strings_test.cc new file mode 100644 index 000000000..070c7a26d --- /dev/null +++ b/extensions/strings_test.cc @@ -0,0 +1,72 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "extensions/strings.h" + +#include +#include + +#include "google/api/expr/v1alpha1/syntax.pb.h" +#include "absl/strings/cord.h" +#include "common/memory.h" +#include "common/value.h" +#include "common/values/legacy_value_manager.h" +#include "extensions/protobuf/runtime_adapter.h" +#include "internal/testing.h" +#include "parser/options.h" +#include "parser/parser.h" +#include "runtime/activation.h" +#include "runtime/runtime.h" +#include "runtime/runtime_builder.h" +#include "runtime/runtime_options.h" +#include "runtime/standard_runtime_builder_factory.h" + +namespace cel::extensions { +namespace { + +using ::google::api::expr::v1alpha1::ParsedExpr; +using ::google::api::expr::parser::Parse; +using ::google::api::expr::parser::ParserOptions; + +TEST(Strings, SplitWithEmptyDelimiterCord) { + MemoryManagerRef memory_manager = MemoryManagerRef::ReferenceCounting(); + const auto options = RuntimeOptions{}; + ASSERT_OK_AND_ASSIGN(auto builder, CreateStandardRuntimeBuilder(options)); + EXPECT_OK(RegisterStringsFunctions(builder.function_registry(), options)); + + ASSERT_OK_AND_ASSIGN(auto runtime, std::move(builder).Build()); + + ASSERT_OK_AND_ASSIGN(ParsedExpr expr, + Parse("foo.split('') == ['h', 'e', 'l', 'l', 'o', ' ', " + "'w', 'o', 'r', 'l', 'd', '!']", + "", ParserOptions{})); + + ASSERT_OK_AND_ASSIGN(std::unique_ptr program, + ProtobufRuntimeAdapter::CreateProgram(*runtime, expr)); + + common_internal::LegacyValueManager value_factory(memory_manager, + runtime->GetTypeProvider()); + + Activation activation; + activation.InsertOrAssignValue("foo", + StringValue{absl::Cord("hello world!")}); + + ASSERT_OK_AND_ASSIGN(Value result, + program->Evaluate(activation, value_factory)); + ASSERT_TRUE(result.Is()); + EXPECT_TRUE(result.As().NativeValue()); +} + +} // namespace +} // namespace cel::extensions