Add error propagation for ValidateCorpusValue().

By returning absl::Status with detailed error message.

PiperOrigin-RevId: 551661565
diff --git a/domain_tests/BUILD b/domain_tests/BUILD
index cd576dc..399a121 100644
--- a/domain_tests/BUILD
+++ b/domain_tests/BUILD
@@ -22,6 +22,7 @@
     name = "domain_testing",
     testonly = 1,
     hdrs = ["domain_testing.h"],
+    visibility = ["@com_google_fuzztest//fuzztest:__subpackages__"],
     deps = [
         "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/hash",
@@ -60,13 +61,11 @@
     srcs = ["arbitrary_domains_test.cc"],
     deps = [
         ":domain_testing",
-        "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/random",
         "@com_google_absl//absl/random:bit_gen_ref",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/time",
-        "@com_google_absl//absl/types:span",
         "@com_google_fuzztest//fuzztest:absl_helpers",
         "@com_google_fuzztest//fuzztest:domain",
         "@com_google_fuzztest//fuzztest:domain_core",
diff --git a/domain_tests/CMakeLists.txt b/domain_tests/CMakeLists.txt
index fa00b22..5cac4e4 100644
--- a/domain_tests/CMakeLists.txt
+++ b/domain_tests/CMakeLists.txt
@@ -40,6 +40,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::flat_hash_set
   absl::optional
@@ -66,6 +67,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   protobuf::libprotobuf
   test_protobuf
@@ -96,6 +98,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::flat_hash_map
   absl::flat_hash_set
@@ -122,6 +125,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::flat_hash_set
   absl::random_random
@@ -149,6 +153,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::random_random
   absl::flat_hash_set
@@ -176,6 +181,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::flat_hash_set
   absl::random_random
@@ -201,6 +207,7 @@
   fuzztest_type_support
   fuzztest_meta
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::flat_hash_set
   absl::int128
@@ -226,6 +233,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::flat_hash_set
   absl::int128
@@ -253,6 +261,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::random_random
   GTest::gmock_main
@@ -275,6 +284,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::random_random
   GTest::gmock_main
@@ -297,6 +307,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::flat_hash_set
   absl::random_random
@@ -320,6 +331,7 @@
   fuzztest_coverage
   fuzztest_type_support
   fuzztest_serialization
+  fuzztest_status
   fuzztest_logging
   absl::flat_hash_set
   absl::random_random
diff --git a/domain_tests/aggregate_combinators_test.cc b/domain_tests/aggregate_combinators_test.cc
index f4cfbb9..49d80ce 100644
--- a/domain_tests/aggregate_combinators_test.cc
+++ b/domain_tests/aggregate_combinators_test.cc
@@ -270,11 +270,17 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for variant domain >> The value .+ is not InRange\(.+\))")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for variant domain >> The value .+ is not InRange\(.+\))")));
 }
 
 TEST(OptionalOf, InitCanMakeValuesOrNull) {
@@ -355,11 +361,13 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(domain_a.ValidateCorpusValue(value_b.corpus_value),
+              IsInvalid("Optional value must be set"));
+  EXPECT_THAT(domain_b.ValidateCorpusValue(value_a.corpus_value),
+              IsInvalid("Optional value must be null"));
 }
 
 TEST(OptionalOf, ValidationRejectsInvalidInnerValue) {
@@ -371,11 +379,15 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(R"(The value .+ is not InRange\(.+\))")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(R"(The value .+ is not InRange\(.+\))")));
 }
 
 TEST(TupleOf, ValidationRejectsInvalidInnerValue) {
@@ -387,11 +399,17 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value in aggregate >> The value .+ is not InRange\(.+\))")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value in aggregate >> The value .+ is not InRange\(.+\))")));
 }
 
 }  // namespace
diff --git a/domain_tests/arbitrary_domains_test.cc b/domain_tests/arbitrary_domains_test.cc
index b3d417d..82aac87 100644
--- a/domain_tests/arbitrary_domains_test.cc
+++ b/domain_tests/arbitrary_domains_test.cc
@@ -18,7 +18,6 @@
 #include <cmath>
 #include <cstdint>
 #include <limits>
-#include <list>
 #include <memory>
 #include <optional>
 #include <string>
@@ -33,13 +32,11 @@
 #include "google/protobuf/util/message_differencer.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
-#include "absl/container/flat_hash_map.h"
 #include "absl/container/flat_hash_set.h"
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/random.h"
 #include "absl/status/status.h"
 #include "absl/time/time.h"
-#include "absl/types/span.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
 #include "./fuzztest/internal/domains/absl_helpers.h"
@@ -232,7 +229,9 @@
     return internal::IRObject::FromCorpus(v);
   }
 
-  bool ValidateCorpusValue(const corpus_type&) const { return true; }
+  absl::Status ValidateCorpusValue(const corpus_type&) const {
+    return absl::OkStatus();
+  }
 
   auto GetPrinter() const { return internal::IntegralPrinter{}; }
 
@@ -625,13 +624,17 @@
   auto domain_with_optional_always_set =
       Arbitrary<internal::TestSubProtobuf>().WithOptionalFieldsAlwaysSet();
   auto corpus_value = domain_with_optional_always_set.FromValue(user_value);
-  EXPECT_FALSE(
-      domain_with_optional_always_set.ValidateCorpusValue(*corpus_value));
+  EXPECT_THAT(
+      domain_with_optional_always_set.ValidateCorpusValue(*corpus_value),
+      IsInvalid("Invalid value for field subproto_i32 >> Optional value must "
+                "be set"));
 
   auto domain_with_repeated_always_set =
       Arbitrary<internal::TestSubProtobuf>().WithRepeatedFieldsAlwaysSet();
