pw_status: Functions for checking the status code

- Add IsErrorCode() functions to simplify checking for specific status
  codes.
- Automatically replace some cases of status == Status::Code() with
  status.IsCode() in update_style.py.

Change-Id: I13be834a2413d694d41a7755d2e317c35d34c5bf
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/19521
Reviewed-by: Keir Mierle <keir@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Commit-Queue: Wyatt Hepler <hepler@google.com>
diff --git a/pw_status/public/pw_status/status.h b/pw_status/public/pw_status/status.h
index be9e568..b7c1926 100644
--- a/pw_status/public/pw_status/status.h
+++ b/pw_status/public/pw_status/status.h
@@ -313,6 +313,56 @@
   // True if the status is Status::Ok().
   [[nodiscard]] constexpr bool ok() const { return code_ == PW_STATUS_OK; }
 
+  // Functions for checking which status this is.
+  [[nodiscard]] constexpr bool IsCancelled() const {
+    return code_ == PW_STATUS_CANCELLED;
+  }
+  [[nodiscard]] constexpr bool IsUnknown() const {
+    return code_ == PW_STATUS_UNKNOWN;
+  }
+  [[nodiscard]] constexpr bool IsInvalidArgument() const {
+    return code_ == PW_STATUS_INVALID_ARGUMENT;
+  }
+  [[nodiscard]] constexpr bool IsDeadlineExceeded() const {
+    return code_ == PW_STATUS_DEADLINE_EXCEEDED;
+  }
+  [[nodiscard]] constexpr bool IsNotFound() const {
+    return code_ == PW_STATUS_NOT_FOUND;
+  }
+  [[nodiscard]] constexpr bool IsAlreadyExists() const {
+    return code_ == PW_STATUS_ALREADY_EXISTS;
+  }
+  [[nodiscard]] constexpr bool IsPermissionDenied() const {
+    return code_ == PW_STATUS_PERMISSION_DENIED;
+  }
+  [[nodiscard]] constexpr bool IsResourceExhausted() const {
+    return code_ == PW_STATUS_RESOURCE_EXHAUSTED;
+  }
+  [[nodiscard]] constexpr bool IsFailedPrecondition() const {
+    return code_ == PW_STATUS_FAILED_PRECONDITION;
+  }
+  [[nodiscard]] constexpr bool IsAborted() const {
+    return code_ == PW_STATUS_ABORTED;
+  }
+  [[nodiscard]] constexpr bool IsOutOfRange() const {
+    return code_ == PW_STATUS_OUT_OF_RANGE;
+  }
+  [[nodiscard]] constexpr bool IsUnimplemented() const {
+    return code_ == PW_STATUS_UNIMPLEMENTED;
+  }
+  [[nodiscard]] constexpr bool IsInternal() const {
+    return code_ == PW_STATUS_INTERNAL;
+  }
+  [[nodiscard]] constexpr bool IsUnavailable() const {
+    return code_ == PW_STATUS_UNAVAILABLE;
+  }
+  [[nodiscard]] constexpr bool IsDataLoss() const {
+    return code_ == PW_STATUS_DATA_LOSS;
+  }
+  [[nodiscard]] constexpr bool IsUnauthenticated() const {
+    return code_ == PW_STATUS_UNAUTHENTICATED;
+  }
+
   // Returns a null-terminated string representation of the Status.
   [[nodiscard]] const char* str() const { return pw_StatusString(code_); }
 
diff --git a/pw_status/public/pw_status/status_with_size.h b/pw_status/public/pw_status/status_with_size.h
index 8c79e8c..d83548e 100644
--- a/pw_status/public/pw_status/status_with_size.h
+++ b/pw_status/public/pw_status/status_with_size.h
@@ -184,6 +184,56 @@
     return static_cast<Status::Code>((size_ & kStatusMask) >> kStatusShift);
   }
 
+  // Functions for checking which status the StatusWithSize contains.
+  [[nodiscard]] constexpr bool IsCancelled() const {
+    return status().IsCancelled();
+  }
+  [[nodiscard]] constexpr bool IsUnknown() const {
+    return status().IsUnknown();
+  }
+  [[nodiscard]] constexpr bool IsInvalidArgument() const {
+    return status().IsInvalidArgument();
+  }
+  [[nodiscard]] constexpr bool IsDeadlineExceeded() const {
+    return status().IsDeadlineExceeded();
+  }
+  [[nodiscard]] constexpr bool IsNotFound() const {
+    return status().IsNotFound();
+  }
+  [[nodiscard]] constexpr bool IsAlreadyExists() const {
+    return status().IsAlreadyExists();
+  }
+  [[nodiscard]] constexpr bool IsPermissionDenied() const {
+    return status().IsPermissionDenied();
+  }
+  [[nodiscard]] constexpr bool IsResourceExhausted() const {
+    return status().IsResourceExhausted();
+  }
+  [[nodiscard]] constexpr bool IsFailedPrecondition() const {
+    return status().IsFailedPrecondition();
+  }
+  [[nodiscard]] constexpr bool IsAborted() const {
+    return status().IsAborted();
+  }
+  [[nodiscard]] constexpr bool IsOutOfRange() const {
+    return status().IsOutOfRange();
+  }
+  [[nodiscard]] constexpr bool IsUnimplemented() const {
+    return status().IsUnimplemented();
+  }
+  [[nodiscard]] constexpr bool IsInternal() const {
+    return status().IsInternal();
+  }
+  [[nodiscard]] constexpr bool IsUnavailable() const {
+    return status().IsUnavailable();
+  }
+  [[nodiscard]] constexpr bool IsDataLoss() const {
+    return status().IsDataLoss();
+  }
+  [[nodiscard]] constexpr bool IsUnauthenticated() const {
+    return status().IsUnauthenticated();
+  }
+
  private:
   size_t size_;
 };
