1 // Copyright 2020 gRPC authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef GRPC_SRC_CORE_UTIL_VALIDATION_ERRORS_H 16 #define GRPC_SRC_CORE_UTIL_VALIDATION_ERRORS_H 17 18 #include <grpc/support/port_platform.h> 19 #include <stddef.h> 20 21 #include <map> 22 #include <string> 23 #include <utility> 24 #include <vector> 25 26 #include "absl/status/status.h" 27 #include "absl/strings/string_view.h" 28 29 namespace grpc_core { 30 31 // Tracks errors that occur during validation of a data structure (e.g., 32 // a JSON object or protobuf message). Errors are tracked based on 33 // which field they are associated with. If at least one error occurs 34 // during validation, the validation failed. 35 // 36 // Example usage: 37 // 38 // absl::StatusOr<std::string> GetFooBar(const Json::Object& json) { 39 // ValidationErrors errors; 40 // { 41 // ValidationErrors::ScopedField field("foo"); 42 // auto it = json.object().find("foo"); 43 // if (it == json.object().end()) { 44 // errors.AddError("field not present"); 45 // } else if (it->second.type() != Json::Type::kObject) { 46 // errors.AddError("must be a JSON object"); 47 // } else { 48 // const Json& foo = it->second; 49 // ValidationErrors::ScopedField field(".bar"); 50 // auto it = foo.object().find("bar"); 51 // if (it == json.object().end()) { 52 // errors.AddError("field not present"); 53 // } else if (it->second.type() != Json::Type::kString) { 54 // errors.AddError("must be a JSON string"); 55 // } else { 56 // return it->second.string(); 57 // } 58 // } 59 // } 60 // return errors.status(absl::StatusCode::kInvalidArgument, 61 // "errors validating foo.bar"); 62 // } 63 class ValidationErrors { 64 public: 65 // Default maximum number of errors to track per scope. 66 static constexpr size_t kMaxErrorCount = 20; 67 68 // Pushes a field name onto the stack at construction and pops it off 69 // of the stack at destruction. 70 class ScopedField { 71 public: ScopedField(ValidationErrors * errors,absl::string_view field_name)72 ScopedField(ValidationErrors* errors, absl::string_view field_name) 73 : errors_(errors) { 74 errors_->PushField(field_name); 75 } 76 77 // Not copyable. 78 ScopedField(const ScopedField& other) = delete; 79 ScopedField& operator=(const ScopedField& other) = delete; 80 81 // Movable. ScopedField(ScopedField && other)82 ScopedField(ScopedField&& other) noexcept 83 : errors_(std::exchange(other.errors_, nullptr)) {} 84 ScopedField& operator=(ScopedField&& other) noexcept { 85 if (errors_ != nullptr) errors_->PopField(); 86 errors_ = std::exchange(other.errors_, nullptr); 87 return *this; 88 } 89 ~ScopedField()90 ~ScopedField() { 91 if (errors_ != nullptr) errors_->PopField(); 92 } 93 94 private: 95 ValidationErrors* errors_; 96 }; 97 ValidationErrors()98 ValidationErrors() : ValidationErrors(kMaxErrorCount) {} 99 100 // Creates a tracker that collects at most `max_error_count` errors per field. ValidationErrors(size_t max_error_count)101 explicit ValidationErrors(size_t max_error_count) 102 : max_error_count_(max_error_count) {} 103 104 // Records that we've encountered an error associated with the current 105 // field. 106 void AddError(absl::string_view error) GPR_ATTRIBUTE_NOINLINE; 107 108 // Returns true if the current field has errors. 109 bool FieldHasErrors() const GPR_ATTRIBUTE_NOINLINE; 110 111 // Returns the resulting status of parsing. 112 // If there are no errors, this will return an Ok status instead of using the 113 // prefix argument. 114 absl::Status status(absl::StatusCode code, absl::string_view prefix) const; 115 116 // Returns the resulting error message 117 // If there are no errors, this will return an empty string. 118 std::string message(absl::string_view prefix) const; 119 120 // Returns true if there are no errors. ok()121 bool ok() const { return field_errors_.empty(); } 122 size()123 size_t size() const { return field_errors_.size(); } 124 125 private: 126 // Pushes a field name onto the stack. 127 void PushField(absl::string_view ext) GPR_ATTRIBUTE_NOINLINE; 128 // Pops a field name off of the stack. 129 void PopField() GPR_ATTRIBUTE_NOINLINE; 130 131 // Errors that we have encountered so far, keyed by field name. 132 // TODO(roth): If we don't actually have any fields for which we 133 // report more than one error, simplify this data structure. 134 std::map<std::string /*field_name*/, std::vector<std::string>> field_errors_; 135 // Stack of field names indicating the field that we are currently 136 // validating. 137 std::vector<std::string> fields_; 138 139 size_t max_error_count_; 140 }; 141 142 } // namespace grpc_core 143 144 #endif // GRPC_SRC_CORE_UTIL_VALIDATION_ERRORS_H 145