-  EXPECT_FALSE(domain_with_repeated_always_set.ValidateCorpusValue(
-      *domain_with_optional_always_set.FromValue(user_value)));
+  EXPECT_THAT(domain_with_repeated_always_set.ValidateCorpusValue(
+                  *domain_with_optional_always_set.FromValue(user_value)),
+              IsInvalid("Invalid value for field subproto_rep_i32 >> Invalid "
+                        "size: 0. Min size: 1"));
 }
 
 TEST(ProtocolBuffer, SerializeAndParseCanHandleExtensions) {
@@ -661,11 +664,15 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid("Invalid value for field i32 >> Optional value must be set"));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid("Invalid value for field i32 >> Optional value must be null"));
 }
 
 TEST(ProtocolBuffer, ValidationRejectsUnexpectedRepeatedField) {
@@ -683,11 +690,17 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for field rep_i32 >> Invalid size: .+. Min size: 1)")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for field rep_i32 >> Invalid size: .+. Max size: 0)")));
 }
 
 TEST(ProtocolBufferEnum, Arbitrary) {
diff --git a/domain_tests/container_combinators_test.cc b/domain_tests/container_combinators_test.cc
index e69155e..fb79c34 100644
--- a/domain_tests/container_combinators_test.cc
+++ b/domain_tests/container_combinators_test.cc
@@ -196,11 +196,13 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(domain_a.ValidateCorpusValue(value_b.corpus_value),
+              IsInvalid("Invalid size: 3. Max size: 2"));
+  EXPECT_THAT(domain_b.ValidateCorpusValue(value_a.corpus_value),
+              IsInvalid("Invalid size: 2. Min size: 3"));
 }
 
 TEST(Container, ValidationRejectsInvalidElements) {
@@ -212,11 +214,17 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value in container at index 0 >> The value .+ is not InRange\(0, 9\))")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value in container at index 0 >> The value .+ is not InRange\(10, 12\))")));
 }
 
 TEST(ContainerCombinatorTest, ValueTypeOfListContainerIsInferred) {
@@ -461,11 +469,17 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value in container at index 0 >> The value .+ is not InRange\(0, 9\))")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value in container at index 0 >> The value .+ is not InRange\(10, 19\))")));
 }
 
 TEST(ContainerCombinatorTest, ArrayOfOne) {
diff --git a/domain_tests/domain_testing.h b/domain_tests/domain_testing.h
index a1948c9..32006ff 100644
--- a/domain_tests/domain_testing.h
+++ b/domain_tests/domain_testing.h
@@ -46,6 +46,28 @@
 
 namespace fuzztest {
 
+// Status matchers.
+
+MATCHER_P(StatusIs, status_code, "") { return arg.code() == status_code; }
+
+MATCHER_P2(StatusIs, status_code, message, "") {
+  return (arg.code() == status_code) &&
+         testing::Matches(message)(std::string(arg.message()));
+}
+
+MATCHER_P(IsInvalid, message, "") {
+  return testing::ExplainMatchResult(
+      StatusIs(absl::StatusCode::kInvalidArgument, message), arg,
+      result_listener);
+}
+
+#ifndef ASSERT_OK
+#define ASSERT_OK(x) ASSERT_THAT(x, StatusIs(absl::StatusCode::kOk))
+#endif  // ASSERT_OK
+#ifndef EXPECT_OK
+#define EXPECT_OK(x) EXPECT_THAT(x, StatusIs(absl::StatusCode::kOk))
+#endif  // EXPECT_OK
+
 // Tests whether arg is in the range [a, b].
 MATCHER_P2(IsInClosedRange, a, b,
            absl::StrCat(negation ? "isn't" : "is", " in the closed range [",
@@ -206,7 +228,7 @@
   {
     auto corpus_value = domain.FromValue(v.user_value);
     ASSERT_TRUE(corpus_value) << v;
-    ASSERT_TRUE(domain.ValidateCorpusValue(*corpus_value));
+    ASSERT_OK(domain.ValidateCorpusValue(*corpus_value));
     auto new_v = domain.GetValue(*corpus_value);
     EXPECT_TRUE(Eq{}(v.user_value, new_v))
         << "v=" << v << " new_v=" << testing::PrintToString(new_v);
@@ -218,7 +240,7 @@
     auto parsed_corpus = domain.ParseCorpus(*parsed);
     ASSERT_TRUE(parsed_corpus)
         << serialized << " value = " << testing::PrintToString(v.user_value);
-    ASSERT_TRUE(domain.ValidateCorpusValue(*parsed_corpus));
+    ASSERT_OK(domain.ValidateCorpusValue(*parsed_corpus));
     EXPECT_TRUE(Eq{}(v.user_value, domain.GetValue(*parsed_corpus)));
   }
 }
diff --git a/domain_tests/in_regexp_domain_test.cc b/domain_tests/in_regexp_domain_test.cc
index 081bf95..054c9ae 100644
--- a/domain_tests/in_regexp_domain_test.cc
+++ b/domain_tests/in_regexp_domain_test.cc
@@ -180,11 +180,13 @@
   auto corpus_value_a = domain_a.FromValue("acceeb");
   auto corpus_value_b = domain_b.FromValue("AADD");
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(*corpus_value_a));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(*corpus_value_b));
+  ASSERT_OK(domain_a.ValidateCorpusValue(*corpus_value_a));
+  ASSERT_OK(domain_b.ValidateCorpusValue(*corpus_value_b));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(*corpus_value_b));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(*corpus_value_a));
+  EXPECT_THAT(domain_a.ValidateCorpusValue(*corpus_value_b),
+              IsInvalid("Invalid value for InRegexp(\"a(c|d)+(e|f)+b\")"));
+  EXPECT_THAT(domain_b.ValidateCorpusValue(*corpus_value_a),
+              IsInvalid("Invalid value for InRegexp(\"A{2,10}D{2,10}\")"));
 }
 
 struct InRegexString {
@@ -198,7 +200,7 @@
   auto domain = InRegexp(regexp);
   auto corpus_value = domain.FromValue(string_in_domain);
   ASSERT_TRUE(corpus_value.has_value());
-  ASSERT_TRUE(domain.ValidateCorpusValue(*corpus_value));
+  ASSERT_OK(domain.ValidateCorpusValue(*corpus_value));
 
   EXPECT_THAT(domain.ParseCorpus(domain.SerializeCorpus(*corpus_value)),
               Optional(ResultOf(
diff --git a/domain_tests/map_filter_combinator_test.cc b/domain_tests/map_filter_combinator_test.cc
index a74328d..04afeff 100644
--- a/domain_tests/map_filter_combinator_test.cc
+++ b/domain_tests/map_filter_combinator_test.cc
@@ -88,11 +88,17 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for Map\(\)-ed domain >> The value .+ is not InRange\(0, 9\))")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for Map\(\)-ed domain >> The value .+ is not InRange\(10, 19\))")));
 }
 
 TEST(BidiMap, WorksWhenMapFunctionHasSameDomainAndRange) {
@@ -117,11 +123,17 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for BidiMap\(\)-ed domain >> The value .+ is not InRange\(0, 9\))")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for BidiMap\(\)-ed domain >> The value .+ is not InRange\(10, 19\))")));
 }
 
 TEST(BidiMap, AcceptsMultipleInnerDomains) {
@@ -211,11 +223,17 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for FlatMap\(\)-ed domain >> The value .+ is not InRange\(0, 9\))")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for FlatMap\(\)-ed domain >> The value .+ is not InRange\(10, 19\))")));
 }
 
 TEST(FlatMap, MutationAcceptsChangingDomains) {
@@ -326,11 +344,13 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(domain_a.ValidateCorpusValue(value_b.corpus_value),
+              IsInvalid("Value does not match Filter() predicate."));
+  EXPECT_THAT(domain_b.ValidateCorpusValue(value_a.corpus_value),
+              IsInvalid("Value does not match Filter() predicate."));
 }
 
 }  // namespace
