pw_status: StatusWithSize constants

- Provide StatusWithSize constants that can be used similarly to
  Status::Codes. For example, StatusWithSize::DATA_LOSS constructs a
  StatusWithSize with status DATA_LOSS and size 0.
- Remove the default constructor argument for the size. Use the
  constants instead.
- Update uses of StatusWithSize(Status::X) to use StatusWithSize::X.

Change-Id: I901b7bb0bbbb4fde52f6b3acc3047f366b250515
diff --git a/pw_kvs/entry.cc b/pw_kvs/entry.cc
index fa224d4..894b2ce 100644
--- a/pw_kvs/entry.cc
+++ b/pw_kvs/entry.cc
@@ -87,7 +87,7 @@
 
 StatusWithSize Entry::ReadValue(span<byte> buffer, size_t offset_bytes) const {
   if (offset_bytes > value_size()) {
-    return StatusWithSize(Status::OUT_OF_RANGE);
+    return StatusWithSize::OUT_OF_RANGE;
   }
 
   const size_t remaining_bytes = value_size() - offset_bytes;
diff --git a/pw_kvs/flash_memory.cc b/pw_kvs/flash_memory.cc
index f3d8dfb..0d164d8 100644
--- a/pw_kvs/flash_memory.cc
+++ b/pw_kvs/flash_memory.cc
@@ -48,7 +48,7 @@
 
 StatusWithSize FlashPartition::Write(Address address, span<const byte> data) {
   if (permission_ == PartitionPermission::kReadOnly) {
-    return StatusWithSize(Status::PERMISSION_DENIED);
+    return StatusWithSize::PERMISSION_DENIED;
   }
   TRY_WITH_SIZE(CheckBounds(address, data.size()));
   return flash_.Write(PartitionToFlashAddress(address), data);
diff --git a/pw_kvs/in_memory_fake_flash.cc b/pw_kvs/in_memory_fake_flash.cc
index 0b91965..1c9b7bc 100644
--- a/pw_kvs/in_memory_fake_flash.cc
+++ b/pw_kvs/in_memory_fake_flash.cc
@@ -79,7 +79,7 @@
 StatusWithSize InMemoryFakeFlash::Read(Address address,
                                        span<std::byte> output) {
   if (address + output.size() >= sector_count() * size_bytes()) {
-    return StatusWithSize(Status::OUT_OF_RANGE);
+    return StatusWithSize::OUT_OF_RANGE;
   }
 
   // Check for injected read errors
@@ -99,14 +99,14 @@
                  size_t(address),
                  data.size(),
                  alignment_bytes());
-    return StatusWithSize(Status::INVALID_ARGUMENT);
+    return StatusWithSize::INVALID_ARGUMENT;
   }
 
   if (data.size() > sector_size_bytes() - (address % sector_size_bytes())) {
     PW_LOG_ERROR("Write crosses sector boundary; address %zx, size %zu B",
                  size_t(address),
                  data.size());
-    return StatusWithSize(Status::INVALID_ARGUMENT);
+    return StatusWithSize::INVALID_ARGUMENT;
   }
 
   if (address + data.size() > sector_count() * sector_size_bytes()) {
@@ -115,7 +115,7 @@
         size_t(address),
         data.size(),
         sector_count() * sector_size_bytes());
-    return StatusWithSize(Status::OUT_OF_RANGE);
+    return StatusWithSize::OUT_OF_RANGE;
   }
 
   // Check in erased state
@@ -123,7 +123,7 @@
     if (buffer_[address + i] != kErasedValue) {
       PW_LOG_ERROR("Writing to previously written address: %zx",
                    size_t(address));
-      return StatusWithSize(Status::UNKNOWN);
+      return StatusWithSize::UNKNOWN;
     }
   }
 
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index 306ad82..6c438e8 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -341,7 +341,7 @@
         entry_header_format_.checksum, key, value_buffer.first(result.size()));
     if (!verify_result.ok()) {
       std::memset(value_buffer.data(), 0, result.size());
-      return StatusWithSize(verify_result);
+      return StatusWithSize(verify_result, 0);
     }
 
     return StatusWithSize(verify_result, result.size());
diff --git a/pw_kvs/public/pw_kvs/alignment.h b/pw_kvs/public/pw_kvs/alignment.h
index 08965ad..61b0b8e 100644
--- a/pw_kvs/public/pw_kvs/alignment.h
+++ b/pw_kvs/public/pw_kvs/alignment.h
@@ -101,7 +101,7 @@
   // TODO: This should convert to PW_CHECK once that is available for use in
   // host tests.
   if (alignment_bytes > kBufferSize) {
-    return StatusWithSize(Status::INTERNAL);
+    return StatusWithSize::INTERNAL;
   }
 
   AlignedWriterBuffer<kBufferSize> buffer(alignment_bytes, output);
