Make an ErrorList based on SizedArray

Change-Id: I6e37d1b6831daf902c625be81b982b3cb57e00c9
Signed-off-by: Ravago Jones <ravagojones@gmail.com>
diff --git a/aos/containers/BUILD b/aos/containers/BUILD
index 9fdc93f..ee1bf98 100644
--- a/aos/containers/BUILD
+++ b/aos/containers/BUILD
@@ -64,6 +64,30 @@
 )
 
 cc_library(
+    name = "error_list",
+    hdrs = [
+        "error_list.h",
+    ],
+    deps = [
+        ":sized_array",
+        "//aos:flatbuffers",
+    ],
+)
+
+cc_test(
+    name = "error_list_test",
+    srcs = [
+        "error_list_test.cc",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    deps = [
+        ":error_list",
+        "//aos:json_to_flatbuffer_fbs",
+        "//aos/testing:googletest",
+    ],
+)
+
+cc_library(
     name = "resizeable_buffer",
     hdrs = [
         "resizeable_buffer.h",
diff --git a/aos/containers/error_list.h b/aos/containers/error_list.h
new file mode 100644
index 0000000..2fbd39e
--- /dev/null
+++ b/aos/containers/error_list.h
@@ -0,0 +1,130 @@
+#ifndef AOS_CONTAINERS_ERROR_LIST_H_
+#define AOS_CONTAINERS_ERROR_LIST_H_
+
+#include <iostream>
+
+#include "aos/containers/sized_array.h"
+#include "flatbuffers/flatbuffers.h"
+
+namespace aos {
+
+// A de-duplicated sorted array based on SizedArray
+// For keeping a list of errors that a subsystem has thrown
+// to publish them in a Status message.
+// It is designed to use flatbuffer enums, and use the reserved fields MAX and
+// MIN to automatically determine how much capacity it needs to have.
+template <typename T>
+class ErrorList {
+ private:
+  using array = SizedArray<T, static_cast<size_t>(T::MAX) -
+                                  static_cast<size_t>(T::MIN) + 1>;
+  array array_;
+
+ public:
+  using value_type = typename array::value_type;
+  using size_type = typename array::size_type;
+  using difference_type = typename array::difference_type;
+  using reference = typename array::reference;
+  using const_reference = typename array::const_reference;
+  using pointer = typename array::pointer;
+  using const_pointer = typename array::const_pointer;
+  using iterator = typename array::iterator;
+  using const_iterator = typename array::const_iterator;
+  using reverse_iterator = typename array::reverse_iterator;
+  using const_reverse_iterator = typename array::const_reverse_iterator;
+
+  constexpr ErrorList() = default;
+  ErrorList(const ErrorList &) = default;
+  ErrorList(ErrorList &&) = default;
+  ErrorList(const flatbuffers::Vector<T> &array) : array_() {
+    for (auto it = array.begin(); it < array.end(); it++) {
+      array_.push_back(*it);
+    }
+    std::sort(array_.begin(), array_.end());
+  };
+
+  ErrorList &operator=(const ErrorList &) = default;
+  ErrorList &operator=(ErrorList &&) = default;
+
+  bool operator==(const ErrorList &other) const {
+    if (other.size() != size()) {
+      return false;
+    }
+    for (size_t i = 0; i < size(); ++i) {
+      if (other[i] != (*this)[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+  bool operator!=(const ErrorList &other) const { return !(*this == other); }
+
+  reference at(size_t i) { return array_.at(i); }
+  const_reference at(size_t i) const { return array_.at(i); }
+
+  reference operator[](size_t i) { return array_[i]; }
+  const_reference operator[](size_t i) const { return array_[i]; }
+
+  reference front() { return array_.front(); }
+  const_reference front() const { return array_.front(); }
+
+  reference back() { return array_.back(); }
+  const_reference back() const { return array_.back(); }
+
+  T *data() { return array_.data(); }
+  const T *data() const { return array_.data(); }
+
+  iterator begin() { return array_.begin(); }
+  const_iterator begin() const { return array_.begin(); }
+  const_iterator cbegin() const { return array_.cbegin(); }
+
+  iterator end() { return array_.end(); }
+  const_iterator end() const { return array_.end(); }
+  const_iterator cend() const { return array_.cend(); }
+
+  reverse_iterator rbegin() { return array_.rbegin(); }
+  const_reverse_iterator rbegin() const { return array_.rbegin(); }
+  const_reverse_iterator crbegin() const { return array_.crbegin(); }
+
+  reverse_iterator rend() { return array_.rend(); }
+  const_reverse_iterator rend() const { return array_.rend(); }
+  const_reverse_iterator crend() const { return array_.crend(); }
+
+  bool empty() const { return array_.empty(); }
+  bool full() const { return array_.full(); }
+
+  size_t size() const { return array_.size(); }
+  constexpr size_t max_size() const { return array_.max_size(); }
+
+  void Clear(const T t) {
+    iterator index = std::find(array_.begin(), array_.end(), t);
+    if (index != array_.end()) {
+      array_.erase(index);
+    }
+  }
+
+  void Set(const T t) {
+    iterator position = std::lower_bound(array_.begin(), array_.end(), t);
+
+    // if it found something, and that something is the same, just leave it
+    if (position != array_.end() && *position == t) {
+      return;
+    }
+
+    // key doesn't already exist
+    array_.insert(position, t);
+  }
+
+  bool Has(const T t) {
+    return std::binary_search(array_.begin(), array_.end(), t);
+  }
+
+  flatbuffers::Offset<flatbuffers::Vector<T>> ToFlatbuffer(
+      flatbuffers::FlatBufferBuilder *fbb) const {
+    return fbb->CreateVector(array_.data(), array_.size());
+  }
+};  // namespace aos
+
+}  // namespace aos
+
+#endif  // AOS_CONTAINERS_ERROR_LIST_H_
diff --git a/aos/containers/error_list_test.cc b/aos/containers/error_list_test.cc
new file mode 100644
index 0000000..3ce23c4
--- /dev/null
+++ b/aos/containers/error_list_test.cc
@@ -0,0 +1,115 @@
+#include "aos/containers/error_list.h"
+
+#include "aos/json_to_flatbuffer_generated.h"
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace testing {
+
+enum class TestEnum : int8_t {
+  FOO = 0,
+  BAR = 1,
+  BAZ = 2,
+  VWEEP = 3,
+  MIN = FOO,
+  MAX = VWEEP
+};
+
+// Tests that setting works and allows no duplicates
+TEST(ErrorListTest, NoDuplicates) {
+  ErrorList<TestEnum> a;
+  EXPECT_EQ(a.size(), 0);
+  a.Set(TestEnum::BAZ);
+  EXPECT_EQ(a.at(0), TestEnum::BAZ);
+  EXPECT_EQ(a.size(), 1);
+  a.Set(TestEnum::BAZ);
+  EXPECT_EQ(a.at(0), TestEnum::BAZ);
+  EXPECT_EQ(a.size(), 1);
+  a.Set(TestEnum::VWEEP);
+  EXPECT_EQ(a.at(0), TestEnum::BAZ);
+  EXPECT_EQ(a.at(1), TestEnum::VWEEP);
+  EXPECT_EQ(a.size(), 2);
+  a.Set(TestEnum::FOO);
+  EXPECT_EQ(a.at(0), TestEnum::FOO);
+  EXPECT_EQ(a.at(1), TestEnum::BAZ);
+  EXPECT_EQ(a.at(2), TestEnum::VWEEP);
+  EXPECT_EQ(a.size(), 3);
+}
+
+// Tests that clearing works
+TEST(ErrorListTest, Clearing) {
+  ErrorList<TestEnum> a;
+  a.Set(TestEnum::FOO);
+  a.Set(TestEnum::BAZ);
+  a.Set(TestEnum::VWEEP);
+  EXPECT_EQ(a.at(0), TestEnum::FOO);
+  EXPECT_EQ(a.at(1), TestEnum::BAZ);
+  EXPECT_EQ(a.at(2), TestEnum::VWEEP);
+  EXPECT_EQ(a.size(), 3);
+
+  a.Clear(TestEnum::BAR);
+  EXPECT_EQ(a.at(0), TestEnum::FOO);
+  EXPECT_EQ(a.at(1), TestEnum::BAZ);
+  EXPECT_EQ(a.at(2), TestEnum::VWEEP);
+  EXPECT_EQ(a.size(), 3);
+
+  a.Clear(TestEnum::BAZ);
+  EXPECT_EQ(a.at(0), TestEnum::FOO);
+  EXPECT_EQ(a.at(1), TestEnum::VWEEP);
+  EXPECT_EQ(a.size(), 2);
+}
+
+// Tests that checking for a value works
+TEST(ErrorListTest, Has) {
+  ErrorList<TestEnum> a;
+  a.Set(TestEnum::FOO);
+  a.Set(TestEnum::BAZ);
+  a.Set(TestEnum::VWEEP);
+  EXPECT_EQ(a.at(0), TestEnum::FOO);
+  EXPECT_EQ(a.at(1), TestEnum::BAZ);
+  EXPECT_EQ(a.at(2), TestEnum::VWEEP);
+  EXPECT_EQ(a.size(), 3);
+
+  EXPECT_TRUE(a.Has(TestEnum::FOO));
+  EXPECT_TRUE(a.Has(TestEnum::VWEEP));
+  EXPECT_TRUE(a.Has(TestEnum::BAZ));
+  EXPECT_FALSE(a.Has(TestEnum::BAR));
+}
+
+// Tests serializing and deserializing to/from flatbuffers.
+TEST(ErrorListTest, Flatbuffers) {
+  ErrorList<BaseType> a;
+  a.Set(BaseType::Bool);
+  a.Set(BaseType::Float);
+  a.Set(BaseType::Short);
+  EXPECT_TRUE(a.Has(BaseType::Bool));
+  EXPECT_TRUE(a.Has(BaseType::Short));
+  EXPECT_TRUE(a.Has(BaseType::Float));
+  EXPECT_EQ(a.at(0), BaseType::Bool);
+  EXPECT_EQ(a.at(1), BaseType::Short);
+  EXPECT_EQ(a.at(2), BaseType::Float);
+  EXPECT_EQ(a.size(), 3);
+
+  flatbuffers::FlatBufferBuilder fbb(1024);
+  flatbuffers::Offset<flatbuffers::Vector<BaseType>> vector =
+      a.ToFlatbuffer(&fbb);
+
+  ConfigurationBuilder builder(fbb);
+  builder.add_vector_foo_enum(vector);
+
+  fbb.Finish(builder.Finish());
+  const Configuration *config =
+      flatbuffers::GetRoot<Configuration>(fbb.GetBufferPointer());
+
+  ErrorList<BaseType> b(*config->vector_foo_enum());
+  EXPECT_TRUE(b.Has(BaseType::Bool));
+  EXPECT_TRUE(b.Has(BaseType::Short));
+  EXPECT_TRUE(b.Has(BaseType::Float));
+  EXPECT_EQ(b.at(0), BaseType::Bool);
+  EXPECT_EQ(b.at(1), BaseType::Short);
+  EXPECT_EQ(b.at(2), BaseType::Float);
+  EXPECT_EQ(b.size(), 3);
+}
+
+}  // namespace testing
+}  // namespace aos
diff --git a/aos/containers/sized_array_test.cc b/aos/containers/sized_array_test.cc
index ae732bc..d055f40 100644
--- a/aos/containers/sized_array_test.cc
+++ b/aos/containers/sized_array_test.cc
@@ -175,5 +175,70 @@
   EXPECT_DEATH(a.emplace_back(5), "Aborted at");
 }
 
+// Tests inserting at various positions in the array.
+TEST(SizedArrayTest, Inserting) {
+  SizedArray<int, 5> a;
+  a.insert(a.begin(), 2);
+  EXPECT_EQ(a.at(0), 2);
+  EXPECT_EQ(a.size(), 1);
+
+  a.emplace_back(3);
+  EXPECT_EQ(a.at(0), 2);
+  EXPECT_EQ(a.at(1), 3);
+  EXPECT_EQ(a.size(), 2);
+
+  a.insert(a.begin(), 0);
+  EXPECT_EQ(a.at(0), 0);
+  EXPECT_EQ(a.at(1), 2);
+  EXPECT_EQ(a.at(2), 3);
+  EXPECT_EQ(a.size(), 3);
+
+  a.insert(a.begin() + 1, 1);
+  EXPECT_EQ(a.at(0), 0);
+  EXPECT_EQ(a.at(1), 1);
+  EXPECT_EQ(a.at(2), 2);
+  EXPECT_EQ(a.at(3), 3);
+  EXPECT_EQ(a.size(), 4);
+
+  a.insert(a.begin() + 1, 0);
+  EXPECT_EQ(a.at(0), 0);
+  EXPECT_EQ(a.at(1), 0);
+  EXPECT_EQ(a.at(2), 1);
+  EXPECT_EQ(a.at(3), 2);
+  EXPECT_EQ(a.at(4), 3);
+  EXPECT_EQ(a.size(), 5);
+}
+
+// Tests erasing things from the array
+TEST(SizedArrayTest, Erasing) {
+  SizedArray<int, 5> a;
+  a.push_back(8);
+  a.push_back(9);
+  a.push_back(7);
+  a.push_back(1);
+  a.push_back(5);
+  EXPECT_EQ(a.at(0), 8);
+  EXPECT_EQ(a.at(1), 9);
+  EXPECT_EQ(a.at(2), 7);
+  EXPECT_EQ(a.at(3), 1);
+  EXPECT_EQ(a.at(4), 5);
+  EXPECT_EQ(a.size(), 5);
+
+  a.erase(a.begin() + 1, a.begin() + 3);
+  EXPECT_EQ(a.at(0), 8);
+  EXPECT_EQ(a.at(1), 1);
+  EXPECT_EQ(a.at(2), 5);
+  EXPECT_EQ(a.size(), 3);
+
+  a.erase(a.begin());
+  EXPECT_EQ(a.at(0), 1);
+  EXPECT_EQ(a.at(1), 5);
+  EXPECT_EQ(a.size(), 2);
+
+  a.erase(a.end() - 1);
+  EXPECT_EQ(a.at(0), 1);
+  EXPECT_EQ(a.size(), 1);
+}
+
 }  // namespace testing
 }  // namespace aos