diff --git a/domain_tests/misc_domains_test.cc b/domain_tests/misc_domains_test.cc
index c78c3db..0bfaac7 100644
--- a/domain_tests/misc_domains_test.cc
+++ b/domain_tests/misc_domains_test.cc
@@ -180,11 +180,17 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for OneOf\(\) domain >> The value .+ is not InRange(.+))")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(
+          R"(Invalid value for OneOf\(\) domain >> The value .+ is not InRange(.+))")));
 }
 
 TEST(OneOf, FromValueReturnsValidCorpusValuesWhenPossible) {
@@ -192,7 +198,7 @@
   auto corpus_value = domain.FromValue(6);
 
   ASSERT_TRUE(corpus_value.has_value());
-  EXPECT_TRUE(domain.ValidateCorpusValue(*corpus_value));
+  EXPECT_OK(domain.ValidateCorpusValue(*corpus_value));
 }
 
 }  // namespace
diff --git a/domain_tests/numeric_domains_test.cc b/domain_tests/numeric_domains_test.cc
index 9d8a3d7..7853f55 100644
--- a/domain_tests/numeric_domains_test.cc
+++ b/domain_tests/numeric_domains_test.cc
@@ -206,11 +206,15 @@
   Value value_a(domain_a, bitgen);
   Value value_b(domain_b, bitgen);
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(value_a.corpus_value));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(value_b.corpus_value));
+  ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value));
+  ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(value_b.corpus_value));
-  EXPECT_FALSE(domain_b.ValidateCorpusValue(value_a.corpus_value));
+  EXPECT_THAT(
+      domain_a.ValidateCorpusValue(value_b.corpus_value),
+      IsInvalid(testing::MatchesRegex(R"(The value .+ is not InRange\(.+\))")));
+  EXPECT_THAT(
+      domain_b.ValidateCorpusValue(value_a.corpus_value),
+      IsInvalid(testing::MatchesRegex(R"(The value .+ is not InRange\(.+\))")));
 }
 
 TYPED_TEST(NumericTest, InRangeValueIsParsedCorrectly) {
@@ -231,7 +235,7 @@
           "FUZZTESTv1 ",
           absl::Substitute(serialized_format, static_cast<int32_t>(max)))));
   ASSERT_TRUE(corpus_value.has_value());
-  EXPECT_TRUE(domain.ValidateCorpusValue(*corpus_value));
+  EXPECT_OK(domain.ValidateCorpusValue(*corpus_value));
 
   corpus_value =
       domain.ParseCorpus(*internal::IRObject::FromString(absl::StrCat(
@@ -239,7 +243,9 @@
           absl::Substitute(serialized_format, static_cast<int32_t>(max) + 1))));
   // Greater than max should be parsed, but rejected by validation.
   ASSERT_TRUE(corpus_value.has_value());
-  EXPECT_FALSE(domain.ValidateCorpusValue(*corpus_value));
+  EXPECT_THAT(
+      domain.ValidateCorpusValue(*corpus_value),
+      IsInvalid(testing::MatchesRegex(R"(The value .+ is not InRange\(.+\))")));
 }
 
 TYPED_TEST(NumericTest, NonZero) {
diff --git a/domain_tests/specific_value_domains_test.cc b/domain_tests/specific_value_domains_test.cc
index ca272dd..363b611 100644
--- a/domain_tests/specific_value_domains_test.cc
+++ b/domain_tests/specific_value_domains_test.cc
@@ -106,10 +106,11 @@
   auto corpus_value_a = domain_a.FromValue('a');
   auto corpus_value_b = domain_b.FromValue('c');
 
-  ASSERT_TRUE(domain_a.ValidateCorpusValue(*corpus_value_a));
-  ASSERT_TRUE(domain_b.ValidateCorpusValue(*corpus_value_b));
+  ASSERT_OK(domain_a.ValidateCorpusValue(*corpus_value_a));
+  ASSERT_OK(domain_b.ValidateCorpusValue(*corpus_value_b));
 
-  EXPECT_FALSE(domain_a.ValidateCorpusValue(*corpus_value_b));
+  EXPECT_THAT(domain_a.ValidateCorpusValue(*corpus_value_b),
+              IsInvalid("Invalid ElementOf() value"));
 }
 
 TEST(Just, Basic) {
diff --git a/e2e_tests/testdata/CMakeLists.txt b/e2e_tests/testdata/CMakeLists.txt
index 5bf6546..dbc3f1d 100644
--- a/e2e_tests/testdata/CMakeLists.txt
+++ b/e2e_tests/testdata/CMakeLists.txt
@@ -9,6 +9,7 @@
 target_link_libraries(
   fuzz_tests_for_microbenchmarking
   PUBLIC
+  fuzztest_status
   protobuf::libprotobuf
   test_protobuf
 )
@@ -29,6 +30,7 @@
   absl::str_format
   absl::time
   fuzztest_googletest_fixture_adapter
+  fuzztest_status
 )
 link_fuzztest(fuzz_tests_for_functional_testing.stripped)
 set_target_properties(
@@ -41,6 +43,11 @@
   fuzz_tests_with_invalid_seeds.stripped
   fuzz_tests_with_invalid_seeds.cc
 )