diff --git a/pw_status/py/pw_status/update_style.py b/pw_status/py/pw_status/update_style.py
index 57c92bb..bdff483 100755
--- a/pw_status/py/pw_status/update_style.py
+++ b/pw_status/py/pw_status/update_style.py
@@ -45,6 +45,16 @@
     'DATA_LOSS': 'DataLoss',
 }
 
+_CODES = '|'.join(_REMAP.keys())
+_FUNCTIONS = '|'.join(_REMAP.values())
+
+_STATUS_WITH_SIZE_CTOR = re.compile(
+    fr'\bStatusWithSize\(Status::({_CODES}),\s*')
+_STATUS = re.compile(fr'\b(Status|StatusWithSize)::({_CODES})(?!")\b')
+_STATUS_EQUALITY = re.compile(
+    fr'Status::(?P<l_func>{_FUNCTIONS})\(\)\s+==\s+(?P<value>[a-zA-Z0-9_.()]+)|'
+    fr'\s+==\s+Status::(?P<r_func>{_FUNCTIONS})\(\)')
+
 
 def _remap_status_with_size(match) -> str:
     return f'StatusWithSize::{_REMAP[match.group(1)]}('
@@ -54,11 +64,10 @@
     return f'{match.group(1)}::{_REMAP[match.group(2)]}()'
 
 
-_CODES = '|'.join(_REMAP.keys())
-
-_STATUS_WITH_SIZE_CTOR = re.compile(
-    fr'\bStatusWithSize\(Status::({_CODES}),\s*')
-_STATUS = re.compile(fr'\b(Status|StatusWithSize)::({_CODES})(?!")\b')
+def _remap_equality(match) -> str:
+    l_func, status, r_func = match.groups('')
+    func = l_func or r_func
+    return f'{status}.ok()' if func == 'Ok' else f'{status}.Is{func}()'
 
 
 def _parse_args():
@@ -92,6 +101,8 @@
             # Replace Status and StatusAWithSize
             text = _STATUS.sub(_remap_codes, text)
 
+            text = _STATUS_EQUALITY.sub(_remap_equality, text)
+
             if orig != text:
                 updated += 1
                 file.write_text(text)
diff --git a/pw_status/status_test.cc b/pw_status/status_test.cc
index 280b8be..a5fa3f4 100644
--- a/pw_status/status_test.cc
+++ b/pw_status/status_test.cc
@@ -90,6 +90,44 @@
   static_assert(PW_STATUS_UNAUTHENTICATED == Status::Unauthenticated());
 }
 
+TEST(Status, IsError) {
+  static_assert(Status::Cancelled().IsCancelled());
+  static_assert(Status::Unknown().IsUnknown());
+  static_assert(Status::InvalidArgument().IsInvalidArgument());
+  static_assert(Status::DeadlineExceeded().IsDeadlineExceeded());
+  static_assert(Status::NotFound().IsNotFound());
+  static_assert(Status::AlreadyExists().IsAlreadyExists());
+  static_assert(Status::PermissionDenied().IsPermissionDenied());
+  static_assert(Status::ResourceExhausted().IsResourceExhausted());
+  static_assert(Status::FailedPrecondition().IsFailedPrecondition());
+  static_assert(Status::Aborted().IsAborted());
+  static_assert(Status::OutOfRange().IsOutOfRange());
+  static_assert(Status::Unimplemented().IsUnimplemented());
+  static_assert(Status::Internal().IsInternal());
+  static_assert(Status::Unavailable().IsUnavailable());
+  static_assert(Status::DataLoss().IsDataLoss());
+  static_assert(Status::Unauthenticated().IsUnauthenticated());
+}
+
+TEST(Status, IsNotError) {
+  static_assert(!Status::Ok().IsCancelled());
+  static_assert(!Status::Ok().IsUnknown());
+  static_assert(!Status::Ok().IsInvalidArgument());
+  static_assert(!Status::Ok().IsDeadlineExceeded());
+  static_assert(!Status::Ok().IsNotFound());
+  static_assert(!Status::Ok().IsAlreadyExists());
+  static_assert(!Status::Ok().IsPermissionDenied());
+  static_assert(!Status::Ok().IsUnauthenticated());
+  static_assert(!Status::Ok().IsResourceExhausted());
+  static_assert(!Status::Ok().IsFailedPrecondition());
+  static_assert(!Status::Ok().IsAborted());
+  static_assert(!Status::Ok().IsOutOfRange());
+  static_assert(!Status::Ok().IsUnimplemented());
+  static_assert(!Status::Ok().IsInternal());
+  static_assert(!Status::Ok().IsUnavailable());
+  static_assert(!Status::Ok().IsDataLoss());
+}
+
 TEST(Status, Strings) {
   EXPECT_STREQ("OK", Status().str());
   EXPECT_STREQ("OK", Status::Ok().str());
diff --git a/pw_status/status_with_size_test.cc b/pw_status/status_with_size_test.cc
index f92e959..e856713 100644
--- a/pw_status/status_with_size_test.cc
+++ b/pw_status/status_with_size_test.cc
@@ -110,7 +110,7 @@
   static_assert(1234u == result.size());
 }
 
