Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(577)

Side by Side Diff: components/json_schema/json_schema_validator.cc

Issue 195193002: Add regular expression support in json_schema component (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/json_schema/json_schema_validator.h" 5 #include "components/json_schema/json_schema_validator.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <cfloat> 8 #include <cfloat>
9 #include <cmath> 9 #include <cmath>
10 #include <vector>
10 11
11 #include "base/json/json_reader.h" 12 #include "base/json/json_reader.h"
13 #include "base/memory/scoped_vector.h"
12 #include "base/strings/string_number_conversions.h" 14 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_util.h" 15 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h" 16 #include "base/strings/stringprintf.h"
15 #include "base/values.h" 17 #include "base/values.h"
16 #include "components/json_schema/json_schema_constants.h" 18 #include "components/json_schema/json_schema_constants.h"
19 #include "third_party/re2/re2/re2.h"
17 20
18 namespace schema = json_schema_constants; 21 namespace schema = json_schema_constants;
19 22
20 namespace { 23 namespace {
21 24
22 double GetNumberValue(const base::Value* value) { 25 double GetNumberValue(const base::Value* value) {
23 double result = 0; 26 double result = 0;
24 CHECK(value->GetAsDouble(&result)) 27 CHECK(value->GetAsDouble(&result))
25 << "Unexpected value type: " << value->GetType(); 28 << "Unexpected value type: " << value->GetType();
26 return result; 29 return result;
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
79 { schema::kDescription, base::Value::TYPE_STRING }, 82 { schema::kDescription, base::Value::TYPE_STRING },
80 { schema::kEnum, base::Value::TYPE_LIST }, 83 { schema::kEnum, base::Value::TYPE_LIST },
81 { schema::kId, base::Value::TYPE_STRING }, 84 { schema::kId, base::Value::TYPE_STRING },
82 { schema::kMaxItems, base::Value::TYPE_INTEGER }, 85 { schema::kMaxItems, base::Value::TYPE_INTEGER },
83 { schema::kMaxLength, base::Value::TYPE_INTEGER }, 86 { schema::kMaxLength, base::Value::TYPE_INTEGER },
84 { schema::kMaximum, base::Value::TYPE_DOUBLE }, 87 { schema::kMaximum, base::Value::TYPE_DOUBLE },
85 { schema::kMinItems, base::Value::TYPE_INTEGER }, 88 { schema::kMinItems, base::Value::TYPE_INTEGER },
86 { schema::kMinLength, base::Value::TYPE_INTEGER }, 89 { schema::kMinLength, base::Value::TYPE_INTEGER },
87 { schema::kMinimum, base::Value::TYPE_DOUBLE }, 90 { schema::kMinimum, base::Value::TYPE_DOUBLE },
88 { schema::kOptional, base::Value::TYPE_BOOLEAN }, 91 { schema::kOptional, base::Value::TYPE_BOOLEAN },
92 { schema::kPattern, base::Value::TYPE_STRING },
93 { schema::kPatternProperties, base::Value::TYPE_DICTIONARY },
89 { schema::kProperties, base::Value::TYPE_DICTIONARY }, 94 { schema::kProperties, base::Value::TYPE_DICTIONARY },
90 { schema::kTitle, base::Value::TYPE_STRING }, 95 { schema::kTitle, base::Value::TYPE_STRING },
91 }; 96 };
92 97
93 bool has_type_or_ref = false; 98 bool has_type_or_ref = false;
94 const base::ListValue* list_value = NULL; 99 const base::ListValue* list_value = NULL;
95 const base::DictionaryValue* dictionary_value = NULL; 100 const base::DictionaryValue* dictionary_value = NULL;
96 std::string string_value; 101 std::string string_value;
97 102
98 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { 103 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
176 if (it.value().IsType(base::Value::TYPE_INTEGER)) { 181 if (it.value().IsType(base::Value::TYPE_INTEGER)) {
177 int integer_value; 182 int integer_value;
178 it.value().GetAsInteger(&integer_value); 183 it.value().GetAsInteger(&integer_value);
179 if (integer_value < 0) { 184 if (integer_value < 0) {
180 *error = base::StringPrintf("Value of %s must be >= 0, got %d", 185 *error = base::StringPrintf("Value of %s must be >= 0, got %d",
181 it.key().c_str(), integer_value); 186 it.key().c_str(), integer_value);
182 return false; 187 return false;
183 } 188 }
184 } 189 }
185 190
191 if (it.key() == schema::kPattern) {
192 it.value().GetAsString(&string_value);
193 if (!re2::RE2(string_value).ok()) {
not at google - send to devlin 2014/03/11 14:53:39 How often does Validate get called? If it's a lot
binjin 2014/03/11 16:27:36 Yes, regular expression compiling is not very chea
not at google - send to devlin 2014/03/11 18:37:16 Good point re uncachability of this stuff. Ok. You
194 *error = "Invalid regular expression in pattern attribute";
not at google - send to devlin 2014/03/11 14:53:39 Could you include the RE2 error message here? htt
binjin 2014/03/11 16:27:36 Done.
195 return false;
196 }
197 }
198
186 // Validate the "properties" attribute. Each entry maps a key to a schema. 199 // Validate the "properties" attribute. Each entry maps a key to a schema.
187 if (it.key() == schema::kProperties) { 200 if (it.key() == schema::kProperties) {
188 it.value().GetAsDictionary(&dictionary_value); 201 it.value().GetAsDictionary(&dictionary_value);
189 for (base::DictionaryValue::Iterator it(*dictionary_value); 202 for (base::DictionaryValue::Iterator it(*dictionary_value);
190 !it.IsAtEnd(); it.Advance()) { 203 !it.IsAtEnd(); it.Advance()) {
191 if (!it.value().GetAsDictionary(&dictionary_value)) { 204 if (!it.value().GetAsDictionary(&dictionary_value)) {
192 *error = "Invalid value for properties attribute"; 205 *error = "Invalid value for properties attribute";
193 return false; 206 return false;
194 } 207 }
195 if (!IsValidSchema(dictionary_value, options, error)) { 208 if (!IsValidSchema(dictionary_value, options, error)) {
196 DCHECK(!error->empty()); 209 DCHECK(!error->empty());
197 return false; 210 return false;
198 } 211 }
199 } 212 }
200 } 213 }
201 214
215 // Validate the "patternProperties" attribute. Each entry maps a valid
216 // regular expression to a schema.
217 if (it.key() == schema::kPatternProperties) {
218 it.value().GetAsDictionary(&dictionary_value);
219 for (base::DictionaryValue::Iterator it(*dictionary_value);
not at google - send to devlin 2014/03/11 14:53:39 huh, how does the |it| declaration here not kill t
Joao da Silva 2014/03/11 16:20:13 IIUC this is the same as shadowing an outer variab
binjin 2014/03/11 16:27:36 My bad, I just copy-and-modified code from above b
220 !it.IsAtEnd(); it.Advance()) {
221 if (!re2::RE2(it.key()).ok()) {
222 *error = "Invalid regular expression in patternProperties attribute";
not at google - send to devlin 2014/03/11 14:53:39 ditto error message
binjin 2014/03/11 16:27:36 Done.
223 return false;
224 }
225 if (!it.value().GetAsDictionary(&dictionary_value)) {
226 *error = "Invalid value for patternProperties attribute";
not at google - send to devlin 2014/03/11 14:53:39 How about "patternProperties must be a dictionary"
binjin 2014/03/11 16:27:36 Done(for this and above).
227 return false;
228 }
229 if (!IsValidSchema(dictionary_value, options, error)) {
230 DCHECK(!error->empty());
231 return false;
232 }
233 }
234 }
235
202 // Validate "additionalProperties" attribute, which is a schema. 236 // Validate "additionalProperties" attribute, which is a schema.
203 if (it.key() == schema::kAdditionalProperties) { 237 if (it.key() == schema::kAdditionalProperties) {
204 it.value().GetAsDictionary(&dictionary_value); 238 it.value().GetAsDictionary(&dictionary_value);
205 if (!IsValidSchema(dictionary_value, options, error)) { 239 if (!IsValidSchema(dictionary_value, options, error)) {
206 DCHECK(!error->empty()); 240 DCHECK(!error->empty());
207 return false; 241 return false;
208 } 242 }
209 } 243 }
210 244
211 // Validate the values contained in an "enum" attribute. 245 // Validate the values contained in an "enum" attribute.
(...skipping 355 matching lines...) Expand 10 before | Expand all | Expand 10 after
567 // 'true'. 601 // 'true'.
568 bool is_optional = false; 602 bool is_optional = false;
569 prop_schema->GetBoolean(schema::kOptional, &is_optional); 603 prop_schema->GetBoolean(schema::kOptional, &is_optional);
570 if (!is_optional) { 604 if (!is_optional) {
571 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); 605 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired));
572 } 606 }
573 } 607 }
574 } 608 }
575 } 609 }
576 610
611 // Allowing any additional items will ignore pattern properties as well.
577 const base::DictionaryValue* additional_properties_schema = NULL; 612 const base::DictionaryValue* additional_properties_schema = NULL;
578 if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema)) 613 if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema))
579 return; 614 return;
580 615
581 // Validate additional properties. 616 const base::DictionaryValue* pattern_properties = NULL;
617 ScopedVector<re2::RE2> pattern_properties_pattern;
618 std::vector<const base::DictionaryValue*> pattern_properties_schema;
619
620 schema->GetDictionary(schema::kPatternProperties, &pattern_properties);
621 if (pattern_properties) {
not at google - send to devlin 2014/03/11 14:53:39 usual style I see would be to have the schema->Get
binjin 2014/03/11 16:27:36 Done(for this and above).
622 for (base::DictionaryValue::Iterator it(*pattern_properties); !it.IsAtEnd();
623 it.Advance()) {
624 re2::RE2* prop_pattern = new re2::RE2(it.key());
625 CHECK(prop_pattern->ok());
not at google - send to devlin 2014/03/11 14:53:39 See comment above about caching these. In fact cou
626 const base::DictionaryValue* prop_schema = NULL;
627 CHECK(it.value().GetAsDictionary(&prop_schema));
628 pattern_properties_pattern.push_back(prop_pattern);
629 pattern_properties_schema.push_back(prop_schema);
630 }
631 }
632
633 // Validate pattern properties and additional properties.
582 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); 634 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd();
583 it.Advance()) { 635 it.Advance()) {
584 if (properties && properties->HasKey(it.key())) 636 if (properties && properties->HasKey(it.key()))
585 continue; 637 continue;
586 638
587 std::string prop_path = path.empty() ? it.key() : path + "." + it.key(); 639 std::string prop_path = path.empty() ? it.key() : path + "." + it.key();
640
641 bool found_matching_pattern = false;
642 for (size_t index = 0; index < pattern_properties_pattern.size(); index++) {
not at google - send to devlin 2014/03/11 14:53:39 ++index
binjin 2014/03/11 16:27:36 Done.
643 if (re2::RE2::PartialMatch(it.key(),
644 *pattern_properties_pattern[index])) {
645 found_matching_pattern = true;
646 Validate(&it.value(), pattern_properties_schema[index], prop_path);
647 break;
648 }
649 }
Joao da Silva 2014/03/11 16:20:13 The order of these checks is not correct. See http
binjin 2014/03/11 17:20:04 I just read the specification, and from what I und
650
651 if (found_matching_pattern)
652 continue;
653
588 if (!additional_properties_schema) { 654 if (!additional_properties_schema) {
589 errors_.push_back(Error(prop_path, kUnexpectedProperty)); 655 errors_.push_back(Error(prop_path, kUnexpectedProperty));
590 } else { 656 } else {
591 Validate(&it.value(), additional_properties_schema, prop_path); 657 Validate(&it.value(), additional_properties_schema, prop_path);
592 } 658 }
593 } 659 }
594 } 660 }
595 661
596 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, 662 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance,
597 const base::DictionaryValue* schema, 663 const base::DictionaryValue* schema,
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
700 766
701 int max_length = 0; 767 int max_length = 0;
702 if (schema->GetInteger(schema::kMaxLength, &max_length)) { 768 if (schema->GetInteger(schema::kMaxLength, &max_length)) {
703 CHECK(max_length >= 0); 769 CHECK(max_length >= 0);
704 if (value.size() > static_cast<size_t>(max_length)) { 770 if (value.size() > static_cast<size_t>(max_length)) {
705 errors_.push_back(Error(path, FormatErrorMessage( 771 errors_.push_back(Error(path, FormatErrorMessage(
706 kStringMaxLength, base::IntToString(max_length)))); 772 kStringMaxLength, base::IntToString(max_length))));
707 } 773 }
708 } 774 }
709 775
710 CHECK(!schema->HasKey(schema::kPattern)) << "Pattern is not supported."; 776 std::string pattern;
777 if (schema->GetString(schema::kPattern, &pattern) &&
778 !re2::RE2::PartialMatch(value, pattern)) {
779 errors_.push_back(Error(path, FormatErrorMessage(kStringPattern, pattern)));
780 }
711 } 781 }
712 782
713 void JSONSchemaValidator::ValidateNumber(const base::Value* instance, 783 void JSONSchemaValidator::ValidateNumber(const base::Value* instance,
714 const base::DictionaryValue* schema, 784 const base::DictionaryValue* schema,
715 const std::string& path) { 785 const std::string& path) {
716 double value = GetNumberValue(instance); 786 double value = GetNumberValue(instance);
717 787
718 // TODO(aa): It would be good to test that the double is not infinity or nan, 788 // TODO(aa): It would be good to test that the double is not infinity or nan,
719 // but isnan and isinf aren't defined on Windows. 789 // but isnan and isinf aren't defined on Windows.
720 790
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
761 831
762 if (*additional_properties_schema) { 832 if (*additional_properties_schema) {
763 std::string additional_properties_type(schema::kAny); 833 std::string additional_properties_type(schema::kAny);
764 CHECK((*additional_properties_schema)->GetString( 834 CHECK((*additional_properties_schema)->GetString(
765 schema::kType, &additional_properties_type)); 835 schema::kType, &additional_properties_type));
766 return additional_properties_type == schema::kAny; 836 return additional_properties_type == schema::kAny;
767 } else { 837 } else {
768 return default_allow_additional_properties_; 838 return default_allow_additional_properties_;
769 } 839 }
770 } 840 }
OLDNEW
« no previous file with comments | « components/json_schema/json_schema_validator.h ('k') | components/json_schema/json_schema_validator_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698