+target_link_libraries(
+  fuzz_tests_with_invalid_seeds.stripped
+  PUBLIC
+  fuzztest_status
+)
 link_fuzztest(fuzz_tests_with_invalid_seeds.stripped)
 set_target_properties(
   fuzz_tests_with_invalid_seeds.stripped
diff --git a/fuzztest/BUILD b/fuzztest/BUILD
index f4d42ca..041e9cf 100644
--- a/fuzztest/BUILD
+++ b/fuzztest/BUILD
@@ -136,6 +136,7 @@
         ":logging",
         ":meta",
         ":serialization",
+        ":status",
         ":table_of_recent_compares",
         ":type_support",
         "@com_google_absl//absl/container:flat_hash_map",
@@ -145,6 +146,7 @@
         "@com_google_absl//absl/random",
         "@com_google_absl//absl/random:bit_gen_ref",
         "@com_google_absl//absl/random:distributions",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/strings:str_format",
         "@com_google_absl//absl/time",
@@ -170,6 +172,7 @@
         ":meta",
         ":regexp_dfa",
         ":serialization",
+        ":status",
         ":type_support",
         "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/container:flat_hash_map",
@@ -365,6 +368,7 @@
         "@com_google_absl//absl/random",
         "@com_google_absl//absl/random:bit_gen_ref",
         "@com_google_absl//absl/random:distributions",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/strings:str_format",
         "@com_google_absl//absl/time",
@@ -432,6 +436,16 @@
 )
 
 cc_library(
+    name = "status",
+    srcs = ["internal/status.cc"],
+    hdrs = ["internal/status.h"],
+    deps = [
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
     name = "subprocess",
     srcs = ["internal/subprocess.cc"],
     hdrs = ["internal/subprocess.h"],
diff --git a/fuzztest/CMakeLists.txt b/fuzztest/CMakeLists.txt
index ea451ee..61c06de 100644
--- a/fuzztest/CMakeLists.txt
+++ b/fuzztest/CMakeLists.txt
@@ -135,6 +135,7 @@
     fuzztest_registration
     fuzztest_logging
     fuzztest_coverage
+    fuzztest_status
     fuzztest_type_support
     protobuf::libprotobuf
     test_protobuf
@@ -273,6 +274,7 @@
   PUBLIC
   fuzztest_domain
   fuzztest_meta
+  fuzztest_status
   fuzztest_type_support
   absl::any_invocable
   absl::str_format
@@ -294,6 +296,7 @@
   fuzztest_logging
   fuzztest_registration
   fuzztest_type_support
+  fuzztest_status
   absl::memory
   absl::str_format
   absl::span
@@ -313,6 +316,7 @@
     fuzztest_registration
     fuzztest_logging
     fuzztest_coverage
+    fuzztest_status
     fuzztest_type_support
     absl::span
     GTest::gmock_main
@@ -357,6 +361,7 @@
     fuzztest_seed_seq
     fuzztest_fixture_driver
     fuzztest_serialization
+    fuzztest_status
     fuzztest_io
     fuzztest_type_support
     protobuf::libprotobuf
@@ -374,6 +379,20 @@
 endif ()
 
 add_library(
+  fuzztest_status
+  OBJECT
+  internal/status.cc
+  internal/status.h
+)
+
+target_link_libraries(
+  fuzztest_status
+  PUBLIC
+  absl::status
+  absl::strings
+)
+
+add_library(
   fuzztest_subprocess
   OBJECT
   internal/subprocess.cc
@@ -447,18 +466,20 @@
   fuzztest_meta
   fuzztest_regexp_dfa
   fuzztest_serialization
+  fuzztest_status
   fuzztest_table_of_recent_compares
   fuzztest_type_support
   absl::base
+  absl::bits
   absl::flat_hash_map
   absl::flat_hash_set
-  absl::bits
   absl::int128
   absl::random_distributions
-  absl::strings
-  absl::str_format
-  absl::synchronization
   absl::span
+  absl::status
+  absl::str_format
+  absl::strings
+  absl::synchronization
 )
 
 add_library(
@@ -494,6 +515,7 @@
     fuzztest_io
     fuzztest_type_support
     fuzztest_runtime
+    fuzztest_status
     absl::time
     GTest::gmock_main
   )
@@ -522,12 +544,14 @@
   fuzztest_logging
   fuzztest_meta
   fuzztest_registration
+  fuzztest_status
   fuzztest_serialization
   fuzztest_type_support
   absl::any_invocable
   absl::function_ref
   absl::random_random
   absl::random_seed_sequences
+  absl::status
   absl::strings
   absl::str_format
   absl::time
@@ -551,6 +575,7 @@
     fuzztest_io
     fuzztest_type_support
     fuzztest_runtime
+    fuzztest_status
     absl::time
     test_protobuf
     GTest::gtest_main
@@ -674,6 +699,7 @@
     fuzztest_runtime
     fuzztest_table_of_recent_compares
     fuzztest_subprocess
+    fuzztest_status
     fuzztest_compatibility_mode
     absl::flags
     absl::flags_parse
diff --git a/fuzztest/domain_core.h b/fuzztest/domain_core.h
index 0061281..0b36b6e 100644
--- a/fuzztest/domain_core.h
+++ b/fuzztest/domain_core.h
@@ -147,7 +147,7 @@
 
   // After creating a corpus value, either via ParseCorpus() or via FromValue()
   // this method is used to determine if the corpus value is valid.
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     return inner_->UntypedValidateCorpusValue(corpus_value);
   }
 
@@ -303,7 +303,7 @@
       return GetInnerDomain().SerializeCorpus(v);
     }
 
-    bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+    absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
       return GetInnerDomain().ValidateCorpusValue(corpus_value);
     }
 
@@ -358,7 +358,7 @@
       return inner_.SerializeCorpus(v);
     }
 
-    bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+    absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
       return inner_.ValidateCorpusValue(corpus_value);
     }
 
diff --git a/fuzztest/internal/domains/aggregate_of_impl.h b/fuzztest/internal/domains/aggregate_of_impl.h
index 9d73903..16dcd33 100644
--- a/fuzztest/internal/domains/aggregate_of_impl.h
+++ b/fuzztest/internal/domains/aggregate_of_impl.h
@@ -29,6 +29,7 @@
 #include "./fuzztest/internal/domains/serialization_helpers.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest::internal {
@@ -182,12 +183,19 @@
     }
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
-    return ApplyIndex<sizeof...(Inner)>([&](auto... I) {
-      return (
-          std::get<I>(inner_).ValidateCorpusValue(std::get<I>(corpus_value)) &&
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    absl::Status result = absl::OkStatus();
+    ApplyIndex<sizeof...(Inner)>([&](auto... I) {
+      (
+          [&] {
+            if (!result.ok()) return;
+            const absl::Status s = std::get<I>(inner_).ValidateCorpusValue(
+                std::get<I>(corpus_value));
+            result = Prefix(s, "Invalid value in aggregate");
+          }(),
           ...);
     });
+    return result;
   }
 
  private:
diff --git a/fuzztest/internal/domains/arbitrary_impl.h b/fuzztest/internal/domains/arbitrary_impl.h
index 71e10ed..89a3d26 100644
--- a/fuzztest/internal/domains/arbitrary_impl.h
+++ b/fuzztest/internal/domains/arbitrary_impl.h
@@ -47,6 +47,7 @@
 #include "./fuzztest/internal/domains/variant_of_impl.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/table_of_recent_compares.h"
 #include "./fuzztest/internal/type_support.h"
 
@@ -73,8 +74,8 @@
 
   void Mutate(value_type&, absl::BitGenRef, bool) {}
 
-  bool ValidateCorpusValue(const value_type&) const {
-    return true;  // Nothing to validate.
+  absl::Status ValidateCorpusValue(const value_type&) const {
+    return absl::OkStatus();  // Nothing to validate.
   }
 
   auto GetPrinter() const { return MonostatePrinter{}; }
@@ -97,8 +98,8 @@
     }
   }
 
-  bool ValidateCorpusValue(const value_type&) const {
-    return true;  // Nothing to validate.
+  absl::Status ValidateCorpusValue(const value_type&) const {
+    return absl::OkStatus();  // Nothing to validate.
   }
 
   auto GetPrinter() const { return IntegralPrinter{}; }
@@ -178,8 +179,8 @@
     }
   }
 