diff --git a/pw_kvs/pw_kvs_private/macros.h b/pw_kvs/pw_kvs_private/macros.h
index 3fa326d..a4be24d 100644
--- a/pw_kvs/pw_kvs_private/macros.h
+++ b/pw_kvs/pw_kvs_private/macros.h
@@ -61,7 +61,7 @@
 }
 
 inline StatusWithSize ConvertToStatusWithSize(Status status) {
-  return StatusWithSize(status);
+  return StatusWithSize(status, 0);
 }
 
 inline StatusWithSize ConvertToStatusWithSize(StatusWithSize status_with_size) {
diff --git a/pw_status/public/pw_status/status_with_size.h b/pw_status/public/pw_status/status_with_size.h
index b13a09d..607b8e1 100644
--- a/pw_status/public/pw_status/status_with_size.h
+++ b/pw_status/public/pw_status/status_with_size.h
@@ -14,11 +14,29 @@
 #pragma once
 
 #include <cstddef>
+#include <type_traits>
 
 #include "pw_status/status.h"
 
 namespace pw {
 
+class StatusWithSize;
+
+namespace internal {
+
+template <int kStatusShift>
+class StatusWithSizeConstant {
+ private:
+  friend class ::pw::StatusWithSize;
+
+  explicit constexpr StatusWithSizeConstant(Status::Code value)
+      : value_(static_cast<size_t>(value) << kStatusShift) {}
+
+  const size_t value_;
+};
+
+}  // namespace internal
+
 // StatusWithSize stores a status and an unsigned integer. The integer must not
 // exceed StatusWithSize::max_size(), which is 134,217,727 (2**27 - 1) on 32-bit
 // systems.
@@ -45,14 +63,51 @@
 //      increases code size.
 //
 class StatusWithSize {
+ private:
+  static constexpr size_t kStatusBits = 5;
+  static constexpr size_t kSizeMask = ~static_cast<size_t>(0) >> kStatusBits;
+  static constexpr size_t kStatusMask = ~kSizeMask;
+  static constexpr size_t kStatusShift = sizeof(size_t) * 8 - kStatusBits;
+
+  using Constant = internal::StatusWithSizeConstant<kStatusShift>;
+
  public:
+  // Non-OK StatusWithSizes can be constructed from these constants, such as:
+  //
+  //   StatusWithSize result = StatusWithSize::NOT_FOUND;
+  //
+  static constexpr Constant CANCELLED{Status::CANCELLED};
+  static constexpr Constant UNKNOWN{Status::UNKNOWN};
+  static constexpr Constant INVALID_ARGUMENT{Status::INVALID_ARGUMENT};
+  static constexpr Constant DEADLINE_EXCEEDED{Status::DEADLINE_EXCEEDED};
+  static constexpr Constant NOT_FOUND{Status::NOT_FOUND};
+  static constexpr Constant ALREADY_EXISTS{Status::ALREADY_EXISTS};
+  static constexpr Constant PERMISSION_DENIED{Status::PERMISSION_DENIED};
+  static constexpr Constant RESOURCE_EXHAUSTED{Status::RESOURCE_EXHAUSTED};
+  static constexpr Constant FAILED_PRECONDITION{Status::FAILED_PRECONDITION};
+  static constexpr Constant ABORTED{Status::ABORTED};
+  static constexpr Constant OUT_OF_RANGE{Status::OUT_OF_RANGE};
+  static constexpr Constant UNIMPLEMENTED{Status::UNIMPLEMENTED};
+  static constexpr Constant INTERNAL{Status::INTERNAL};
+  static constexpr Constant UNAVAILABLE{Status::UNAVAILABLE};
+  static constexpr Constant DATA_LOSS{Status::DATA_LOSS};
+  static constexpr Constant UNAUTHENTICATED{Status::UNAUTHENTICATED};
+
+  // Creates a StatusWithSize with Status::OK and a size of 0.
+  explicit constexpr StatusWithSize() : size_(0) {}
+
   // Creates a StatusWithSize with Status::OK and the provided size.
+  // std::enable_if is used to prevent enum types (e.g. Status) from being used.
   // TODO(hepler): Add debug-only assert that size <= max_size().
-  explicit constexpr StatusWithSize(size_t size = 0) : size_(size) {}
+  template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+  explicit constexpr StatusWithSize(T size) : size_(size) {}
 
   // Creates a StatusWithSize with the provided status and size.
-  explicit constexpr StatusWithSize(Status::Code status, size_t size = 0)
-      : StatusWithSize(size | (static_cast<size_t>(status) << kStatusShift)) {}
+  constexpr StatusWithSize(Status::Code status, size_t size)
+      : StatusWithSize((static_cast<size_t>(status) << kStatusShift) | size) {}
+
+  // Allow implicit conversions from the StatusWithSize constants.
+  constexpr StatusWithSize(Constant constant) : size_(constant.value_) {}
 
   constexpr StatusWithSize(const StatusWithSize&) = default;
   constexpr StatusWithSize& operator=(const StatusWithSize&) = default;
@@ -71,11 +126,6 @@
   }
 
  private:
-  static constexpr size_t kStatusBits = 5;
-  static constexpr size_t kSizeMask = ~static_cast<size_t>(0) >> kStatusBits;
-  static constexpr size_t kStatusMask = ~kSizeMask;
-  static constexpr size_t kStatusShift = sizeof(size_t) * 8 - kStatusBits;
-
   size_t size_;
 };
 
diff --git a/pw_status/status_with_size_test.cc b/pw_status/status_with_size_test.cc
index 2ed4162..473384a 100644
--- a/pw_status/status_with_size_test.cc
+++ b/pw_status/status_with_size_test.cc
@@ -51,27 +51,18 @@
   EXPECT_EQ(99u, result.size());
 }
 