-TEST(Status, Functions_Status) {
+TEST(StatusWithSize, Functions_Status) {
   // clang-format off
   static_assert(StatusWithSize::Ok(0).status() == Status::Ok());
   static_assert(StatusWithSize::Cancelled().status() == Status::Cancelled());
@@ -150,7 +150,7 @@
   // clang-format on
 }
 
-TEST(Status, Functions_DefaultSize) {
+TEST(StatusWithSize, Functions_DefaultSize) {
   static_assert(StatusWithSize::Cancelled().size() == 0u);
   static_assert(StatusWithSize::Unknown().size() == 0u);
   static_assert(StatusWithSize::InvalidArgument().size() == 0u);
@@ -169,7 +169,7 @@
   static_assert(StatusWithSize::DataLoss().size() == 0u);
 }
 
-TEST(Status, Functions_SpecifiedSize) {
+TEST(StatusWithSize, Functions_SpecifiedSize) {
   static_assert(StatusWithSize::Ok(123).size() == 123u);
   static_assert(StatusWithSize::Cancelled(123).size() == 123u);
   static_assert(StatusWithSize::Unknown(123).size() == 123u);
@@ -189,5 +189,42 @@
   static_assert(StatusWithSize::DataLoss(123).size() == 123u);
 }
 
+TEST(StatusWithSize, IsError) {
+  static_assert(StatusWithSize::Cancelled().IsCancelled());
+  static_assert(StatusWithSize::Unknown().IsUnknown());
+  static_assert(StatusWithSize::InvalidArgument().IsInvalidArgument());
+  static_assert(StatusWithSize::DeadlineExceeded().IsDeadlineExceeded());
+  static_assert(StatusWithSize::NotFound().IsNotFound());
+  static_assert(StatusWithSize::AlreadyExists().IsAlreadyExists());
+  static_assert(StatusWithSize::PermissionDenied().IsPermissionDenied());
+  static_assert(StatusWithSize::ResourceExhausted().IsResourceExhausted());
+  static_assert(StatusWithSize::FailedPrecondition().IsFailedPrecondition());
+  static_assert(StatusWithSize::Aborted().IsAborted());
+  static_assert(StatusWithSize::OutOfRange().IsOutOfRange());
+  static_assert(StatusWithSize::Unimplemented().IsUnimplemented());
+  static_assert(StatusWithSize::Internal().IsInternal());
+  static_assert(StatusWithSize::Unavailable().IsUnavailable());
+  static_assert(StatusWithSize::DataLoss().IsDataLoss());
+  static_assert(StatusWithSize::Unauthenticated().IsUnauthenticated());
+}
+
+TEST(StatusWithSize, IsNotError) {
+  static_assert(!StatusWithSize::Ok(0).IsCancelled());
+  static_assert(!StatusWithSize::Ok(0).IsUnknown());
+  static_assert(!StatusWithSize::Ok(0).IsInvalidArgument());
+  static_assert(!StatusWithSize::Ok(0).IsDeadlineExceeded());
+  static_assert(!StatusWithSize::Ok(0).IsNotFound());
+  static_assert(!StatusWithSize::Ok(0).IsAlreadyExists());
+  static_assert(!StatusWithSize::Ok(0).IsPermissionDenied());
+  static_assert(!StatusWithSize::Ok(0).IsUnauthenticated());
+  static_assert(!StatusWithSize::Ok(0).IsResourceExhausted());
+  static_assert(!StatusWithSize::Ok(0).IsFailedPrecondition());
+  static_assert(!StatusWithSize::Ok(0).IsAborted());
+  static_assert(!StatusWithSize::Ok(0).IsOutOfRange());
+  static_assert(!StatusWithSize::Ok(0).IsUnimplemented());
+  static_assert(!StatusWithSize::Ok(0).IsInternal());
+  static_assert(!StatusWithSize::Ok(0).IsUnavailable());
+  static_assert(!StatusWithSize::Ok(0).IsDataLoss());
+}
 }  // namespace
 }  // namespace pw