-  bool ValidateCorpusValue(const value_type&) const {
-    return true;  // Nothing to validate.
+  absl::Status ValidateCorpusValue(const value_type&) const {
+    return absl::OkStatus();  // Nothing to validate.
   }
 
   auto GetPrinter() const { return IntegralPrinter{}; }
@@ -217,8 +218,8 @@
     val = std::byte{u8};
   }
 
-  bool ValidateCorpusValue(const corpus_type&) const {
-    return true;  // Nothing to validate.
+  absl::Status ValidateCorpusValue(const corpus_type&) const {
+    return absl::OkStatus();  // Nothing to validate.
   }
 
   auto GetPrinter() const { return IntegralPrinter{}; }
@@ -270,8 +271,8 @@
     } while (val == prev || (std::isnan(prev) && std::isnan(val)));
   }
 
-  bool ValidateCorpusValue(const value_type&) const {
-    return true;  // Nothing to validate.
+  absl::Status ValidateCorpusValue(const value_type&) const {
+    return absl::OkStatus();  // Nothing to validate.
   }
 
   auto GetPrinter() const { return FloatingPrinter{}; }
@@ -341,8 +342,8 @@
     return IRObject::FromCorpus(v);
   }
 
-  bool ValidateCorpusValue(const corpus_type&) const {
-    return true;  // Nothing to validate.
+  absl::Status ValidateCorpusValue(const corpus_type&) const {
+    return absl::OkStatus();  // Nothing to validate.
   }
 
  private:
diff --git a/fuzztest/internal/domains/bit_flag_combination_of_impl.h b/fuzztest/internal/domains/bit_flag_combination_of_impl.h
index 0198668..73270a4 100644
--- a/fuzztest/internal/domains/bit_flag_combination_of_impl.h
+++ b/fuzztest/internal/domains/bit_flag_combination_of_impl.h
@@ -63,7 +63,9 @@
     }
   }
 
-  bool ValidateCorpusValue(const value_type&) const { return true; }
+  absl::Status ValidateCorpusValue(const value_type&) const {
+    return absl::OkStatus();
+  }
 
   auto GetPrinter() const { return AutodetectTypePrinter<T>(); }
 
diff --git a/fuzztest/internal/domains/container_of_impl.h b/fuzztest/internal/domains/container_of_impl.h
index 878284f..8e94f50 100644
--- a/fuzztest/internal/domains/container_of_impl.h
+++ b/fuzztest/internal/domains/container_of_impl.h
@@ -26,6 +26,7 @@
 
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/distributions.h"
+#include "absl/status/status.h"
 #include "absl/strings/str_format.h"
 #include "absl/types/span.h"
 #include "./fuzztest/internal/coverage.h"
@@ -34,6 +35,7 @@
 #include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/table_of_recent_compares.h"
 #include "./fuzztest/internal/type_support.h"
 
@@ -303,18 +305,27 @@
     }
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     // Check size.
-    if (corpus_value.size() < min_size() || corpus_value.size() > max_size()) {
-      return false;
+    if (corpus_value.size() < min_size()) {
+      return absl::InvalidArgumentError(absl::StrCat(
+          "Invalid size: ", corpus_value.size(), ". Min size: ", min_size()));
+    }
+    if (corpus_value.size() > max_size()) {
+      return absl::InvalidArgumentError(absl::StrCat(
+          "Invalid size: ", corpus_value.size(), ". Max size: ", max_size()));
     }
     // Check elements.
+    int i = 0;
     for (const auto& elem : corpus_value) {
-      if (!inner_.ValidateCorpusValue(elem)) {
-        return false;
+      const absl::Status s = inner_.ValidateCorpusValue(elem);
+      if (!s.ok()) {
+        return Prefix(s,
+                      absl::StrCat("Invalid value in container at index ", i));
       }
+      i++;
     }
-    return true;
+    return absl::OkStatus();
   }
 
   InnerDomainT Inner() const { return inner_; }
diff --git a/fuzztest/internal/domains/domain_base.h b/fuzztest/internal/domains/domain_base.h
index 2c0234d..eb6be00 100644
--- a/fuzztest/internal/domains/domain_base.h
+++ b/fuzztest/internal/domains/domain_base.h
@@ -27,6 +27,7 @@
 
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/distributions.h"
+#include "absl/status/status.h"
 #include "absl/strings/str_format.h"
 #include "./fuzztest/internal/any.h"
 #include "./fuzztest/internal/logging.h"
@@ -70,7 +71,7 @@
       const GenericDomainCorpusType& val) = 0;
   virtual std::optional<GenericDomainCorpusType> UntypedParseCorpus(
       const IRObject& obj) const = 0;
-  virtual bool UntypedValidateCorpusValue(
+  virtual absl::Status UntypedValidateCorpusValue(
       const GenericDomainCorpusType& corpus_value) const = 0;
   virtual IRObject UntypedSerializeCorpus(
       const GenericDomainCorpusType& v) const = 0;
@@ -176,7 +177,7 @@
     return derived().SerializeCorpus(v.template GetAs<CorpusType>());
   }
 
-  bool UntypedValidateCorpusValue(
+  absl::Status UntypedValidateCorpusValue(
       const GenericDomainCorpusType& corpus_value) const final {
     return derived().ValidateCorpusValue(corpus_value.GetAs<CorpusType>());
   }
@@ -244,8 +245,8 @@
       std::optional<CorpusType> corpus_value = derived().FromValue(seed);
       if (!corpus_value.has_value()) ReportBadSeedAndExit(seed);
 
-      bool valid = derived().ValidateCorpusValue(*corpus_value);
-      if (!valid) ReportBadSeedAndExit(seed);
+      absl::Status valid = derived().ValidateCorpusValue(*corpus_value);
+      if (!valid.ok()) ReportBadSeedAndExit(seed);
 
       seeds_.push_back(*std::move(corpus_value));
     }
