diff --git a/bazel/deps.bzl b/bazel/deps.bzl index 56d6fe019..f327fa406 100644 --- a/bazel/deps.bzl +++ b/bazel/deps.bzl @@ -7,9 +7,9 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_jar") def base_deps(): """Base evaluator and test dependencies.""" - # Abseil LTS 20240116.2 - ABSL_SHA1 = "d7aaad83b488fd62bd51c81ecf16cd938532cc0a" - ABSL_SHA256 = "68e7d36d621769ab500b2ebeec6a7910420566874b4b33b340a04bd70e67fe43" + # Abseil LTS 20240722.0 + ABSL_SHA1 = "4447c7562e3bc702ade25105912dce503f0c4010" + ABSL_SHA256 = "d8342ad77aa9e16103c486b615460c24a695a1f04cdb760eb02fef780df99759" http_archive( name = "com_google_absl", urls = ["https://github.com/abseil/abseil-cpp/archive/" + ABSL_SHA1 + ".zip"], @@ -17,9 +17,9 @@ def base_deps(): sha256 = ABSL_SHA256, ) - # v1.14.0 - GOOGLETEST_SHA1 = "f8d7d77c06936315286eb55f8de22cd23c188571" - GOOGLETEST_SHA256 = "b976cf4fd57b318afdb1bdb27fc708904b3e4bed482859eb94ba2b4bdd077fe2" + # v1.15.2 + GOOGLETEST_SHA1 = "b514bdc898e2951020cbdca1304b75f5950d1f59" + GOOGLETEST_SHA256 = "8c0ceafa3ea24bf78e3519b7846d99e76c45899aa4dac4d64e7dd62e495de9fd" http_archive( name = "com_google_googletest", urls = ["https://github.com/google/googletest/archive/" + GOOGLETEST_SHA1 + ".zip"], diff --git a/common/BUILD b/common/BUILD index 0cc033ea4..13ff56040 100644 --- a/common/BUILD +++ b/common/BUILD @@ -904,3 +904,222 @@ cc_library( "@com_google_absl//absl/utility", ], ) + +cc_library( + name = "arena_string", + hdrs = ["arena_string.h"], + deps = [ + "@com_google_absl//absl/base", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/strings:string_view", + ], +) + +cc_test( + name = "arena_string_test", + srcs = ["arena_string_test.cc"], + deps = [ + ":arena_string", + "//internal:testing", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/strings:string_view", + ], +) + +cc_library( + name = "arena_string_pool", + hdrs = ["arena_string_pool.h"], + deps = [ + ":arena_string", + "//internal:string_pool", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/strings:string_view", + "@com_google_protobuf//:protobuf", + ], +) + +cc_test( + name = "arena_string_pool_test", + srcs = ["arena_string_pool_test.cc"], + deps = [ + ":arena_string_pool", + "//internal:testing", + "@com_google_protobuf//:protobuf", + ], +) + +cc_library( + name = "arena_bytes", + hdrs = ["arena_bytes.h"], + deps = [ + "@com_google_absl//absl/base", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/strings:string_view", + ], +) + +cc_test( + name = "arena_bytes_test", + srcs = ["arena_bytes_test.cc"], + deps = [ + ":arena_bytes", + "//internal:testing", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/strings:string_view", + ], +) + +cc_library( + name = "arena_bytes_pool", + hdrs = ["arena_bytes_pool.h"], + deps = [ + ":arena_bytes", + "//internal:string_pool", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/strings:string_view", + "@com_google_protobuf//:protobuf", + ], +) + +cc_test( + name = "arena_bytes_pool_test", + srcs = ["arena_bytes_pool_test.cc"], + deps = [ + ":arena_bytes_pool", + "//internal:testing", + "@com_google_protobuf//:protobuf", + ], +) + +cc_library( + name = "arena_constant", + srcs = ["arena_constant.cc"], + hdrs = ["arena_constant.h"], + deps = [ + ":arena_bytes", + ":arena_bytes_pool", + ":arena_string", + ":arena_string_pool", + ":constant", + "//internal:time", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/functional:overload", + "@com_google_absl//absl/log:absl_check", + "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:variant", + "@com_google_absl//absl/utility", + ], +) + +cc_test( + name = "arena_constant_test", + srcs = ["arena_constant_test.cc"], + deps = [ + ":arena_bytes", + ":arena_bytes_pool", + ":arena_constant", + ":arena_string", + ":arena_string_pool", + ":constant", + "//internal:testing", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:variant", + "@com_google_protobuf//:protobuf", + ], +) + +cc_library( + name = "arena_constant_proto", + srcs = ["arena_constant_proto.cc"], + hdrs = ["arena_constant_proto.h"], + deps = [ + ":arena_bytes", + ":arena_bytes_pool", + ":arena_constant", + ":arena_string", + ":arena_string_pool", + "//internal:proto_time_encoding", + "//internal:status_macros", + "//internal:time", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_cc_proto", + "@com_google_protobuf//:protobuf", + ], +) + +cc_test( + name = "arena_constant_proto_test", + srcs = ["arena_constant_proto_test.cc"], + deps = [ + ":arena_bytes", + ":arena_bytes_pool", + ":arena_constant", + ":arena_constant_proto", + ":arena_string", + ":arena_string_pool", + "//internal:proto_matchers", + "//internal:testing", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_cc_proto", + "@com_google_protobuf//:protobuf", + ], +) + +cc_library( + name = "arena_constant_proto_v1alpha1", + srcs = ["arena_constant_proto_v1alpha1.cc"], + hdrs = ["arena_constant_proto_v1alpha1.h"], + deps = [ + ":arena_bytes", + ":arena_bytes_pool", + ":arena_constant", + ":arena_string", + ":arena_string_pool", + "//internal:proto_time_encoding", + "//internal:status_macros", + "//internal:time", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "@com_google_googleapis//google/api/expr/v1alpha1:checked_cc_proto", + "@com_google_protobuf//:protobuf", + ], +) + +cc_test( + name = "arena_constant_proto_v1alpha1_test", + srcs = ["arena_constant_proto_v1alpha1_test.cc"], + deps = [ + ":arena_bytes", + ":arena_bytes_pool", + ":arena_constant", + ":arena_constant_proto_v1alpha1", + ":arena_string", + ":arena_string_pool", + "//internal:proto_matchers", + "//internal:testing", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + "@com_google_googleapis//google/api/expr/v1alpha1:checked_cc_proto", + "@com_google_protobuf//:protobuf", + ], +) diff --git a/common/arena_bytes.h b/common/arena_bytes.h new file mode 100644 index 000000000..585f4ba11 --- /dev/null +++ b/common/arena_bytes.h @@ -0,0 +1,251 @@ +// 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_COMMON_ARENA_BYTES_H_ +#define THIRD_PARTY_CEL_CPP_COMMON_ARENA_BYTES_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/base/attributes.h" +#include "absl/base/casts.h" +#include "absl/base/macros.h" +#include "absl/base/nullability.h" +#include "absl/strings/string_view.h" + +namespace cel { + +class ArenaBytesPool; + +// Bug in current Abseil LTS. Fixed in +// https://github.com/abseil/abseil-cpp/commit/fd7713cb9a97c49096211ff40de280b6cebbb21c +// which is not yet in an LTS. +#if defined(__clang__) && (!defined(__clang_major__) || __clang_major__ >= 13) +#define CEL_ATTRIBUTE_ARENA_BYTES_VIEW ABSL_ATTRIBUTE_VIEW +#else +#define CEL_ATTRIBUTE_ARENA_BYTES_VIEW +#endif + +class CEL_ATTRIBUTE_ARENA_BYTES_VIEW ArenaBytes final { + private: + template + static constexpr bool IsStringLiteral(const char (&string)[N]) { + static_assert(N > 0); + for (size_t i = 0; i < N - 1; ++i) { + if (string[i] == '\0') { + return false; + } + } + return string[N - 1] == '\0'; + } + + public: + using traits_type = std::char_traits; + using value_type = char; + using pointer = char*; + using const_pointer = const char*; + using reference = char&; + using const_reference = const char&; + using const_iterator = const_pointer; + using iterator = const_iterator; + using const_reverse_iterator = std::reverse_iterator; + using reverse_iterator = const_reverse_iterator; + using size_type = uint32_t; + using difference_type = int32_t; + + using absl_internal_is_view = std::true_type; + + template + static constexpr ArenaBytes Static(const char (&bytes)[N]) +#if ABSL_HAVE_ATTRIBUTE(enable_if) + __attribute__((enable_if(IsStringLiteral(bytes), + "chosen when 'bytes' is a string literal"))) +#endif + { + static_assert(N > 0); + static_assert(N - 1 <= std::numeric_limits::max()); + return ArenaBytes(bytes); + } + + ArenaBytes() = default; + ArenaBytes(const ArenaBytes&) = default; + ArenaBytes& operator=(const ArenaBytes&) = default; + + constexpr size_type size() const { return size_; } + + constexpr bool empty() const { return size() == 0; } + + constexpr size_type max_size() const { + return std::numeric_limits::max(); + } + + constexpr absl::Nonnull data() const { return data_; } + + constexpr const_reference front() const { + ABSL_ASSERT(!empty()); + return data()[0]; + } + + constexpr const_reference back() const { + ABSL_ASSERT(!empty()); + return data()[size() - 1]; + } + + constexpr const_reference operator[](size_type index) const { + ABSL_ASSERT(index < size()); + return data()[index]; + } + + constexpr void remove_prefix(size_type n) { + ABSL_ASSERT(n <= size()); + data_ += n; + size_ -= n; + } + + constexpr void remove_suffix(size_type n) { + ABSL_ASSERT(n <= size()); + size_ -= n; + } + + constexpr const_iterator begin() const { return data(); } + + constexpr const_iterator cbegin() const { return begin(); } + + constexpr const_iterator end() const { return data() + size(); } + + constexpr const_iterator cend() const { return end(); } + + constexpr const_reverse_iterator rbegin() const { + return std::make_reverse_iterator(end()); + } + + constexpr const_reverse_iterator crbegin() const { return rbegin(); } + + constexpr const_reverse_iterator rend() const { + return std::make_reverse_iterator(begin()); + } + + constexpr const_reverse_iterator crend() const { return rend(); } + + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr operator absl::string_view() const { + return absl::string_view(data(), size()); + } + + private: + friend class ArenaBytesPool; + + constexpr explicit ArenaBytes(absl::string_view value) + : data_(value.data()), size_(static_cast(value.size())) { + ABSL_ASSERT(value.data() != nullptr); + ABSL_ASSERT(value.size() <= max_size()); + } + + absl::Nonnull data_ = ""; + size_type size_ = 0; +}; + +constexpr bool operator==(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) == + absl::implicit_cast(rhs); +} + +constexpr bool operator==(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) == rhs; +} + +constexpr bool operator==(absl::string_view lhs, ArenaBytes rhs) { + return lhs == absl::implicit_cast(rhs); +} + +constexpr bool operator!=(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) != + absl::implicit_cast(rhs); +} + +constexpr bool operator!=(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) != rhs; +} + +constexpr bool operator!=(absl::string_view lhs, ArenaBytes rhs) { + return lhs != absl::implicit_cast(rhs); +} + +constexpr bool operator<(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) < + absl::implicit_cast(rhs); +} + +constexpr bool operator<(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) < rhs; +} + +constexpr bool operator<(absl::string_view lhs, ArenaBytes rhs) { + return lhs < absl::implicit_cast(rhs); +} + +constexpr bool operator<=(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) <= + absl::implicit_cast(rhs); +} + +constexpr bool operator<=(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) <= rhs; +} + +constexpr bool operator<=(absl::string_view lhs, ArenaBytes rhs) { + return lhs <= absl::implicit_cast(rhs); +} + +constexpr bool operator>(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) > + absl::implicit_cast(rhs); +} + +constexpr bool operator>(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) > rhs; +} + +constexpr bool operator>(absl::string_view lhs, ArenaBytes rhs) { + return lhs > absl::implicit_cast(rhs); +} + +constexpr bool operator>=(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) >= + absl::implicit_cast(rhs); +} + +constexpr bool operator>=(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) >= rhs; +} + +constexpr bool operator>=(absl::string_view lhs, ArenaBytes rhs) { + return lhs >= absl::implicit_cast(rhs); +} + +template +H AbslHashValue(H state, ArenaBytes arena_string) { + return H::combine(std::move(state), + absl::implicit_cast(arena_string)); +} + +#undef CEL_ATTRIBUTE_ARENA_BYTES_VIEW + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_COMMON_ARENA_BYTES_H_ diff --git a/common/arena_bytes_pool.h b/common/arena_bytes_pool.h new file mode 100644 index 000000000..fbea50c05 --- /dev/null +++ b/common/arena_bytes_pool.h @@ -0,0 +1,64 @@ +// 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_COMMON_ARENA_BYTES_POOL_H_ +#define THIRD_PARTY_CEL_CPP_COMMON_ARENA_BYTES_POOL_H_ + +#include + +#include "absl/base/attributes.h" +#include "absl/base/nullability.h" +#include "absl/strings/string_view.h" +#include "common/arena_bytes.h" +#include "internal/string_pool.h" +#include "google/protobuf/arena.h" + +namespace cel { + +class ArenaBytesPool; + +absl::Nonnull> NewArenaBytesPool( + absl::Nonnull arena ABSL_ATTRIBUTE_LIFETIME_BOUND); + +class ArenaBytesPool final { + public: + ArenaBytesPool(const ArenaBytesPool&) = delete; + ArenaBytesPool(ArenaBytesPool&&) = delete; + ArenaBytesPool& operator=(const ArenaBytesPool&) = delete; + ArenaBytesPool& operator=(ArenaBytesPool&&) = delete; + + ArenaBytes InternBytes(absl::string_view bytes) { + return ArenaBytes(strings_.InternString(bytes)); + } + + ArenaBytes InternBytes(ArenaBytes) = delete; + + private: + friend absl::Nonnull> NewArenaBytesPool( + absl::Nonnull); + + explicit ArenaBytesPool(absl::Nonnull arena) + : strings_(arena) {} + + internal::StringPool strings_; +}; + +inline absl::Nonnull> NewArenaBytesPool( + absl::Nonnull arena ABSL_ATTRIBUTE_LIFETIME_BOUND) { + return std::unique_ptr(new ArenaBytesPool(arena)); +} + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_COMMON_ARENA_BYTES_POOL_H_ diff --git a/common/arena_bytes_pool_test.cc b/common/arena_bytes_pool_test.cc new file mode 100644 index 000000000..85bb31d1a --- /dev/null +++ b/common/arena_bytes_pool_test.cc @@ -0,0 +1,32 @@ +// 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 "common/arena_bytes_pool.h" + +#include "internal/testing.h" +#include "google/protobuf/arena.h" + +namespace cel { +namespace { + +TEST(ArenaBytesPool, InternBytes) { + google::protobuf::Arena arena; + auto bytes_pool = NewArenaBytesPool(&arena); + auto expected = bytes_pool->InternBytes("Hello World!"); + auto got = bytes_pool->InternBytes("Hello World!"); + EXPECT_EQ(expected.data(), got.data()); +} + +} // namespace +} // namespace cel diff --git a/common/arena_bytes_test.cc b/common/arena_bytes_test.cc new file mode 100644 index 000000000..019b3ecb3 --- /dev/null +++ b/common/arena_bytes_test.cc @@ -0,0 +1,126 @@ +// 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 "common/arena_bytes.h" + +#include "absl/hash/hash.h" +#include "absl/hash/hash_testing.h" +#include "absl/strings/string_view.h" +#include "internal/testing.h" + +namespace cel { +namespace { + +using testing::Eq; +using testing::Ge; +using testing::Gt; +using testing::IsEmpty; +using testing::Le; +using testing::Lt; +using testing::Ne; +using testing::SizeIs; + +TEST(ArenaBytes, Default) { + ArenaBytes string; + EXPECT_THAT(string, IsEmpty()); + EXPECT_THAT(string, SizeIs(0)); + EXPECT_THAT(string, Eq(ArenaBytes())); +} + +TEST(ArenaBytes, Iterator) { + ArenaBytes string = ArenaBytes::Static("Hello World!"); + auto it = string.cbegin(); + EXPECT_THAT(*it++, Eq('H')); + EXPECT_THAT(*it++, Eq('e')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq(' ')); + EXPECT_THAT(*it++, Eq('W')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq('r')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('d')); + EXPECT_THAT(*it++, Eq('!')); + EXPECT_THAT(it, Eq(string.cend())); +} + +TEST(ArenaBytes, ReverseIterator) { + ArenaBytes string = ArenaBytes::Static("Hello World!"); + auto it = string.crbegin(); + EXPECT_THAT(*it++, Eq('!')); + EXPECT_THAT(*it++, Eq('d')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('r')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq('W')); + EXPECT_THAT(*it++, Eq(' ')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('e')); + EXPECT_THAT(*it++, Eq('H')); + EXPECT_THAT(it, Eq(string.crend())); +} + +TEST(ArenaBytes, RemovePrefix) { + ArenaBytes string = ArenaBytes::Static("Hello World!"); + string.remove_prefix(6); + EXPECT_EQ(string, "World!"); +} + +TEST(ArenaBytes, RemoveSuffix) { + ArenaBytes string = ArenaBytes::Static("Hello World!"); + string.remove_suffix(7); + EXPECT_EQ(string, "Hello"); +} + +TEST(ArenaBytes, Equal) { + EXPECT_THAT(ArenaBytes::Static("1"), Eq(ArenaBytes::Static("1"))); +} + +TEST(ArenaBytes, NotEqual) { + EXPECT_THAT(ArenaBytes::Static("1"), Ne(ArenaBytes::Static("2"))); +} + +TEST(ArenaBytes, Less) { + EXPECT_THAT(ArenaBytes::Static("1"), Lt(ArenaBytes::Static("2"))); +} + +TEST(ArenaBytes, LessEqual) { + EXPECT_THAT(ArenaBytes::Static("1"), Le(ArenaBytes::Static("1"))); +} + +TEST(ArenaBytes, Greater) { + EXPECT_THAT(ArenaBytes::Static("2"), Gt(ArenaBytes::Static("1"))); +} + +TEST(ArenaBytes, GreaterEqual) { + EXPECT_THAT(ArenaBytes::Static("1"), Ge(ArenaBytes::Static("1"))); +} + +TEST(ArenaBytes, ImplementsAbslHashCorrectly) { + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( + {ArenaBytes::Static(""), ArenaBytes::Static("Hello World!"), + ArenaBytes::Static("How much wood could a woodchuck chuck if a " + "woodchuck could chuck wood?")})); +} + +TEST(ArenaBytes, Hash) { + EXPECT_EQ(absl::HashOf(ArenaBytes::Static("Hello World!")), + absl::HashOf(absl::string_view("Hello World!"))); +} + +} // namespace +} // namespace cel diff --git a/common/arena_constant.cc b/common/arena_constant.cc new file mode 100644 index 000000000..6ea2b89df --- /dev/null +++ b/common/arena_constant.cc @@ -0,0 +1,79 @@ +// 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 "common/arena_constant.h" + +#include +#include + +#include "absl/base/nullability.h" +#include "absl/functional/overload.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "absl/types/variant.h" +#include "common/arena_bytes_pool.h" +#include "common/arena_string_pool.h" +#include "common/constant.h" + +namespace cel { + +absl::string_view ArenaConstantKindName(ArenaConstantKind constant_kind) { + switch (constant_kind) { + case ArenaConstantKind::kUnspecified: + return "UNSPECIFIED"; + case ArenaConstantKind::kNull: + return "NULL"; + case ArenaConstantKind::kBool: + return "BOOL"; + case ArenaConstantKind::kInt: + return "INT"; + case ArenaConstantKind::kUint: + return "UINT"; + case ArenaConstantKind::kDouble: + return "DOUBLE"; + case ArenaConstantKind::kBytes: + return "BYTES"; + case ArenaConstantKind::kString: + return "STRING"; + case ArenaConstantKind::kDuration: + return "DURATION"; + case ArenaConstantKind::kTimestamp: + return "TIMESTAMP"; + default: + return "ERROR"; + } +} + +ArenaConstant MakeArenaConstant(absl::Nonnull string_pool, + absl::Nonnull bytes_pool, + const Constant& constant) { + return absl::visit( + absl::Overload([](absl::monostate) { return ArenaConstant(); }, + [](std::nullptr_t value) { return ArenaConstant(value); }, + [](bool value) { return ArenaConstant(value); }, + [](int64_t value) { return ArenaConstant(value); }, + [](uint64_t value) { return ArenaConstant(value); }, + [](double value) { return ArenaConstant(value); }, + [&](const BytesConstant& value) { + return ArenaConstant(bytes_pool->InternBytes(value)); + }, + [&](const StringConstant& value) { + return ArenaConstant(string_pool->InternString(value)); + }, + [](absl::Duration value) { return ArenaConstant(value); }, + [](absl::Time value) { return ArenaConstant(value); }), + constant.kind()); +} + +} // namespace cel diff --git a/common/arena_constant.h b/common/arena_constant.h new file mode 100644 index 000000000..038a91b99 --- /dev/null +++ b/common/arena_constant.h @@ -0,0 +1,294 @@ +// 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_COMMON_ARENA_CONSTANT_H_ +#define THIRD_PARTY_CEL_CPP_COMMON_ARENA_CONSTANT_H_ + +#include +#include +#include + +#include "absl/base/attributes.h" +#include "absl/base/nullability.h" +#include "absl/log/absl_check.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "absl/types/optional.h" +#include "absl/types/variant.h" +#include "absl/utility/utility.h" +#include "common/arena_bytes.h" +#include "common/arena_string.h" +#include "common/constant.h" +#include "internal/time.h" + +namespace cel { + +enum class ArenaConstantKind { + kUnspecified = 0, + kNull, + kBool, + kInt, + kUint, + kDouble, + kBytes, + kString, + kDuration, + kTimestamp, +}; + +absl::string_view ArenaConstantKindName(ArenaConstantKind constant_kind); + +template +void AbslStringify(S& sink, ArenaConstantKind constant_kind) { + sink.Append(ArenaConstantKindName(constant_kind)); +} + +using ArenaConstantValue = + absl::variant; + +class ArenaConstant final { + public: + using Kind = ArenaConstantKind; + + ArenaConstant() = default; + ArenaConstant(const ArenaConstant&) = default; + ArenaConstant& operator=(const ArenaConstant&) = default; + + // NOLINTNEXTLINE(google-explicit-constructor) + ArenaConstant(std::nullptr_t value) + : value_(absl::in_place_type, value) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + ArenaConstant(bool value) : value_(absl::in_place_type, value) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + ArenaConstant(int64_t value) : value_(absl::in_place_type, value) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + ArenaConstant(uint64_t value) + : value_(absl::in_place_type, value) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + ArenaConstant(double value) : value_(absl::in_place_type, value) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + ArenaConstant(ArenaBytes value) + : value_(absl::in_place_type, value) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + ArenaConstant(ArenaString value) + : value_(absl::in_place_type, value) {} + + // NOLINTNEXTLINE(google-explicit-constructor) + ArenaConstant(absl::Duration value) + : value_(absl::in_place_type, value) { + ABSL_DCHECK_OK(cel::internal::ValidateDuration(value)); + } + + // NOLINTNEXTLINE(google-explicit-constructor) + ArenaConstant(absl::Time value) + : value_(absl::in_place_type, value) { + ABSL_DCHECK_OK(cel::internal::ValidateTimestamp(value)); + } + + Kind kind() const { return static_cast(value_.index()); } + + bool IsUnspecified() const { return kind() == Kind::kUnspecified; } + + bool IsNull() const { return kind() == Kind::kNull; } + + bool IsBool() const { return kind() == Kind::kBool; } + + bool IsInt() const { return kind() == Kind::kInt; } + + bool IsUint() const { return kind() == Kind::kUint; } + + bool IsDouble() const { return kind() == Kind::kDouble; } + + bool IsBytes() const { return kind() == Kind::kBytes; } + + bool IsString() const { return kind() == Kind::kString; } + + bool IsDuration() const { return kind() == Kind::kDuration; } + + bool IsTimestamp() const { return kind() == Kind::kTimestamp; } + + absl::optional AsUnspecified() const { + if (const auto* alternative = absl::get_if(&value()); + alternative != nullptr) { + return *alternative; + } + return absl::nullopt; + } + + absl::optional AsNull() const { + if (const auto* alternative = absl::get_if(&value()); + alternative != nullptr) { + return *alternative; + } + return absl::nullopt; + } + + absl::optional AsBool() const { + if (const auto* alternative = absl::get_if(&value()); + alternative != nullptr) { + return *alternative; + } + return absl::nullopt; + } + + absl::optional AsInt() const { + if (const auto* alternative = absl::get_if(&value()); + alternative != nullptr) { + return *alternative; + } + return absl::nullopt; + } + + absl::optional AsUint() const { + if (const auto* alternative = absl::get_if(&value()); + alternative != nullptr) { + return *alternative; + } + return absl::nullopt; + } + + absl::optional AsDouble() const { + if (const auto* alternative = absl::get_if(&value()); + alternative != nullptr) { + return *alternative; + } + return absl::nullopt; + } + + absl::optional AsBytes() const { + if (const auto* alternative = absl::get_if(&value()); + alternative != nullptr) { + return *alternative; + } + return absl::nullopt; + } + + absl::optional AsString() const { + if (const auto* alternative = absl::get_if(&value()); + alternative != nullptr) { + return *alternative; + } + return absl::nullopt; + } + + absl::optional AsDuration() const { + if (const auto* alternative = absl::get_if(&value()); + alternative != nullptr) { + return *alternative; + } + return absl::nullopt; + } + + absl::optional AsTimestamp() const { + if (const auto* alternative = absl::get_if(&value()); + alternative != nullptr) { + return *alternative; + } + return absl::nullopt; + } + + explicit operator absl::monostate() const { + ABSL_DCHECK(IsUnspecified()); + return absl::get(value()); + } + + explicit operator std::nullptr_t() const { + ABSL_DCHECK(IsNull()); + return absl::get(value()); + } + + explicit operator bool() const { + ABSL_DCHECK(IsBool()); + return absl::get(value()); + } + + explicit operator int64_t() const { + ABSL_DCHECK(IsInt()); + return absl::get(value()); + } + + explicit operator uint64_t() const { + ABSL_DCHECK(IsUint()); + return absl::get(value()); + } + + explicit operator double() const { + ABSL_DCHECK(IsDouble()); + return absl::get(value()); + } + + explicit operator ArenaBytes() const { + ABSL_DCHECK(IsBytes()); + return absl::get(value()); + } + + explicit operator ArenaString() const { + ABSL_DCHECK(IsString()); + return absl::get(value()); + } + + explicit operator absl::Duration() const { + ABSL_DCHECK(IsDuration()); + return absl::get(value()); + } + + explicit operator absl::Time() const { + ABSL_DCHECK(IsTimestamp()); + return absl::get(value()); + } + + friend bool operator==(const ArenaConstant& lhs, const ArenaConstant& rhs) { + return lhs.value() == rhs.value(); + } + + template + friend H AbslHashValue(H state, const ArenaConstant& constant) { + return H::combine(std::move(state), constant.value()); + } + + private: + // Variant holding the content. We do not expose it directly because once we + // do we cannot easily add additional types without breaking source + // compatibility. `std::visit` requires being exhaustive, but definition + // adding another type would break existing code. + using Value = absl::variant; + + const Value& value() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return value_; } + + Value& value() ABSL_ATTRIBUTE_LIFETIME_BOUND { return value_; } + + Value value_; +}; + +inline bool operator!=(const ArenaConstant& lhs, const ArenaConstant& rhs) { + return !operator==(lhs, rhs); +} + +ArenaConstant MakeArenaConstant( + absl::Nonnull string_pool ABSL_ATTRIBUTE_LIFETIME_BOUND, + absl::Nonnull bytes_pool ABSL_ATTRIBUTE_LIFETIME_BOUND, + const Constant& constant); + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_COMMON_ARENA_CONSTANT_H_ diff --git a/common/arena_constant_proto.cc b/common/arena_constant_proto.cc new file mode 100644 index 000000000..f388f44a7 --- /dev/null +++ b/common/arena_constant_proto.cc @@ -0,0 +1,116 @@ +// 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 "common/arena_constant_proto.h" + +#include + +#include "google/api/expr/v1alpha1/syntax.pb.h" +#include "google/protobuf/struct.pb.h" +#include "absl/base/nullability.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/time/time.h" +#include "common/arena_bytes.h" +#include "common/arena_bytes_pool.h" +#include "common/arena_constant.h" +#include "common/arena_string.h" +#include "common/arena_string_pool.h" +#include "internal/proto_time_encoding.h" +#include "internal/status_macros.h" +#include "internal/time.h" + +namespace cel { + +using ConstantProto = ::google::api::expr::v1alpha1::Constant; + +absl::StatusOr ArenaConstantFromProto( + absl::Nonnull string_pool, + absl::Nonnull bytes_pool, const ConstantProto& proto) { + switch (proto.constant_kind_case()) { + case ConstantProto::CONSTANT_KIND_NOT_SET: + return ArenaConstant(); + case ConstantProto::kNullValue: + return nullptr; + case ConstantProto::kBoolValue: + return proto.bool_value(); + case ConstantProto::kInt64Value: + return static_cast(proto.int64_value()); + case ConstantProto::kUint64Value: + return static_cast(proto.uint64_value()); + case ConstantProto::kDoubleValue: + return static_cast(proto.double_value()); + case ConstantProto::kBytesValue: + return bytes_pool->InternBytes(proto.bytes_value()); + case ConstantProto::kStringValue: + return string_pool->InternString(proto.string_value()); + case ConstantProto::kDurationValue: { + auto value = cel::internal::DecodeDuration(proto.duration_value()); + CEL_RETURN_IF_ERROR(cel::internal::ValidateDuration(value)); + return value; + } + case ConstantProto::kTimestampValue: { + auto value = cel::internal::DecodeTime(proto.timestamp_value()); + CEL_RETURN_IF_ERROR(cel::internal::ValidateTimestamp(value)); + return value; + } + default: + return absl::DataLossError(absl::StrCat("unexpected constant kind case: ", + proto.constant_kind_case())); + } +} + +absl::Status ArenaConstantToProto(const ArenaConstant& constant, + absl::Nonnull proto) { + switch (constant.kind()) { + case ArenaConstantKind::kUnspecified: + proto->constant_kind_case(); + break; + case ArenaConstantKind::kNull: + proto->set_null_value(google::protobuf::NULL_VALUE); + break; + case ArenaConstantKind::kBool: + proto->set_bool_value(static_cast(constant)); + break; + case ArenaConstantKind::kInt: + proto->set_int64_value(static_cast(constant)); + break; + case ArenaConstantKind::kUint: + proto->set_uint64_value(static_cast(constant)); + break; + case ArenaConstantKind::kDouble: + proto->set_double_value(static_cast(constant)); + break; + case ArenaConstantKind::kBytes: + proto->set_bytes_value(static_cast(constant)); + break; + case ArenaConstantKind::kString: + proto->set_string_value(static_cast(constant)); + break; + case ArenaConstantKind::kDuration: + return cel::internal::EncodeDuration( + static_cast(constant), + proto->mutable_duration_value()); + case ArenaConstantKind::kTimestamp: + return cel::internal::EncodeTime(static_cast(constant), + proto->mutable_timestamp_value()); + default: + return absl::DataLossError( + absl::StrCat("unexpected constant kind: ", constant.kind())); + } + return absl::OkStatus(); +} + +} // namespace cel diff --git a/common/arena_constant_proto.h b/common/arena_constant_proto.h new file mode 100644 index 000000000..a22a63a99 --- /dev/null +++ b/common/arena_constant_proto.h @@ -0,0 +1,39 @@ +// 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_COMMON_ARENA_CONSTANT_PROTO_H_ +#define THIRD_PARTY_CEL_CPP_COMMON_ARENA_CONSTANT_PROTO_H_ + +#include "google/api/expr/v1alpha1/syntax.pb.h" +#include "absl/base/nullability.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "common/arena_bytes_pool.h" +#include "common/arena_constant.h" +#include "common/arena_string_pool.h" + +namespace cel { + +absl::StatusOr ArenaConstantFromProto( + absl::Nonnull string_pool, + absl::Nonnull bytes_pool, + const google::api::expr::v1alpha1::Constant& proto); + +absl::Status ArenaConstantToProto( + const ArenaConstant& constant, + absl::Nonnull proto); + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_COMMON_ARENA_CONSTANT_PROTO_H_ diff --git a/common/arena_constant_proto_test.cc b/common/arena_constant_proto_test.cc new file mode 100644 index 000000000..870091449 --- /dev/null +++ b/common/arena_constant_proto_test.cc @@ -0,0 +1,200 @@ +// 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 "common/arena_constant_proto.h" + +#include +#include + +#include "google/api/expr/v1alpha1/syntax.pb.h" +#include "absl/base/nullability.h" +#include "absl/time/time.h" +#include "absl/types/optional.h" +#include "common/arena_bytes.h" +#include "common/arena_bytes_pool.h" +#include "common/arena_constant.h" +#include "common/arena_string.h" +#include "common/arena_string_pool.h" +#include "internal/proto_matchers.h" +#include "internal/testing.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/text_format.h" + +namespace cel { +namespace { + +using ::cel::internal::test::EqualsProto; +using testing::Eq; +using testing::Test; + +using ConstantProto = ::google::api::expr::v1alpha1::Constant; + +class ArenaConstantProtoTest : public Test { + public: + void SetUp() override { + arena_.emplace(); + string_pool_ = NewArenaStringPool(arena()); + bytes_pool_ = NewArenaBytesPool(arena()); + } + + void TearDown() override { + bytes_pool_.reset(); + string_pool_.reset(); + arena_.reset(); + } + + absl::Nonnull arena() { return &*arena_; } + + absl::Nonnull string_pool() { return string_pool_.get(); } + + absl::Nonnull bytes_pool() { return bytes_pool_.get(); } + + private: + absl::optional arena_; + std::unique_ptr string_pool_; + std::unique_ptr bytes_pool_; +}; + +TEST_F(ArenaConstantProtoTest, Unspecified) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb()pb", &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, + ArenaConstantFromProto(string_pool(), bytes_pool(), expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant())); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProto(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoTest, Null) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(null_value: NULL_VALUE)pb", &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, + ArenaConstantFromProto(string_pool(), bytes_pool(), expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(nullptr))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProto(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoTest, Bool) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(bool_value: true)pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, + ArenaConstantFromProto(string_pool(), bytes_pool(), expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(true))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProto(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoTest, Int) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(int64_value: 1)pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, + ArenaConstantFromProto(string_pool(), bytes_pool(), expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(int64_t{1}))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProto(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoTest, Uint) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(uint64_value: 1)pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, + ArenaConstantFromProto(string_pool(), bytes_pool(), expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(uint64_t{1}))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProto(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoTest, Double) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(double_value: 1.0)pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, + ArenaConstantFromProto(string_pool(), bytes_pool(), expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(1.0))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProto(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoTest, Bytes) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(bytes_value: "foo")pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, + ArenaConstantFromProto(string_pool(), bytes_pool(), expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(ArenaBytes::Static("foo")))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProto(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoTest, String) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(string_value: "foo")pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, + ArenaConstantFromProto(string_pool(), bytes_pool(), expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(ArenaString::Static("foo")))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProto(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoTest, Duration) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(duration_value: { seconds: 1 nanos: 1 })pb", &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, + ArenaConstantFromProto(string_pool(), bytes_pool(), expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(absl::Seconds(1) + absl::Nanoseconds(1)))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProto(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoTest, Timestamp) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(timestamp_value: { seconds: 1 nanos: 1 })pb", &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, + ArenaConstantFromProto(string_pool(), bytes_pool(), expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(absl::UnixEpoch() + absl::Seconds(1) + + absl::Nanoseconds(1)))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProto(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +} // namespace +} // namespace cel diff --git a/common/arena_constant_proto_v1alpha1.cc b/common/arena_constant_proto_v1alpha1.cc new file mode 100644 index 000000000..71d41463c --- /dev/null +++ b/common/arena_constant_proto_v1alpha1.cc @@ -0,0 +1,116 @@ +// 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 "common/arena_constant_proto_v1alpha1.h" + +#include + +#include "google/api/expr/v1alpha1/syntax.pb.h" +#include "google/protobuf/struct.pb.h" +#include "absl/base/nullability.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/time/time.h" +#include "common/arena_bytes.h" +#include "common/arena_bytes_pool.h" +#include "common/arena_constant.h" +#include "common/arena_string.h" +#include "common/arena_string_pool.h" +#include "internal/proto_time_encoding.h" +#include "internal/status_macros.h" +#include "internal/time.h" + +namespace cel { + +using ConstantProto = ::google::api::expr::v1alpha1::Constant; + +absl::StatusOr ArenaConstantFromProtoV1Alpha1( + absl::Nonnull string_pool, + absl::Nonnull bytes_pool, const ConstantProto& proto) { + switch (proto.constant_kind_case()) { + case ConstantProto::CONSTANT_KIND_NOT_SET: + return ArenaConstant(); + case ConstantProto::kNullValue: + return nullptr; + case ConstantProto::kBoolValue: + return proto.bool_value(); + case ConstantProto::kInt64Value: + return static_cast(proto.int64_value()); + case ConstantProto::kUint64Value: + return static_cast(proto.uint64_value()); + case ConstantProto::kDoubleValue: + return static_cast(proto.double_value()); + case ConstantProto::kBytesValue: + return bytes_pool->InternBytes(proto.bytes_value()); + case ConstantProto::kStringValue: + return string_pool->InternString(proto.string_value()); + case ConstantProto::kDurationValue: { + auto value = cel::internal::DecodeDuration(proto.duration_value()); + CEL_RETURN_IF_ERROR(cel::internal::ValidateDuration(value)); + return value; + } + case ConstantProto::kTimestampValue: { + auto value = cel::internal::DecodeTime(proto.timestamp_value()); + CEL_RETURN_IF_ERROR(cel::internal::ValidateTimestamp(value)); + return value; + } + default: + return absl::DataLossError(absl::StrCat("unexpected constant kind case: ", + proto.constant_kind_case())); + } +} + +absl::Status ArenaConstantToProtoV1Alpha1(const ArenaConstant& constant, + absl::Nonnull proto) { + switch (constant.kind()) { + case ArenaConstantKind::kUnspecified: + proto->constant_kind_case(); + break; + case ArenaConstantKind::kNull: + proto->set_null_value(google::protobuf::NULL_VALUE); + break; + case ArenaConstantKind::kBool: + proto->set_bool_value(static_cast(constant)); + break; + case ArenaConstantKind::kInt: + proto->set_int64_value(static_cast(constant)); + break; + case ArenaConstantKind::kUint: + proto->set_uint64_value(static_cast(constant)); + break; + case ArenaConstantKind::kDouble: + proto->set_double_value(static_cast(constant)); + break; + case ArenaConstantKind::kBytes: + proto->set_bytes_value(static_cast(constant)); + break; + case ArenaConstantKind::kString: + proto->set_string_value(static_cast(constant)); + break; + case ArenaConstantKind::kDuration: + return cel::internal::EncodeDuration( + static_cast(constant), + proto->mutable_duration_value()); + case ArenaConstantKind::kTimestamp: + return cel::internal::EncodeTime(static_cast(constant), + proto->mutable_timestamp_value()); + default: + return absl::DataLossError( + absl::StrCat("unexpected constant kind: ", constant.kind())); + } + return absl::OkStatus(); +} + +} // namespace cel diff --git a/common/arena_constant_proto_v1alpha1.h b/common/arena_constant_proto_v1alpha1.h new file mode 100644 index 000000000..c5ba09b1a --- /dev/null +++ b/common/arena_constant_proto_v1alpha1.h @@ -0,0 +1,39 @@ +// 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_COMMON_ARENA_CONSTANT_PROTO_V1ALPHA1_H_ +#define THIRD_PARTY_CEL_CPP_COMMON_ARENA_CONSTANT_PROTO_V1ALPHA1_H_ + +#include "google/api/expr/v1alpha1/syntax.pb.h" +#include "absl/base/nullability.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "common/arena_bytes_pool.h" +#include "common/arena_constant.h" +#include "common/arena_string_pool.h" + +namespace cel { + +absl::StatusOr ArenaConstantFromProtoV1Alpha1( + absl::Nonnull string_pool, + absl::Nonnull bytes_pool, + const google::api::expr::v1alpha1::Constant& proto); + +absl::Status ArenaConstantToProtoV1Alpha1( + const ArenaConstant& constant, + absl::Nonnull proto); + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_COMMON_ARENA_CONSTANT_PROTO_V1ALPHA1_H_ diff --git a/common/arena_constant_proto_v1alpha1_test.cc b/common/arena_constant_proto_v1alpha1_test.cc new file mode 100644 index 000000000..815bc5767 --- /dev/null +++ b/common/arena_constant_proto_v1alpha1_test.cc @@ -0,0 +1,200 @@ +// 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 "common/arena_constant_proto_v1alpha1.h" + +#include +#include + +#include "google/api/expr/v1alpha1/syntax.pb.h" +#include "absl/base/nullability.h" +#include "absl/time/time.h" +#include "absl/types/optional.h" +#include "common/arena_bytes.h" +#include "common/arena_bytes_pool.h" +#include "common/arena_constant.h" +#include "common/arena_string.h" +#include "common/arena_string_pool.h" +#include "internal/proto_matchers.h" +#include "internal/testing.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/text_format.h" + +namespace cel { +namespace { + +using ::cel::internal::test::EqualsProto; +using testing::Eq; +using testing::Test; + +using ConstantProto = ::google::api::expr::v1alpha1::Constant; + +class ArenaConstantProtoV1Alpha1Test : public Test { + public: + void SetUp() override { + arena_.emplace(); + string_pool_ = NewArenaStringPool(arena()); + bytes_pool_ = NewArenaBytesPool(arena()); + } + + void TearDown() override { + bytes_pool_.reset(); + string_pool_.reset(); + arena_.reset(); + } + + absl::Nonnull arena() { return &*arena_; } + + absl::Nonnull string_pool() { return string_pool_.get(); } + + absl::Nonnull bytes_pool() { return bytes_pool_.get(); } + + private: + absl::optional arena_; + std::unique_ptr string_pool_; + std::unique_ptr bytes_pool_; +}; + +TEST_F(ArenaConstantProtoV1Alpha1Test, Unspecified) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb()pb", &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, ArenaConstantFromProtoV1Alpha1(string_pool(), bytes_pool(), + expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant())); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProtoV1Alpha1(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoV1Alpha1Test, Null) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(null_value: NULL_VALUE)pb", &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, ArenaConstantFromProtoV1Alpha1(string_pool(), bytes_pool(), + expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(nullptr))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProtoV1Alpha1(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoV1Alpha1Test, Bool) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(bool_value: true)pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, ArenaConstantFromProtoV1Alpha1(string_pool(), bytes_pool(), + expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(true))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProtoV1Alpha1(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoV1Alpha1Test, Int) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(int64_value: 1)pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, ArenaConstantFromProtoV1Alpha1(string_pool(), bytes_pool(), + expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(int64_t{1}))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProtoV1Alpha1(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoV1Alpha1Test, Uint) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(uint64_value: 1)pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, ArenaConstantFromProtoV1Alpha1(string_pool(), bytes_pool(), + expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(uint64_t{1}))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProtoV1Alpha1(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoV1Alpha1Test, Double) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(double_value: 1.0)pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, ArenaConstantFromProtoV1Alpha1(string_pool(), bytes_pool(), + expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(1.0))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProtoV1Alpha1(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoV1Alpha1Test, Bytes) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(bytes_value: "foo")pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, ArenaConstantFromProtoV1Alpha1(string_pool(), bytes_pool(), + expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(ArenaBytes::Static("foo")))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProtoV1Alpha1(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoV1Alpha1Test, String) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(string_value: "foo")pb", + &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, ArenaConstantFromProtoV1Alpha1(string_pool(), bytes_pool(), + expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(ArenaString::Static("foo")))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProtoV1Alpha1(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoV1Alpha1Test, Duration) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(duration_value: { seconds: 1 nanos: 1 })pb", &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, ArenaConstantFromProtoV1Alpha1(string_pool(), bytes_pool(), + expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(absl::Seconds(1) + absl::Nanoseconds(1)))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProtoV1Alpha1(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +TEST_F(ArenaConstantProtoV1Alpha1Test, Timestamp) { + ConstantProto expected_proto; + ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString( + R"pb(timestamp_value: { seconds: 1 nanos: 1 })pb", &expected_proto)); + ASSERT_OK_AND_ASSIGN( + auto got, ArenaConstantFromProtoV1Alpha1(string_pool(), bytes_pool(), + expected_proto)); + EXPECT_THAT(got, Eq(ArenaConstant(absl::UnixEpoch() + absl::Seconds(1) + + absl::Nanoseconds(1)))); + ConstantProto got_proto; + EXPECT_OK(ArenaConstantToProtoV1Alpha1(got, &got_proto)); + EXPECT_THAT(got_proto, EqualsProto(expected_proto)); +} + +} // namespace +} // namespace cel diff --git a/common/arena_constant_test.cc b/common/arena_constant_test.cc new file mode 100644 index 000000000..2553062e6 --- /dev/null +++ b/common/arena_constant_test.cc @@ -0,0 +1,191 @@ +// 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 "common/arena_constant.h" + +#include +#include + +#include "absl/hash/hash_testing.h" +#include "absl/time/time.h" +#include "absl/types/variant.h" +#include "common/arena_bytes.h" +#include "common/arena_bytes_pool.h" +#include "common/arena_string.h" +#include "common/arena_string_pool.h" +#include "common/constant.h" +#include "internal/testing.h" +#include "google/protobuf/arena.h" + +namespace cel { +namespace { + +using testing::An; +using testing::Eq; +using testing::Ne; +using testing::Optional; +using testing::Test; + +TEST(ArenaConstantKind, Name) { + EXPECT_EQ(ArenaConstantKindName(ArenaConstantKind::kUnspecified), + "UNSPECIFIED"); + EXPECT_EQ(ArenaConstantKindName(ArenaConstantKind::kNull), "NULL"); + EXPECT_EQ(ArenaConstantKindName(ArenaConstantKind::kBool), "BOOL"); + EXPECT_EQ(ArenaConstantKindName(ArenaConstantKind::kInt), "INT"); + EXPECT_EQ(ArenaConstantKindName(ArenaConstantKind::kUint), "UINT"); + EXPECT_EQ(ArenaConstantKindName(ArenaConstantKind::kDouble), "DOUBLE"); + EXPECT_EQ(ArenaConstantKindName(ArenaConstantKind::kBytes), "BYTES"); + EXPECT_EQ(ArenaConstantKindName(ArenaConstantKind::kString), "STRING"); + EXPECT_EQ(ArenaConstantKindName(ArenaConstantKind::kDuration), "DURATION"); + EXPECT_EQ(ArenaConstantKindName(ArenaConstantKind::kTimestamp), "TIMESTAMP"); + EXPECT_EQ(ArenaConstantKindName(static_cast(42)), "ERROR"); +} + +TEST(ArenaConstant, Unspecified) { + EXPECT_THAT(ArenaConstant(), Eq(ArenaConstant())); + EXPECT_THAT(ArenaConstant(), Ne(ArenaConstant(true))); + EXPECT_TRUE(ArenaConstant().IsUnspecified()); + EXPECT_THAT(ArenaConstant().AsUnspecified(), Optional(An())); + EXPECT_THAT(static_cast(ArenaConstant()), + An()); +} + +TEST(ArenaConstant, Null) { + EXPECT_THAT(ArenaConstant(nullptr), Eq(ArenaConstant(nullptr))); + EXPECT_THAT(ArenaConstant(nullptr), Ne(ArenaConstant())); + EXPECT_TRUE(ArenaConstant(nullptr).IsNull()); + EXPECT_THAT(ArenaConstant(nullptr).AsNull(), Optional(An())); + EXPECT_THAT(static_cast(ArenaConstant(nullptr)), + An()); +} + +TEST(ArenaConstant, Bool) { + EXPECT_THAT(ArenaConstant(true), Eq(ArenaConstant(true))); + EXPECT_THAT(ArenaConstant(true), Ne(ArenaConstant(false))); + EXPECT_TRUE(ArenaConstant(true).IsBool()); + EXPECT_THAT(ArenaConstant(true).AsBool(), Optional(Eq(true))); + EXPECT_THAT(static_cast(ArenaConstant(true)), Eq(true)); +} + +TEST(ArenaConstant, Int) { + EXPECT_THAT(ArenaConstant(int64_t{1}), Eq(ArenaConstant(int64_t{1}))); + EXPECT_THAT(ArenaConstant(int64_t{1}), Ne(ArenaConstant(int64_t{0}))); + EXPECT_TRUE(ArenaConstant(int64_t{1}).IsInt()); + EXPECT_THAT(ArenaConstant(int64_t{1}).AsInt(), Optional(Eq(int64_t{1}))); + EXPECT_THAT(static_cast(ArenaConstant(int64_t{1})), Eq(int64_t{1})); +} + +TEST(ArenaConstant, Uint) { + EXPECT_THAT(ArenaConstant(uint64_t{1}), Eq(ArenaConstant(uint64_t{1}))); + EXPECT_THAT(ArenaConstant(uint64_t{1}), Ne(ArenaConstant(uint64_t{0}))); + EXPECT_TRUE(ArenaConstant(uint64_t{1}).IsUint()); + EXPECT_THAT(ArenaConstant(uint64_t{1}).AsUint(), Optional(Eq(uint64_t{1}))); + EXPECT_THAT(static_cast(ArenaConstant(uint64_t{1})), + Eq(uint64_t{1})); +} + +TEST(ArenaConstant, Double) { + EXPECT_THAT(ArenaConstant(1.0), Eq(ArenaConstant(1.0))); + EXPECT_THAT(ArenaConstant(1.0), Ne(ArenaConstant(0.0))); + EXPECT_TRUE(ArenaConstant(1.0).IsDouble()); + EXPECT_THAT(ArenaConstant(1.0).AsDouble(), Optional(Eq(1.0))); + EXPECT_THAT(static_cast(ArenaConstant(1.0)), Eq(1.0)); +} + +TEST(ArenaConstant, Bytes) { + auto bytes = ArenaBytes::Static("foo"); + EXPECT_THAT(ArenaConstant(bytes), Eq(ArenaConstant(bytes))); + EXPECT_THAT(ArenaConstant(bytes), Ne(ArenaConstant(ArenaBytes::Static("")))); + EXPECT_TRUE(ArenaConstant(bytes).IsBytes()); + EXPECT_THAT(ArenaConstant(bytes).AsBytes(), Optional(Eq(bytes))); + EXPECT_THAT(static_cast(ArenaConstant(bytes)), Eq(bytes)); +} + +TEST(ArenaConstant, String) { + auto string = ArenaString::Static("foo"); + EXPECT_THAT(ArenaConstant(string), Eq(ArenaConstant(string))); + EXPECT_THAT(ArenaConstant(string), + Ne(ArenaConstant(ArenaString::Static("")))); + EXPECT_TRUE(ArenaConstant(string).IsString()); + EXPECT_THAT(ArenaConstant(string).AsString(), Optional(Eq(string))); + EXPECT_THAT(static_cast(ArenaConstant(string)), Eq(string)); +} + +TEST(ArenaConstant, Duration) { + auto duration = absl::Seconds(1); + EXPECT_THAT(ArenaConstant(duration), Eq(ArenaConstant(duration))); + EXPECT_THAT(ArenaConstant(duration), Ne(ArenaConstant(absl::ZeroDuration()))); + EXPECT_TRUE(ArenaConstant(duration).IsDuration()); + EXPECT_THAT(ArenaConstant(duration).AsDuration(), Optional(Eq(duration))); + EXPECT_THAT(static_cast(ArenaConstant(duration)), + Eq(duration)); +} + +TEST(ArenaConstant, Timestamp) { + auto timestamp = absl::UnixEpoch() + absl::Seconds(1); + EXPECT_THAT(ArenaConstant(timestamp), Eq(ArenaConstant(timestamp))); + EXPECT_THAT(ArenaConstant(timestamp), Ne(ArenaConstant(absl::UnixEpoch()))); + EXPECT_TRUE(ArenaConstant(timestamp).IsTimestamp()); + EXPECT_THAT(ArenaConstant(timestamp).AsTimestamp(), Optional(Eq(timestamp))); + EXPECT_THAT(static_cast(ArenaConstant(timestamp)), Eq(timestamp)); +} + +TEST(ArenaConstant, ImplementsAbslHashCorrectly) { + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( + {ArenaConstant(), ArenaConstant(nullptr), ArenaConstant(true), + ArenaConstant(int64_t{1}), ArenaConstant(uint64_t{1}), + ArenaConstant(1.0), ArenaConstant(ArenaBytes::Static("foo")), + ArenaConstant(ArenaString::Static("foo")), + ArenaConstant(absl::Seconds(1)), + ArenaConstant(absl::UnixEpoch() + absl::Seconds(1))})); +} + +TEST(ArenaConstant, Make) { + google::protobuf::Arena arena; + auto string_pool = NewArenaStringPool(&arena); + auto bytes_pool = NewArenaBytesPool(&arena); + EXPECT_THAT( + MakeArenaConstant(string_pool.get(), bytes_pool.get(), Constant()), + Eq(ArenaConstant())); + EXPECT_THAT( + MakeArenaConstant(string_pool.get(), bytes_pool.get(), Constant(nullptr)), + Eq(ArenaConstant(nullptr))); + EXPECT_THAT( + MakeArenaConstant(string_pool.get(), bytes_pool.get(), Constant(true)), + Eq(ArenaConstant(true))); + EXPECT_THAT(MakeArenaConstant(string_pool.get(), bytes_pool.get(), + Constant(int64_t{1})), + Eq(ArenaConstant(int64_t{1}))); + EXPECT_THAT(MakeArenaConstant(string_pool.get(), bytes_pool.get(), + Constant(uint64_t{1})), + Eq(ArenaConstant(uint64_t{1}))); + EXPECT_THAT( + MakeArenaConstant(string_pool.get(), bytes_pool.get(), Constant(1.0)), + Eq(ArenaConstant(1.0))); + EXPECT_THAT(MakeArenaConstant(string_pool.get(), bytes_pool.get(), + Constant(BytesConstant("foo"))), + Eq(ArenaConstant(ArenaBytes::Static("foo")))); + EXPECT_THAT(MakeArenaConstant(string_pool.get(), bytes_pool.get(), + Constant(StringConstant("foo"))), + Eq(ArenaConstant(ArenaString::Static("foo")))); + EXPECT_THAT(MakeArenaConstant(string_pool.get(), bytes_pool.get(), + Constant(absl::Seconds(1))), + Eq(ArenaConstant(absl::Seconds(1)))); + EXPECT_THAT(MakeArenaConstant(string_pool.get(), bytes_pool.get(), + Constant(absl::UnixEpoch() + absl::Seconds(1))), + Eq(ArenaConstant(absl::UnixEpoch() + absl::Seconds(1)))); +} + +} // namespace +} // namespace cel diff --git a/common/arena_string.h b/common/arena_string.h new file mode 100644 index 000000000..809fe010c --- /dev/null +++ b/common/arena_string.h @@ -0,0 +1,251 @@ +// 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_COMMON_ARENA_STRING_H_ +#define THIRD_PARTY_CEL_CPP_COMMON_ARENA_STRING_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/base/attributes.h" +#include "absl/base/casts.h" +#include "absl/base/macros.h" +#include "absl/base/nullability.h" +#include "absl/strings/string_view.h" + +namespace cel { + +class ArenaStringPool; + +// Bug in current Abseil LTS. Fixed in +// https://github.com/abseil/abseil-cpp/commit/fd7713cb9a97c49096211ff40de280b6cebbb21c +// which is not yet in an LTS. +#if defined(__clang__) && (!defined(__clang_major__) || __clang_major__ >= 13) +#define CEL_ATTRIBUTE_ARENA_STRING_VIEW ABSL_ATTRIBUTE_VIEW +#else +#define CEL_ATTRIBUTE_ARENA_STRING_VIEW +#endif + +class CEL_ATTRIBUTE_ARENA_STRING_VIEW ArenaString final { + private: + template + static constexpr bool IsStringLiteral(const char (&string)[N]) { + static_assert(N > 0); + for (size_t i = 0; i < N - 1; ++i) { + if (string[i] == '\0') { + return false; + } + } + return string[N - 1] == '\0'; + } + + public: + using traits_type = std::char_traits; + using value_type = char; + using pointer = char*; + using const_pointer = const char*; + using reference = char&; + using const_reference = const char&; + using const_iterator = const_pointer; + using iterator = const_iterator; + using const_reverse_iterator = std::reverse_iterator; + using reverse_iterator = const_reverse_iterator; + using size_type = uint32_t; + using difference_type = int32_t; + + using absl_internal_is_view = std::true_type; + + template + static constexpr ArenaString Static(const char (&string)[N]) +#if ABSL_HAVE_ATTRIBUTE(enable_if) + __attribute__((enable_if(ArenaString::IsStringLiteral(string), + "chosen when 'string' is a string literal"))) +#endif + { + static_assert(N > 0); + static_assert(N - 1 <= std::numeric_limits::max()); + return ArenaString(string); + } + + ArenaString() = default; + ArenaString(const ArenaString&) = default; + ArenaString& operator=(const ArenaString&) = default; + + constexpr size_type size() const { return size_; } + + constexpr bool empty() const { return size() == 0; } + + constexpr size_type max_size() const { + return std::numeric_limits::max(); + } + + constexpr absl::Nonnull data() const { return data_; } + + constexpr const_reference front() const { + ABSL_ASSERT(!empty()); + return data()[0]; + } + + constexpr const_reference back() const { + ABSL_ASSERT(!empty()); + return data()[size() - 1]; + } + + constexpr const_reference operator[](size_type index) const { + ABSL_ASSERT(index < size()); + return data()[index]; + } + + constexpr void remove_prefix(size_type n) { + ABSL_ASSERT(n <= size()); + data_ += n; + size_ -= n; + } + + constexpr void remove_suffix(size_type n) { + ABSL_ASSERT(n <= size()); + size_ -= n; + } + + constexpr const_iterator begin() const { return data(); } + + constexpr const_iterator cbegin() const { return begin(); } + + constexpr const_iterator end() const { return data() + size(); } + + constexpr const_iterator cend() const { return end(); } + + constexpr const_reverse_iterator rbegin() const { + return std::make_reverse_iterator(end()); + } + + constexpr const_reverse_iterator crbegin() const { return rbegin(); } + + constexpr const_reverse_iterator rend() const { + return std::make_reverse_iterator(begin()); + } + + constexpr const_reverse_iterator crend() const { return rend(); } + + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr operator absl::string_view() const { + return absl::string_view(data(), size()); + } + + private: + friend class ArenaStringPool; + + constexpr explicit ArenaString(absl::string_view value) + : data_(value.data()), size_(static_cast(value.size())) { + ABSL_ASSERT(value.data() != nullptr); + ABSL_ASSERT(value.size() <= max_size()); + } + + absl::Nonnull data_ = ""; + size_type size_ = 0; +}; + +constexpr bool operator==(ArenaString lhs, ArenaString rhs) { + return absl::implicit_cast(lhs) == + absl::implicit_cast(rhs); +} + +constexpr bool operator==(ArenaString lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) == rhs; +} + +constexpr bool operator==(absl::string_view lhs, ArenaString rhs) { + return lhs == absl::implicit_cast(rhs); +} + +constexpr bool operator!=(ArenaString lhs, ArenaString rhs) { + return absl::implicit_cast(lhs) != + absl::implicit_cast(rhs); +} + +constexpr bool operator!=(ArenaString lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) != rhs; +} + +constexpr bool operator!=(absl::string_view lhs, ArenaString rhs) { + return lhs != absl::implicit_cast(rhs); +} + +constexpr bool operator<(ArenaString lhs, ArenaString rhs) { + return absl::implicit_cast(lhs) < + absl::implicit_cast(rhs); +} + +constexpr bool operator<(ArenaString lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) < rhs; +} + +constexpr bool operator<(absl::string_view lhs, ArenaString rhs) { + return lhs < absl::implicit_cast(rhs); +} + +constexpr bool operator<=(ArenaString lhs, ArenaString rhs) { + return absl::implicit_cast(lhs) <= + absl::implicit_cast(rhs); +} + +constexpr bool operator<=(ArenaString lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) <= rhs; +} + +constexpr bool operator<=(absl::string_view lhs, ArenaString rhs) { + return lhs <= absl::implicit_cast(rhs); +} + +constexpr bool operator>(ArenaString lhs, ArenaString rhs) { + return absl::implicit_cast(lhs) > + absl::implicit_cast(rhs); +} + +constexpr bool operator>(ArenaString lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) > rhs; +} + +constexpr bool operator>(absl::string_view lhs, ArenaString rhs) { + return lhs > absl::implicit_cast(rhs); +} + +constexpr bool operator>=(ArenaString lhs, ArenaString rhs) { + return absl::implicit_cast(lhs) >= + absl::implicit_cast(rhs); +} + +constexpr bool operator>=(ArenaString lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) >= rhs; +} + +constexpr bool operator>=(absl::string_view lhs, ArenaString rhs) { + return lhs >= absl::implicit_cast(rhs); +} + +template +H AbslHashValue(H state, ArenaString arena_string) { + return H::combine(std::move(state), + absl::implicit_cast(arena_string)); +} + +#undef CEL_ATTRIBUTE_ARENA_STRING_VIEW + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_COMMON_ARENA_STRING_H_ diff --git a/common/arena_string_pool.h b/common/arena_string_pool.h new file mode 100644 index 000000000..97de1334a --- /dev/null +++ b/common/arena_string_pool.h @@ -0,0 +1,64 @@ +// 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_COMMON_ARENA_STRING_POOL_H_ +#define THIRD_PARTY_CEL_CPP_COMMON_ARENA_STRING_POOL_H_ + +#include + +#include "absl/base/attributes.h" +#include "absl/base/nullability.h" +#include "absl/strings/string_view.h" +#include "common/arena_string.h" +#include "internal/string_pool.h" +#include "google/protobuf/arena.h" + +namespace cel { + +class ArenaStringPool; + +absl::Nonnull> NewArenaStringPool( + absl::Nonnull arena ABSL_ATTRIBUTE_LIFETIME_BOUND); + +class ArenaStringPool final { + public: + ArenaStringPool(const ArenaStringPool&) = delete; + ArenaStringPool(ArenaStringPool&&) = delete; + ArenaStringPool& operator=(const ArenaStringPool&) = delete; + ArenaStringPool& operator=(ArenaStringPool&&) = delete; + + ArenaString InternString(absl::string_view string) { + return ArenaString(strings_.InternString(string)); + } + + ArenaString InternString(ArenaString) = delete; + + private: + friend absl::Nonnull> NewArenaStringPool( + absl::Nonnull); + + explicit ArenaStringPool(absl::Nonnull arena) + : strings_(arena) {} + + internal::StringPool strings_; +}; + +inline absl::Nonnull> NewArenaStringPool( + absl::Nonnull arena ABSL_ATTRIBUTE_LIFETIME_BOUND) { + return std::unique_ptr(new ArenaStringPool(arena)); +} + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_COMMON_ARENA_STRING_POOL_H_ diff --git a/common/arena_string_pool_test.cc b/common/arena_string_pool_test.cc new file mode 100644 index 000000000..dda0fa864 --- /dev/null +++ b/common/arena_string_pool_test.cc @@ -0,0 +1,32 @@ +// 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 "common/arena_string_pool.h" + +#include "internal/testing.h" +#include "google/protobuf/arena.h" + +namespace cel { +namespace { + +TEST(ArenaStringPool, InternString) { + google::protobuf::Arena arena; + auto string_pool = NewArenaStringPool(&arena); + auto expected = string_pool->InternString("Hello World!"); + auto got = string_pool->InternString("Hello World!"); + EXPECT_EQ(expected.data(), got.data()); +} + +} // namespace +} // namespace cel diff --git a/common/arena_string_test.cc b/common/arena_string_test.cc new file mode 100644 index 000000000..9d80b9828 --- /dev/null +++ b/common/arena_string_test.cc @@ -0,0 +1,126 @@ +// 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 "common/arena_string.h" + +#include "absl/hash/hash.h" +#include "absl/hash/hash_testing.h" +#include "absl/strings/string_view.h" +#include "internal/testing.h" + +namespace cel { +namespace { + +using testing::Eq; +using testing::Ge; +using testing::Gt; +using testing::IsEmpty; +using testing::Le; +using testing::Lt; +using testing::Ne; +using testing::SizeIs; + +TEST(ArenaString, Default) { + ArenaString string; + EXPECT_THAT(string, IsEmpty()); + EXPECT_THAT(string, SizeIs(0)); + EXPECT_THAT(string, Eq(ArenaString())); +} + +TEST(ArenaString, Iterator) { + ArenaString string = ArenaString::Static("Hello World!"); + auto it = string.cbegin(); + EXPECT_THAT(*it++, Eq('H')); + EXPECT_THAT(*it++, Eq('e')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq(' ')); + EXPECT_THAT(*it++, Eq('W')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq('r')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('d')); + EXPECT_THAT(*it++, Eq('!')); + EXPECT_THAT(it, Eq(string.cend())); +} + +TEST(ArenaString, ReverseIterator) { + ArenaString string = ArenaString::Static("Hello World!"); + auto it = string.crbegin(); + EXPECT_THAT(*it++, Eq('!')); + EXPECT_THAT(*it++, Eq('d')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('r')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq('W')); + EXPECT_THAT(*it++, Eq(' ')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('e')); + EXPECT_THAT(*it++, Eq('H')); + EXPECT_THAT(it, Eq(string.crend())); +} + +TEST(ArenaString, RemovePrefix) { + ArenaString string = ArenaString::Static("Hello World!"); + string.remove_prefix(6); + EXPECT_EQ(string, "World!"); +} + +TEST(ArenaString, RemoveSuffix) { + ArenaString string = ArenaString::Static("Hello World!"); + string.remove_suffix(7); + EXPECT_EQ(string, "Hello"); +} + +TEST(ArenaString, Equal) { + EXPECT_THAT(ArenaString::Static("1"), Eq(ArenaString::Static("1"))); +} + +TEST(ArenaString, NotEqual) { + EXPECT_THAT(ArenaString::Static("1"), Ne(ArenaString::Static("2"))); +} + +TEST(ArenaString, Less) { + EXPECT_THAT(ArenaString::Static("1"), Lt(ArenaString::Static("2"))); +} + +TEST(ArenaString, LessEqual) { + EXPECT_THAT(ArenaString::Static("1"), Le(ArenaString::Static("1"))); +} + +TEST(ArenaString, Greater) { + EXPECT_THAT(ArenaString::Static("2"), Gt(ArenaString::Static("1"))); +} + +TEST(ArenaString, GreaterEqual) { + EXPECT_THAT(ArenaString::Static("1"), Ge(ArenaString::Static("1"))); +} + +TEST(ArenaString, ImplementsAbslHashCorrectly) { + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( + {ArenaString::Static(""), ArenaString::Static("Hello World!"), + ArenaString::Static("How much wood could a woodchuck chuck if a " + "woodchuck could chuck wood?")})); +} + +TEST(ArenaString, Hash) { + EXPECT_EQ(absl::HashOf(ArenaString::Static("Hello World!")), + absl::HashOf(absl::string_view("Hello World!"))); +} + +} // namespace +} // namespace cel