-TEST(StatusWithSize, ConstructWithStatusCodeAndDefaultSize) {
-  StatusWithSize result(Status::ALREADY_EXISTS);
+TEST(StatusWithSize, ConstructFromConstant) {
+  StatusWithSize result(StatusWithSize::ALREADY_EXISTS);
+
   EXPECT_EQ(Status::ALREADY_EXISTS, result.status());
   EXPECT_EQ(0u, result.size());
-}
 
-TEST(StatusWithSize, ConstructWithStatusAndDefaultSize) {
-  StatusWithSize result(Status{Status::ALREADY_EXISTS});
-  EXPECT_EQ(Status::ALREADY_EXISTS, result.status());
+  result = StatusWithSize::NOT_FOUND;
+
+  EXPECT_EQ(Status::NOT_FOUND, result.status());
   EXPECT_EQ(0u, result.size());
 }
 
-TEST(StatusWithSize, AllStatusValues_DefaultSize) {
-  for (int i = 0; i < 32; ++i) {
-    StatusWithSize result(static_cast<Status::Code>(i));
-    EXPECT_EQ(result.ok(), i == 0);
-    EXPECT_EQ(i, static_cast<int>(result.status()));
-    EXPECT_EQ(0u, result.size());
-  }
-}
-
 TEST(StatusWithSize, AllStatusValues_ZeroSize) {
   for (int i = 0; i < 32; ++i) {
     StatusWithSize result(static_cast<Status::Code>(i), 0);
diff --git a/pw_string/format.cc b/pw_string/format.cc
index 03942a3..b069fe8 100644
--- a/pw_string/format.cc
+++ b/pw_string/format.cc
@@ -31,7 +31,7 @@
                             const char* format,
                             va_list args) {
   if (buffer.empty()) {
-    return StatusWithSize(Status::RESOURCE_EXHAUSTED);
+    return StatusWithSize::RESOURCE_EXHAUSTED;
   }
 
   const int result = std::vsnprintf(buffer.data(), buffer.size(), format, args);
@@ -40,7 +40,7 @@
   // Discard any output by terminating the buffer.
   if (result < 0) {
     buffer[0] = '\0';
-    return StatusWithSize(Status::INVALID_ARGUMENT);
+    return StatusWithSize::INVALID_ARGUMENT;
   }
 
   // If result >= buffer.size(), the output was truncated and null-terminated.
diff --git a/pw_string/to_string_test.cc b/pw_string/to_string_test.cc
index 2dcb2d3..19d4931 100644
--- a/pw_string/to_string_test.cc
+++ b/pw_string/to_string_test.cc
@@ -43,7 +43,7 @@
   int result =
       std::snprintf(buffer.data(), buffer.size(), CustomType::kToString);
   if (result < 0) {
-    return StatusWithSize(Status::UNKNOWN);
+    return StatusWithSize::UNKNOWN;
   }
   if (static_cast<size_t>(result) < buffer.size()) {
     return StatusWithSize(result);
diff --git a/pw_string/type_to_string.cc b/pw_string/type_to_string.cc
index f22b3e3..fd2d1f6 100644
--- a/pw_string/type_to_string.cc
+++ b/pw_string/type_to_string.cc
@@ -51,7 +51,7 @@
   if (!buffer.empty()) {
     buffer[0] = '\0';
   }
-  return StatusWithSize(Status::RESOURCE_EXHAUSTED);
+  return StatusWithSize::RESOURCE_EXHAUSTED;
 }
 
 }  // namespace
@@ -181,7 +181,7 @@
 StatusWithSize CopyString(const std::string_view& value,
                           const span<char>& buffer) {
   if (buffer.empty()) {
-    return StatusWithSize(Status::RESOURCE_EXHAUSTED);
+    return StatusWithSize::RESOURCE_EXHAUSTED;
   }
 
   const size_t copied = value.copy(buffer.data(), buffer.size() - 1);