diff --git a/fuzztest/internal/domains/element_of_impl.h b/fuzztest/internal/domains/element_of_impl.h
index 105fb5f..8e97755 100644
--- a/fuzztest/internal/domains/element_of_impl.h
+++ b/fuzztest/internal/domains/element_of_impl.h
@@ -99,8 +99,11 @@
     return IRObject::FromCorpus(v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
-    return static_cast<size_t>(corpus_value) < values_.size();
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    if (static_cast<size_t>(corpus_value) < values_.size()) {
+      return absl::OkStatus();
+    }
+    return absl::InvalidArgumentError("Invalid ElementOf() value");
   }
 
  private:
diff --git a/fuzztest/internal/domains/filter_impl.h b/fuzztest/internal/domains/filter_impl.h
index 1fc48cc..fc5e7f3 100644
--- a/fuzztest/internal/domains/filter_impl.h
+++ b/fuzztest/internal/domains/filter_impl.h
@@ -23,6 +23,7 @@
 #include "./fuzztest/internal/domains/domain_base.h"
 #include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest::internal {
@@ -73,8 +74,10 @@
     return inner_.SerializeCorpus(v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
-    return predicate_(GetValue(corpus_value));
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    if (predicate_(GetValue(corpus_value))) return absl::OkStatus();
+    return absl::InvalidArgumentError(
+        "Value does not match Filter() predicate.");
   }
 
  private:
diff --git a/fuzztest/internal/domains/flat_map_impl.h b/fuzztest/internal/domains/flat_map_impl.h
index 4ab82cd..ea7a6ce 100644
--- a/fuzztest/internal/domains/flat_map_impl.h
+++ b/fuzztest/internal/domains/flat_map_impl.h
@@ -26,6 +26,7 @@
 #include "./fuzztest/internal/domains/serialization_helpers.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest::internal {
@@ -138,15 +139,22 @@
     return SerializeWithDomainTuple(domain, v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     // Check input values first.
-    bool input_values_valid =
-        ApplyIndex<sizeof...(InputDomain)>([&](auto... I) {
-          return (std::get<I>(input_domains_)
-                      .ValidateCorpusValue(std::get<I + 1>(corpus_value)) &&
-                  ...);
-        });
-    if (!input_values_valid) return false;
+    absl::Status input_values_validity = absl::OkStatus();
+    ApplyIndex<sizeof...(InputDomain)>([&](auto... I) {
+      (
+          [&] {
+            if (!input_values_validity.ok()) return;
+            const absl::Status s =
+                std::get<I>(input_domains_)
+                    .ValidateCorpusValue(std::get<I + 1>(corpus_value));
+            input_values_validity =
+                Prefix(s, "Invalid value for FlatMap()-ed domain");
+          }(),
+          ...);
+    });
+    if (!input_values_validity.ok()) return input_values_validity;
     // Check the output value.
     return GetOutputDomain(corpus_value)
         .ValidateCorpusValue(std::get<0>(corpus_value));
diff --git a/fuzztest/internal/domains/in_grammar_impl.h b/fuzztest/internal/domains/in_grammar_impl.h
index 64f8912..ce4cd52 100644
--- a/fuzztest/internal/domains/in_grammar_impl.h
+++ b/fuzztest/internal/domains/in_grammar_impl.h
@@ -673,11 +673,11 @@
     return TopDomain::ParseCorpus(obj);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     // Validation is currently done during Parsing, and UserToCorpusValue() is
     // not supported yet.
     // TODO(lszekeres): Refactor so that validation happens here instead.
-    return true;
+    return absl::OkStatus();
   }
 
  private:
diff --git a/fuzztest/internal/domains/in_range_impl.h b/fuzztest/internal/domains/in_range_impl.h
index 9b9619f..4ab0f4d 100644
--- a/fuzztest/internal/domains/in_range_impl.h
+++ b/fuzztest/internal/domains/in_range_impl.h
@@ -23,6 +23,7 @@
 
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/distributions.h"
+#include "absl/strings/str_cat.h"
 #include "./fuzztest/internal/coverage.h"
 #include "./fuzztest/internal/domains/domain_base.h"
 #include "./fuzztest/internal/domains/value_mutation_helpers.h"
@@ -155,8 +156,19 @@
     } while (val == prev);  // Make sure Mutate really mutates.
   }
 
-  bool ValidateCorpusValue(const value_type& corpus_value) const {
-    return (min_ <= corpus_value && corpus_value <= max_);
+  absl::Status ValidateCorpusValue(const value_type& corpus_value) const {
+    if (min_ <= corpus_value && corpus_value <= max_) return absl::OkStatus();
+    // We cannot just absl::StrCat() the error message, because it doesn't
+    // accept some types (like char).
+    std::string error_message;
+    absl::Format(&error_message, "The value ");
+    PrintValue(*this, corpus_value, &error_message, PrintMode::kSourceCode);
+    absl::Format(&error_message, " is not InRange(");
+    PrintValue(*this, min_, &error_message, PrintMode::kSourceCode);
+    absl::Format(&error_message, ", ");
+    PrintValue(*this, max_, &error_message, PrintMode::kSourceCode);
+    absl::Format(&error_message, ")");
+    return absl::InvalidArgumentError(error_message);
   }
 
   auto GetPrinter() const {
diff --git a/fuzztest/internal/domains/in_regexp_impl.h b/fuzztest/internal/domains/in_regexp_impl.h
index 82ef104..2940c93 100644
--- a/fuzztest/internal/domains/in_regexp_impl.h
+++ b/fuzztest/internal/domains/in_regexp_impl.h
@@ -39,7 +39,7 @@
 class InRegexpImpl : public DomainBase<InRegexpImpl, std::string, DFAPath> {
  public:
   explicit InRegexpImpl(std::string_view regex_str)
-      : dfa_(RegexpDFA::Create(regex_str)) {}
+      : regex_str_(regex_str), dfa_(RegexpDFA::Create(regex_str)) {}
 
   DFAPath Init(absl::BitGenRef prng) {
     if (auto seed = MaybeGetRandomSeed(prng)) return *seed;
@@ -139,9 +139,11 @@
     return obj;
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     // Check whether this is a valid path in the DFA.
-    return dfa_.DFAPathToString(corpus_value).has_value();
+    if (dfa_.DFAPathToString(corpus_value).has_value()) return absl::OkStatus();
+    return absl::InvalidArgumentError(
+        absl::StrCat("Invalid value for InRegexp(\"", regex_str_, "\")"));
   }
 
  private:
@@ -230,6 +232,7 @@
     }
     return false;
   }
+  std::string regex_str_;
   RegexpDFA dfa_;
 };
 
