mirror of https://github.com/grpc/grpc.git
json_object_loader: refactor ErrorList into its own library (#31049)
* json_object_loader: refactor ErrorList into its own library * fix observability_config_test * generate_projects * iwyupull/31071/head
parent
78c7cda232
commit
07df5ff9c7
39 changed files with 609 additions and 240 deletions
@ -0,0 +1,61 @@ |
||||
// Copyright 2020 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/lib/gprpp/validation_errors.h" |
||||
|
||||
#include <algorithm> |
||||
#include <utility> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/str_join.h" |
||||
#include "absl/strings/strip.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
void ValidationErrors::PushField(absl::string_view ext) { |
||||
// Skip leading '.' for top-level field names.
|
||||
if (fields_.empty()) absl::ConsumePrefix(&ext, "."); |
||||
fields_.emplace_back(std::string(ext)); |
||||
} |
||||
|
||||
void ValidationErrors::PopField() { fields_.pop_back(); } |
||||
|
||||
void ValidationErrors::AddError(absl::string_view error) { |
||||
field_errors_[absl::StrJoin(fields_, "")].emplace_back(error); |
||||
} |
||||
|
||||
bool ValidationErrors::FieldHasErrors() const { |
||||
return field_errors_.find(absl::StrJoin(fields_, "")) != field_errors_.end(); |
||||
} |
||||
|
||||
absl::Status ValidationErrors::status(absl::string_view prefix) const { |
||||
if (field_errors_.empty()) return absl::OkStatus(); |
||||
std::vector<std::string> errors; |
||||
for (const auto& p : field_errors_) { |
||||
if (p.second.size() > 1) { |
||||
errors.emplace_back(absl::StrCat("field:", p.first, " errors:[", |
||||
absl::StrJoin(p.second, "; "), "]")); |
||||
} else { |
||||
errors.emplace_back( |
||||
absl::StrCat("field:", p.first, " error:", p.second[0])); |
||||
} |
||||
} |
||||
return absl::InvalidArgumentError( |
||||
absl::StrCat(prefix, ": [", absl::StrJoin(errors, "; "), "]")); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,110 @@ |
||||
// Copyright 2020 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef GRPC_CORE_LIB_GPRPP_VALIDATION_ERRORS_H |
||||
#define GRPC_CORE_LIB_GPRPP_VALIDATION_ERRORS_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <stddef.h> |
||||
|
||||
#include <map> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/status.h" |
||||
#include "absl/strings/string_view.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
// Tracks errors that occur during validation of a data structure (e.g.,
|
||||
// a JSON object or protobuf message). Errors are tracked based on
|
||||
// which field they are associated with. If at least one error occurs
|
||||
// during validation, the validation failed.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// absl::StatusOr<std::string> GetFooBar(const Json::Object& json) {
|
||||
// ValidationErrors errors;
|
||||
// {
|
||||
// ValidationErrors::ScopedField field("foo");
|
||||
// auto it = json.object_value().find("foo");
|
||||
// if (it == json.object_value().end()) {
|
||||
// errors.AddError("field not present");
|
||||
// } else if (it->second.type() != Json::Type::OBJECT) {
|
||||
// errors.AddError("must be a JSON object");
|
||||
// } else {
|
||||
// const Json& foo = it->second;
|
||||
// ValidationErrors::ScopedField field(".bar");
|
||||
// auto it = foo.object_value().find("bar");
|
||||
// if (it == json.object_value().end()) {
|
||||
// errors.AddError("field not present");
|
||||
// } else if (it->second.type() != Json::Type::STRING) {
|
||||
// errors.AddError("must be a JSON string");
|
||||
// } else {
|
||||
// return it->second.string_value();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return errors.status("errors validating foo.bar");
|
||||
// }
|
||||
class ValidationErrors { |
||||
public: |
||||
// Pushes a field name onto the stack at construction and pops it off
|
||||
// of the stack at destruction.
|
||||
class ScopedField { |
||||
public: |
||||
ScopedField(ValidationErrors* errors, absl::string_view field_name) |
||||
: errors_(errors) { |
||||
errors_->PushField(field_name); |
||||
} |
||||
~ScopedField() { errors_->PopField(); } |
||||
|
||||
private: |
||||
ValidationErrors* errors_; |
||||
}; |
||||
|
||||
// Records that we've encountered an error associated with the current
|
||||
// field.
|
||||
void AddError(absl::string_view error) GPR_ATTRIBUTE_NOINLINE; |
||||
|
||||
// Returns true if the current field has errors.
|
||||
bool FieldHasErrors() const GPR_ATTRIBUTE_NOINLINE; |
||||
|
||||
// Returns the resulting status of parsing.
|
||||
absl::Status status(absl::string_view prefix) const; |
||||
|
||||
// Returns true if there are no errors.
|
||||
bool ok() const { return field_errors_.empty(); } |
||||
|
||||
size_t size() const { return field_errors_.size(); } |
||||
|
||||
private: |
||||
// Pushes a field name onto the stack.
|
||||
void PushField(absl::string_view ext) GPR_ATTRIBUTE_NOINLINE; |
||||
// Pops a field name off of the stack.
|
||||
void PopField() GPR_ATTRIBUTE_NOINLINE; |
||||
|
||||
// Errors that we have encountered so far, keyed by field name.
|
||||
// TODO(roth): If we don't actually have any fields for which we
|
||||
// report more than one error, simplify this data structure.
|
||||
std::map<std::string /*field_name*/, std::vector<std::string>> field_errors_; |
||||
// Stack of field names indicating the field that we are currently
|
||||
// validating.
|
||||
std::vector<std::string> fields_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_CORE_LIB_GPRPP_VALIDATION_ERRORS_H
|
@ -0,0 +1,111 @@ |
||||
//
|
||||
// Copyright 2022 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include "src/core/lib/gprpp/validation_errors.h" |
||||
|
||||
#include <memory> |
||||
#include <thread> |
||||
|
||||
#include <gtest/gtest.h> |
||||
|
||||
#include "test/core/util/test_config.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace testing { |
||||
namespace { |
||||
|
||||
TEST(ValidationErrors, NoErrors) { |
||||
ValidationErrors errors; |
||||
EXPECT_TRUE(errors.ok()); |
||||
EXPECT_EQ(errors.size(), 0); |
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, "foo"); |
||||
{ ValidationErrors::ScopedField field(&errors, ".bar"); } |
||||
} |
||||
EXPECT_TRUE(errors.ok()); |
||||
EXPECT_EQ(errors.size(), 0); |
||||
absl::Status status = errors.status("errors validating config"); |
||||
EXPECT_TRUE(status.ok()) << status; |
||||
} |
||||
|
||||
TEST(ValidationErrors, OneError) { |
||||
ValidationErrors errors; |
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, "foo"); |
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, ".bar"); |
||||
errors.AddError("value smells funny"); |
||||
} |
||||
} |
||||
EXPECT_FALSE(errors.ok()); |
||||
EXPECT_EQ(errors.size(), 1); |
||||
absl::Status status = errors.status("errors validating config"); |
||||
EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ( |
||||
status.message(), |
||||
"errors validating config: [field:foo.bar error:value smells funny]") |
||||
<< status; |
||||
} |
||||
|
||||
TEST(ValidationErrors, MultipleErrorsForSameField) { |
||||
ValidationErrors errors; |
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, "foo"); |
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, ".bar"); |
||||
errors.AddError("value smells funny"); |
||||
errors.AddError("value is ugly"); |
||||
} |
||||
} |
||||
EXPECT_FALSE(errors.ok()); |
||||
EXPECT_EQ(errors.size(), 1); |
||||
absl::Status status = errors.status("errors validating config"); |
||||
EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(status.message(), |
||||
"errors validating config: [field:foo.bar errors:[" |
||||
"value smells funny; value is ugly]]") |
||||
<< status; |
||||
} |
||||
|
||||
TEST(ValidationErrors, ErrorsForMultipleFields) { |
||||
ValidationErrors errors; |
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, "foo"); |
||||
{ |
||||
ValidationErrors::ScopedField field(&errors, ".bar"); |
||||
errors.AddError("value smells funny"); |
||||
} |
||||
errors.AddError("too hot"); |
||||
} |
||||
EXPECT_FALSE(errors.ok()); |
||||
EXPECT_EQ(errors.size(), 2); |
||||
absl::Status status = errors.status("errors validating config"); |
||||
EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); |
||||
EXPECT_EQ(status.message(), |
||||
"errors validating config: [" |
||||
"field:foo error:too hot; field:foo.bar error:value smells funny]") |
||||
<< status; |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
grpc::testing::TestEnvironment env(&argc, argv); |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue