• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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