diff --git a/fuzztest/internal/domains/map_impl.h b/fuzztest/internal/domains/map_impl.h
index e3ba937..3dcd080 100644
--- a/fuzztest/internal/domains/map_impl.h
+++ b/fuzztest/internal/domains/map_impl.h
@@ -26,6 +26,7 @@
 #include "./fuzztest/internal/domains/serialization_helpers.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest::internal {
@@ -83,12 +84,19 @@
     return SerializeWithDomainTuple(inner_, v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
-    return ApplyIndex<sizeof...(Inner)>([&](auto... I) {
-      return (
-          std::get<I>(inner_).ValidateCorpusValue(std::get<I>(corpus_value)) &&
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    absl::Status result = absl::OkStatus();
+    ApplyIndex<sizeof...(Inner)>([&](auto... I) {
+      (
+          [&] {
+            if (!result.ok()) return;
+            const absl::Status s = std::get<I>(inner_).ValidateCorpusValue(
+                std::get<I>(corpus_value));
+            result = Prefix(s, "Invalid value for Map()-ed domain");
+          }(),
           ...);
     });
+    return result;
   }
 
  private:
@@ -148,12 +156,21 @@
     return SerializeWithDomainTuple(inner_, v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
-    return ApplyIndex<sizeof...(Inner)>([&](auto... I) {
-      return (
-          std::get<I>(inner_).ValidateCorpusValue(std::get<I>(corpus_value)) &&
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    absl::Status result = absl::OkStatus();
+    ApplyIndex<sizeof...(Inner)>([&](auto... I) {
+      (
+          [&] {
+            if (!result.ok()) return;
+            const absl::Status s = std::get<I>(inner_).ValidateCorpusValue(
+                std::get<I>(corpus_value));
+            if (!s.ok()) {
+              result = Prefix(s, "Invalid value for BidiMap()-ed domain");
+            }
+          }(),
           ...);
     });
+    return result;
   }
 
   std::optional<corpus_type> FromValue(const value_type& v) const {
diff --git a/fuzztest/internal/domains/one_of_impl.h b/fuzztest/internal/domains/one_of_impl.h
index 8db46ad..596a09c 100644
--- a/fuzztest/internal/domains/one_of_impl.h
+++ b/fuzztest/internal/domains/one_of_impl.h
@@ -27,6 +27,7 @@
 #include "./fuzztest/internal/domains/serialization_helpers.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest::internal {
@@ -93,8 +94,9 @@
       auto corpus_value = std::get<I>(domains_).FromValue(v);
       if (!corpus_value.has_value()) return false;
 
-      bool valid = std::get<I>(domains_).ValidateCorpusValue(*corpus_value);
-      if (!valid) return false;
+      const absl::Status valid =
+          std::get<I>(domains_).ValidateCorpusValue(*corpus_value);
+      if (!valid.ok()) return false;
 
       res.emplace(std::in_place_index<I>, *std::move(corpus_value));
       return true;
@@ -118,10 +120,11 @@
     return SerializeWithDomainVariant(domains_, v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     return Switch<kNumDomains>(corpus_value.index(), [&](auto I) {
-      return std::get<I>(domains_).ValidateCorpusValue(
-          std::get<I>(corpus_value));
+      const absl::Status s =
+          std::get<I>(domains_).ValidateCorpusValue(std::get<I>(corpus_value));
+      return Prefix(s, "Invalid value for OneOf() domain");
     });
   }
 
diff --git a/fuzztest/internal/domains/optional_of_impl.h b/fuzztest/internal/domains/optional_of_impl.h
index 12dc0ff..b592588 100644
--- a/fuzztest/internal/domains/optional_of_impl.h
+++ b/fuzztest/internal/domains/optional_of_impl.h
@@ -27,6 +27,7 @@
 #include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest::internal {
@@ -119,15 +120,19 @@
     return SerializeWithDomainOptional(inner_, v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     bool is_null = std::get_if<std::monostate>(&corpus_value);
     if (is_null) {
-      return policy_ != OptionalPolicy::kWithoutNull;
-    } else {
-      if (policy_ == OptionalPolicy::kAlwaysNull) return false;
-      // Validate inner object.
-      return inner_.ValidateCorpusValue(std::get<1>(corpus_value));
+      if (policy_ == OptionalPolicy::kWithoutNull) {
+        return absl::InvalidArgumentError("Optional value must be set");
+      }
+      return absl::OkStatus();
     }
+    if (policy_ == OptionalPolicy::kAlwaysNull) {
+      return absl::InvalidArgumentError("Optional value must be null");
+    }
+    // Validate inner object.
+    return inner_.ValidateCorpusValue(std::get<1>(corpus_value));
   }
 
   OptionalOfImpl& SetAlwaysNull() {
diff --git a/fuzztest/internal/domains/protobuf_domain_impl.h b/fuzztest/internal/domains/protobuf_domain_impl.h
index e452d96..1365114 100644
--- a/fuzztest/internal/domains/protobuf_domain_impl.h
+++ b/fuzztest/internal/domains/protobuf_domain_impl.h
@@ -45,6 +45,7 @@
 #include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/type_support.h"
 
 namespace google::protobuf {
@@ -901,7 +902,7 @@
     const ProtobufDomainUntypedImpl& self;
     // nullopt indicates that the field is not set.
     const std::optional<GenericDomainCorpusType>& corpus_value;
-    bool& out;
+    absl::Status& out;
 
     template <typename T>
     void VisitSingular(const FieldDescriptor* field) {
@@ -909,8 +910,9 @@
           corpus_value.has_value()
               ? *corpus_value
               : GetUnsetCorpusValue<T, /*is_repeated=*/false>(field);
-      out = self.GetSubDomain<T, /*is_repeated=*/false>(field)
-                .ValidateCorpusValue(value);
+      absl::Status s = self.GetSubDomain<T, /*is_repeated=*/false>(field)
+                           .ValidateCorpusValue(value);
+      out = Prefix(s, absl::StrCat("Invalid value for field ", field->name()));
     }
 
     template <typename T>
@@ -919,9 +921,10 @@
           corpus_value.has_value()
               ? *corpus_value
               : GetUnsetCorpusValue<T, /*is_repeated=*/true>(field);
-      out =
+      absl::Status s =
           self.GetSubDomain<T, /*is_repeated=*/true>(field).ValidateCorpusValue(
               value);
+      out = Prefix(s, absl::StrCat("Invalid value for field ", field->name()));
     }
 
    private:
@@ -941,7 +944,7 @@
     }
   };
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     for (int field_index = 0;
          field_index < prototype_.Get()->GetDescriptor()->field_count();
          ++field_index) {
@@ -951,12 +954,12 @@
       auto inner_corpus_value = (field_number_value != corpus_value.end())
                                     ? std::optional(field_number_value->second)
                                     : std::nullopt;
-      bool result;
+      absl::Status result;
       VisitProtobufField(field,
                          ValidateVisitor{*this, inner_corpus_value, result});
-      if (!result) return false;
+      if (!result.ok()) return result;
     }
-    return true;
+    return absl::OkStatus();
   }
 
   auto GetPrinter() const { return ProtobufPrinter{}; }
@@ -1531,7 +1534,7 @@
     return inner_.SerializeCorpus(v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     return inner_.ValidateCorpusValue(corpus_value);
   }
 
@@ -1996,8 +1999,8 @@
     return ProtobufEnumPrinter<decltype(descriptor())>{descriptor()};
   }
 
-  bool ValidateCorpusValue(const value_type&) const {
-    return true;  // Any number is fine.
+  absl::Status ValidateCorpusValue(const value_type&) const {
+    return absl::OkStatus();  // Any number is fine.
   }
 
  private:
diff --git a/fuzztest/internal/domains/smart_pointer_of_impl.h b/fuzztest/internal/domains/smart_pointer_of_impl.h
index 0e04edd..1245931 100644
--- a/fuzztest/internal/domains/smart_pointer_of_impl.h
+++ b/fuzztest/internal/domains/smart_pointer_of_impl.h
@@ -24,6 +24,7 @@
 #include "./fuzztest/internal/domains/domain_base.h"
 #include "./fuzztest/internal/domains/serialization_helpers.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest::internal {
@@ -98,9 +99,11 @@
     return SerializeWithDomainOptional(GetOrMakeInnerConst(), v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
-    return (corpus_value.index() == 0) ||
-           GetOrMakeInnerConst().ValidateCorpusValue(std::get<1>(corpus_value));
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    if (corpus_value.index() == 0) return absl::OkStatus();
+    const absl::Status s =
+        GetOrMakeInnerConst().ValidateCorpusValue(std::get<1>(corpus_value));
+    return Prefix(s, "Invalid value for smart pointer domain");
   }
 
  private:
diff --git a/fuzztest/internal/domains/unique_elements_container_of_impl.h b/fuzztest/internal/domains/unique_elements_container_of_impl.h
index 3db697f..35d573a 100644
--- a/fuzztest/internal/domains/unique_elements_container_of_impl.h
+++ b/fuzztest/internal/domains/unique_elements_container_of_impl.h
@@ -85,7 +85,7 @@
     return unique_domain_.SerializeCorpus(v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     return unique_domain_.ValidateCorpusValue(corpus_value);
   }
 
diff --git a/fuzztest/internal/domains/variant_of_impl.h b/fuzztest/internal/domains/variant_of_impl.h
index a92cd4c..6b7ba70 100644
--- a/fuzztest/internal/domains/variant_of_impl.h
+++ b/fuzztest/internal/domains/variant_of_impl.h
@@ -27,6 +27,7 @@
 #include "./fuzztest/internal/domains/serialization_helpers.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
 #include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest::internal {
@@ -98,9 +99,11 @@
     return SerializeWithDomainVariant(inner_, v);
   }
 
-  bool ValidateCorpusValue(const corpus_type& corpus_value) const {
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
     return Switch<sizeof...(Inner)>(corpus_value.index(), [&](auto I) {
-      return std::get<I>(inner_).ValidateCorpusValue(std::get<I>(corpus_value));
+      const absl::Status s =
+          std::get<I>(inner_).ValidateCorpusValue(std::get<I>(corpus_value));
+      return Prefix(s, "Invalid value for variant domain");
     });
   }
 
diff --git a/fuzztest/internal/runtime.cc b/fuzztest/internal/runtime.cc
index f1b21a9..5e54ce7 100644
--- a/fuzztest/internal/runtime.cc
+++ b/fuzztest/internal/runtime.cc
@@ -32,6 +32,7 @@
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/discrete_distribution.h"
 #include "absl/random/random.h"
+#include "absl/status/status.h"
 #include "absl/strings/numbers.h"
 #include "absl/strings/str_format.h"
 #include "absl/strings/string_view.h"
@@ -298,8 +299,9 @@
   auto corpus_value = params_domain_->UntypedParseCorpus(*ir_value);
   if (!corpus_value) return std::nullopt;
 
-  bool valid = params_domain_->UntypedValidateCorpusValue(*corpus_value);
-  if (!valid) return std::nullopt;
+  absl::Status valid =
+      params_domain_->UntypedValidateCorpusValue(*corpus_value);
+  if (!valid.ok()) return std::nullopt;
 
   return corpus_value;
 }
diff --git a/fuzztest/internal/status.cc b/fuzztest/internal/status.cc
new file mode 100644
index 0000000..3e5b92e
--- /dev/null
+++ b/fuzztest/internal/status.cc
@@ -0,0 +1,22 @@
+#include "./fuzztest/internal/status.h"
+
+#include "absl/strings/string_view.h"
+
+absl::Status SetMessage(const absl::Status& status, absl::string_view message) {
+  absl::Status result(status.code(), message);
+  status.ForEachPayload(
+      [&](absl::string_view type_url, const absl::Cord& payload) {
+        result.SetPayload(type_url, payload);
+      });
+  return result;
+}
+
+absl::Status Prefix(const absl::Status& status, absl::string_view prefix) {
+  if (status.ok() || prefix.empty()) return status;
+  return SetMessage(status, absl::StrCat(prefix, " >> ", status.message()));
+}
+
+absl::Status Postfix(const absl::Status& status, absl::string_view postfix) {
+  if (status.ok() || postfix.empty()) return status;
+  return SetMessage(status, absl::StrCat(status.message(), " >> ", postfix));
+}
diff --git a/fuzztest/internal/status.h b/fuzztest/internal/status.h
new file mode 100644
index 0000000..9cdc690
--- /dev/null
+++ b/fuzztest/internal/status.h
@@ -0,0 +1,12 @@
+#ifndef FUZZTEST_FUZZTEST_INTERNAL_STATUS_H_
+#define FUZZTEST_FUZZTEST_INTERNAL_STATUS_H_
+
+#include "absl/status/status.h"
+
+// Prefix status error message with `prefix`, if `status` is not OK.
+absl::Status Prefix(const absl::Status& status, absl::string_view prefix);
+
+// Postfix status error message with `postfix`, if `status` is not OK.
+absl::Status Postfix(const absl::Status& status, absl::string_view postfix);
+
+#endif  // FUZZTEST_FUZZTEST_INTERNAL_STATUS_H_