FTL: Add Expected<T, E>

Extend `base::expected` with `has_error()` and `value_opt()`.

Bug: 185536303
Test: ftl_test
Change-Id: I07d70dc8fe7ebfe2f08626dff51aef0b98430f61
diff --git a/include/ftl/expected.h b/include/ftl/expected.h
new file mode 100644
index 0000000..12b6102
--- /dev/null
+++ b/include/ftl/expected.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#pragma once
+
+#include <android-base/expected.h>
+#include <ftl/optional.h>
+
+#include <utility>
+
+namespace android::ftl {
+
+// Superset of base::expected<T, E> with monadic operations.
+//
+// TODO: Extend std::expected<T, E> in C++23.
+//
+template <typename T, typename E>
+struct Expected final : base::expected<T, E> {
+  using Base = base::expected<T, E>;
+  using Base::expected;
+
+  using Base::error;
+  using Base::has_value;
+  using Base::value;
+
+  template <typename P>
+  constexpr bool has_error(P predicate) const {
+    return !has_value() && predicate(error());
+  }
+
+  constexpr Optional<T> value_opt() const& {
+    return has_value() ? Optional(value()) : std::nullopt;
+  }
+
+  constexpr Optional<T> value_opt() && {
+    return has_value() ? Optional(std::move(value())) : std::nullopt;
+  }
+
+  // Delete new for this class. Its base doesn't have a virtual destructor, and
+  // if it got deleted via base class pointer, it would cause undefined
+  // behavior. There's not a good reason to allocate this object on the heap
+  // anyway.
+  static void* operator new(size_t) = delete;
+  static void* operator new[](size_t) = delete;
+};
+
+template <typename E>
+constexpr auto Unexpected(E&& error) {
+  return base::unexpected(std::forward<E>(error));
+}
+
+}  // namespace android::ftl
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index 918680d..5ac965f 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -10,11 +10,15 @@
 cc_test {
     name: "ftl_test",
     test_suites: ["device-tests"],
+    header_libs: [
+        "libbase_headers",
+    ],
     srcs: [
         "algorithm_test.cpp",
         "cast_test.cpp",
         "concat_test.cpp",
         "enum_test.cpp",
+        "expected_test.cpp",
         "fake_guard_test.cpp",
         "flags_test.cpp",
         "function_test.cpp",
diff --git a/libs/ftl/expected_test.cpp b/libs/ftl/expected_test.cpp
new file mode 100644
index 0000000..8cb07e4
--- /dev/null
+++ b/libs/ftl/expected_test.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 <ftl/expected.h>
+#include <gtest/gtest.h>
+
+#include <string>
+#include <system_error>
+
+namespace android::test {
+
+using IntExp = ftl::Expected<int, std::errc>;
+using StringExp = ftl::Expected<std::string, std::errc>;
+
+using namespace std::string_literals;
+
+TEST(Expected, Construct) {
+  // Default value.
+  EXPECT_TRUE(IntExp().has_value());
+  EXPECT_EQ(IntExp(), IntExp(0));
+
+  EXPECT_TRUE(StringExp().has_value());
+  EXPECT_EQ(StringExp(), StringExp(""));
+
+  // Value.
+  ASSERT_TRUE(IntExp(42).has_value());
+  EXPECT_EQ(42, IntExp(42).value());
+
+  ASSERT_TRUE(StringExp("test").has_value());
+  EXPECT_EQ("test"s, StringExp("test").value());
+
+  // Error.
+  const auto exp = StringExp(ftl::Unexpected(std::errc::invalid_argument));
+  ASSERT_FALSE(exp.has_value());
+  EXPECT_EQ(std::errc::invalid_argument, exp.error());
+}
+
+TEST(Expected, HasError) {
+  EXPECT_FALSE(IntExp(123).has_error([](auto) { return true; }));
+  EXPECT_FALSE(IntExp(ftl::Unexpected(std::errc::io_error)).has_error([](auto) { return false; }));
+
+  EXPECT_TRUE(StringExp(ftl::Unexpected(std::errc::permission_denied)).has_error([](auto e) {
+    return e == std::errc::permission_denied;
+  }));
+}
+
+TEST(Expected, ValueOpt) {
+  EXPECT_EQ(ftl::Optional(-1), IntExp(-1).value_opt());
+  EXPECT_EQ(std::nullopt, IntExp(ftl::Unexpected(std::errc::broken_pipe)).value_opt());
+
+  {
+    const StringExp exp("foo"s);
+    EXPECT_EQ(ftl::Optional('f'),
+              exp.value_opt().transform([](const auto& s) { return s.front(); }));
+    EXPECT_EQ("foo"s, exp.value());
+  }
+  {
+    StringExp exp("foobar"s);
+    EXPECT_EQ(ftl::Optional(6), std::move(exp).value_opt().transform(&std::string::length));
+    EXPECT_TRUE(exp.value().empty());
+  }
+}
+
+}  // namespace android::test