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

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: fixes 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/logging.h"
14 #include "base/memory/scoped_vector.h"
12 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_util.h" 16 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h" 17 #include "base/strings/stringprintf.h"
15 #include "base/values.h" 18 #include "base/values.h"
16 #include "components/json_schema/json_schema_constants.h" 19 #include "components/json_schema/json_schema_constants.h"
20 #include "third_party/re2/re2/re2.h"
17 21
18 namespace schema = json_schema_constants; 22 namespace schema = json_schema_constants;
19 23
20 namespace { 24 namespace {
21 25
22 double GetNumberValue(const base::Value* value) { 26 double GetNumberValue(const base::Value* value) {
23 double result = 0; 27 double result = 0;
24 CHECK(value->GetAsDouble(&result)) 28 CHECK(value->GetAsDouble(&result))
25 << "Unexpected value type: " << value->GetType(); 29 << "Unexpected value type: " << value->GetType();
26 return result; 30 return result;
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
79 { schema::kDescription, base::Value::TYPE_STRING }, 83 { schema::kDescription, base::Value::TYPE_STRING },
80 { schema::kEnum, base::Value::TYPE_LIST }, 84 { schema::kEnum, base::Value::TYPE_LIST },
81 { schema::kId, base::Value::TYPE_STRING }, 85 { schema::kId, base::Value::TYPE_STRING },
82 { schema::kMaxItems, base::Value::TYPE_INTEGER }, 86 { schema::kMaxItems, base::Value::TYPE_INTEGER },
83 { schema::kMaxLength, base::Value::TYPE_INTEGER }, 87 { schema::kMaxLength, base::Value::TYPE_INTEGER },
84 { schema::kMaximum, base::Value::TYPE_DOUBLE }, 88 { schema::kMaximum, base::Value::TYPE_DOUBLE },
85 { schema::kMinItems, base::Value::TYPE_INTEGER }, 89 { schema::kMinItems, base::Value::TYPE_INTEGER },
86 { schema::kMinLength, base::Value::TYPE_INTEGER }, 90 { schema::kMinLength, base::Value::TYPE_INTEGER },
87 { schema::kMinimum, base::Value::TYPE_DOUBLE }, 91 { schema::kMinimum, base::Value::TYPE_DOUBLE },
88 { schema::kOptional, base::Value::TYPE_BOOLEAN }, 92 { schema::kOptional, base::Value::TYPE_BOOLEAN },
93 { schema::kPattern, base::Value::TYPE_STRING },
94 { schema::kPatternProperties, base::Value::TYPE_DICTIONARY },
89 { schema::kProperties, base::Value::TYPE_DICTIONARY }, 95 { schema::kProperties, base::Value::TYPE_DICTIONARY },
90 { schema::kTitle, base::Value::TYPE_STRING }, 96 { schema::kTitle, base::Value::TYPE_STRING },
91 }; 97 };
92 98
93 bool has_type_or_ref = false; 99 bool has_type_or_ref = false;
94 const base::ListValue* list_value = NULL; 100 const base::ListValue* list_value = NULL;
95 const base::DictionaryValue* dictionary_value = NULL; 101 const base::DictionaryValue* dictionary_value = NULL;
96 std::string string_value; 102 std::string string_value;
97 103
98 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { 104 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
179 if (integer_value < 0) { 185 if (integer_value < 0) {
180 *error = base::StringPrintf("Value of %s must be >= 0, got %d", 186 *error = base::StringPrintf("Value of %s must be >= 0, got %d",
181 it.key().c_str(), integer_value); 187 it.key().c_str(), integer_value);
182 return false; 188 return false;
183 } 189 }
184 } 190 }
185 191
186 // Validate the "properties" attribute. Each entry maps a key to a schema. 192 // Validate the "properties" attribute. Each entry maps a key to a schema.
187 if (it.key() == schema::kProperties) { 193 if (it.key() == schema::kProperties) {
188 it.value().GetAsDictionary(&dictionary_value); 194 it.value().GetAsDictionary(&dictionary_value);
189 for (base::DictionaryValue::Iterator it(*dictionary_value); 195 for (base::DictionaryValue::Iterator iter(*dictionary_value);
190 !it.IsAtEnd(); it.Advance()) { 196 !iter.IsAtEnd(); iter.Advance()) {
191 if (!it.value().GetAsDictionary(&dictionary_value)) { 197 if (!iter.value().GetAsDictionary(&dictionary_value)) {
192 *error = "Invalid value for properties attribute"; 198 *error = "properties must be a dictionary";
193 return false; 199 return false;
194 } 200 }
195 if (!IsValidSchema(dictionary_value, options, error)) { 201 if (!IsValidSchema(dictionary_value, options, error)) {
202 DCHECK(!error->empty());
203 return false;
204 }
205 }
206 }
207
208 // Validate the "patternProperties" attribute. Each entry maps a regular
209 // expression to a schema. The validity of the regular expression expression
210 // won't be checked here for performance reason.
not at google - send to devlin 2014/03/25 14:38:06 .... "reasons. Instead, invalid regular expression
binjin 2014/03/27 12:21:01 Done.
211 if (it.key() == schema::kPatternProperties) {
212 it.value().GetAsDictionary(&dictionary_value);
213 for (base::DictionaryValue::Iterator iter(*dictionary_value);
214 !iter.IsAtEnd(); iter.Advance()) {
215 if (!iter.value().GetAsDictionary(&dictionary_value)) {
216 *error = "patternProperties must be a dictionary";
217 return false;
218 }
219 if (!IsValidSchema(dictionary_value, options, error)) {
196 DCHECK(!error->empty()); 220 DCHECK(!error->empty());
197 return false; 221 return false;
198 } 222 }
199 } 223 }
200 } 224 }
201 225
202 // Validate "additionalProperties" attribute, which is a schema. 226 // Validate "additionalProperties" attribute, which is a schema.
203 if (it.key() == schema::kAdditionalProperties) { 227 if (it.key() == schema::kAdditionalProperties) {
204 it.value().GetAsDictionary(&dictionary_value); 228 it.value().GetAsDictionary(&dictionary_value);
205 if (!IsValidSchema(dictionary_value, options, error)) { 229 if (!IsValidSchema(dictionary_value, options, error)) {
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
301 const char JSONSchemaValidator::kStringPattern[] = 325 const char JSONSchemaValidator::kStringPattern[] =
302 "String must match the pattern: *."; 326 "String must match the pattern: *.";
303 const char JSONSchemaValidator::kNumberMinimum[] = 327 const char JSONSchemaValidator::kNumberMinimum[] =
304 "Value must not be less than *."; 328 "Value must not be less than *.";
305 const char JSONSchemaValidator::kNumberMaximum[] = 329 const char JSONSchemaValidator::kNumberMaximum[] =
306 "Value must not be greater than *."; 330 "Value must not be greater than *.";
307 const char JSONSchemaValidator::kInvalidType[] = 331 const char JSONSchemaValidator::kInvalidType[] =
308 "Expected '*' but got '*'."; 332 "Expected '*' but got '*'.";
309 const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] = 333 const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] =
310 "Expected 'integer' but got 'number', consider using Math.round()."; 334 "Expected 'integer' but got 'number', consider using Math.round().";
335 const char JSONSchemaValidator::kInvalidRegex[] =
336 "Regular expression /*/ is invalid: *";
311 337
312 338
313 // static 339 // static
314 std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) { 340 std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) {
315 switch (value->GetType()) { 341 switch (value->GetType()) {
316 case base::Value::TYPE_NULL: 342 case base::Value::TYPE_NULL:
317 return schema::kNull; 343 return schema::kNull;
318 case base::Value::TYPE_BOOLEAN: 344 case base::Value::TYPE_BOOLEAN:
319 return schema::kBoolean; 345 return schema::kBoolean;
320 case base::Value::TYPE_INTEGER: 346 case base::Value::TYPE_INTEGER:
(...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after
544 } 570 }
545 } 571 }
546 572
547 errors_.push_back(Error(path, kInvalidEnum)); 573 errors_.push_back(Error(path, kInvalidEnum));
548 } 574 }
549 575
550 void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance, 576 void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance,
551 const base::DictionaryValue* schema, 577 const base::DictionaryValue* schema,
552 const std::string& path) { 578 const std::string& path) {
553 const base::DictionaryValue* properties = NULL; 579 const base::DictionaryValue* properties = NULL;
554 schema->GetDictionary(schema::kProperties, &properties); 580 if (schema->GetDictionary(schema::kProperties, &properties)) {
555 if (properties) {
556 for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd(); 581 for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd();
557 it.Advance()) { 582 it.Advance()) {
558 std::string prop_path = path.empty() ? it.key() : (path + "." + it.key()); 583 std::string prop_path = path.empty() ? it.key() : (path + "." + it.key());
559 const base::DictionaryValue* prop_schema = NULL; 584 const base::DictionaryValue* prop_schema = NULL;
560 CHECK(it.value().GetAsDictionary(&prop_schema)); 585 CHECK(it.value().GetAsDictionary(&prop_schema));
561 586
562 const base::Value* prop_value = NULL; 587 const base::Value* prop_value = NULL;
563 if (instance->Get(it.key(), &prop_value)) { 588 if (instance->Get(it.key(), &prop_value)) {
564 Validate(prop_value, prop_schema, prop_path); 589 Validate(prop_value, prop_schema, prop_path);
565 } else { 590 } else {
566 // Properties are required unless there is an optional field set to 591 // Properties are required unless there is an optional field set to
567 // 'true'. 592 // 'true'.
568 bool is_optional = false; 593 bool is_optional = false;
569 prop_schema->GetBoolean(schema::kOptional, &is_optional); 594 prop_schema->GetBoolean(schema::kOptional, &is_optional);
570 if (!is_optional) { 595 if (!is_optional) {
571 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); 596 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired));
572 } 597 }
573 } 598 }
574 } 599 }
575 } 600 }
576 601
577 const base::DictionaryValue* additional_properties_schema = NULL; 602 const base::DictionaryValue* additional_properties_schema = NULL;
578 if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema)) 603 bool allow_any_additional_properties =
579 return; 604 SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema);
580 605
581 // Validate additional properties. 606 const base::DictionaryValue* pattern_properties = NULL;
607 ScopedVector<re2::RE2> pattern_properties_pattern;
608 std::vector<const base::DictionaryValue*> pattern_properties_schema;
609
610 if (schema->GetDictionary(schema::kPatternProperties, &pattern_properties)) {
611 for (base::DictionaryValue::Iterator it(*pattern_properties); !it.IsAtEnd();
612 it.Advance()) {
613 re2::RE2* prop_pattern = new re2::RE2(it.key());
614 if (!prop_pattern->ok()) {
615 LOG(WARNING) << "Regular expression /" << it.key()
616 << "/ is invalid: " << prop_pattern->error() << ".";
617 errors_.push_back(
618 Error(path,
619 FormatErrorMessage(
620 kInvalidRegex, it.key(), prop_pattern->error())));
621 continue;
622 }
623 const base::DictionaryValue* prop_schema = NULL;
624 CHECK(it.value().GetAsDictionary(&prop_schema));
625 pattern_properties_pattern.push_back(prop_pattern);
626 pattern_properties_schema.push_back(prop_schema);
627 }
628 }
629
630 // Validate pattern properties and additional properties.
582 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); 631 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd();
583 it.Advance()) { 632 it.Advance()) {
584 if (properties && properties->HasKey(it.key())) 633 std::string prop_path = path.empty() ? it.key() : path + "." + it.key();
634
635 bool found_matching_pattern = false;
636 for (size_t index = 0; index < pattern_properties_pattern.size(); ++index) {
637 if (re2::RE2::PartialMatch(it.key(),
638 *pattern_properties_pattern[index])) {
639 found_matching_pattern = true;
640 Validate(&it.value(), pattern_properties_schema[index], prop_path);
641 break;
642 }
643 }
644
645 if (found_matching_pattern || allow_any_additional_properties ||
646 (properties && properties->HasKey(it.key())))
585 continue; 647 continue;
586 648
587 std::string prop_path = path.empty() ? it.key() : path + "." + it.key();
588 if (!additional_properties_schema) { 649 if (!additional_properties_schema) {
589 errors_.push_back(Error(prop_path, kUnexpectedProperty)); 650 errors_.push_back(Error(prop_path, kUnexpectedProperty));
590 } else { 651 } else {
591 Validate(&it.value(), additional_properties_schema, prop_path); 652 Validate(&it.value(), additional_properties_schema, prop_path);
592 } 653 }
593 } 654 }
594 } 655 }
595 656
596 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, 657 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance,
597 const base::DictionaryValue* schema, 658 const base::DictionaryValue* schema,
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
700 761
701 int max_length = 0; 762 int max_length = 0;
702 if (schema->GetInteger(schema::kMaxLength, &max_length)) { 763 if (schema->GetInteger(schema::kMaxLength, &max_length)) {
703 CHECK(max_length >= 0); 764 CHECK(max_length >= 0);
704 if (value.size() > static_cast<size_t>(max_length)) { 765 if (value.size() > static_cast<size_t>(max_length)) {
705 errors_.push_back(Error(path, FormatErrorMessage( 766 errors_.push_back(Error(path, FormatErrorMessage(
706 kStringMaxLength, base::IntToString(max_length)))); 767 kStringMaxLength, base::IntToString(max_length))));
707 } 768 }
708 } 769 }
709 770
710 CHECK(!schema->HasKey(schema::kPattern)) << "Pattern is not supported."; 771 std::string pattern;
772 if (schema->GetString(schema::kPattern, &pattern)) {
773 re2::RE2 compiled_regex(pattern);
774 if (!compiled_regex.ok()) {
775 LOG(WARNING) << "Regular expression /" << pattern
776 << "/ is invalid: " << compiled_regex.error() << ".";
777 errors_.push_back(Error(
778 path,
779 FormatErrorMessage(kInvalidRegex, pattern, compiled_regex.error())));
780 } else if (!re2::RE2::PartialMatch(value, compiled_regex)) {
781 errors_.push_back(
782 Error(path, FormatErrorMessage(kStringPattern, pattern)));
783 }
784 }
711 } 785 }
712 786
713 void JSONSchemaValidator::ValidateNumber(const base::Value* instance, 787 void JSONSchemaValidator::ValidateNumber(const base::Value* instance,
714 const base::DictionaryValue* schema, 788 const base::DictionaryValue* schema,
715 const std::string& path) { 789 const std::string& path) {
716 double value = GetNumberValue(instance); 790 double value = GetNumberValue(instance);
717 791
718 // TODO(aa): It would be good to test that the double is not infinity or nan, 792 // 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. 793 // but isnan and isinf aren't defined on Windows.
720 794
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
761 835
762 if (*additional_properties_schema) { 836 if (*additional_properties_schema) {
763 std::string additional_properties_type(schema::kAny); 837 std::string additional_properties_type(schema::kAny);
764 CHECK((*additional_properties_schema)->GetString( 838 CHECK((*additional_properties_schema)->GetString(
765 schema::kType, &additional_properties_type)); 839 schema::kType, &additional_properties_type));
766 return additional_properties_type == schema::kAny; 840 return additional_properties_type == schema::kAny;
767 } else { 841 } else {
768 return default_allow_additional_properties_; 842 return default_allow_additional_properties_;
769 } 843 }
770 } 844 }
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