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

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: fix a mistake 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 valid
209 // regular expression to a schema.
210 if (it.key() == schema::kPatternProperties) {
211 it.value().GetAsDictionary(&dictionary_value);
212 for (base::DictionaryValue::Iterator iter(*dictionary_value);
213 !iter.IsAtEnd(); iter.Advance()) {
214 if (!iter.value().GetAsDictionary(&dictionary_value)) {
215 *error = "patternProperty must be a dictionary";
Joao da Silva 2014/03/21 10:15:16 patternProperties
binjin 2014/03/21 14:57:43 Done.
216 return false;
217 }
218 if (!IsValidSchema(dictionary_value, options, error)) {
196 DCHECK(!error->empty()); 219 DCHECK(!error->empty());
197 return false; 220 return false;
198 } 221 }
199 } 222 }
200 } 223 }
201 224
202 // Validate "additionalProperties" attribute, which is a schema. 225 // Validate "additionalProperties" attribute, which is a schema.
203 if (it.key() == schema::kAdditionalProperties) { 226 if (it.key() == schema::kAdditionalProperties) {
204 it.value().GetAsDictionary(&dictionary_value); 227 it.value().GetAsDictionary(&dictionary_value);
205 if (!IsValidSchema(dictionary_value, options, error)) { 228 if (!IsValidSchema(dictionary_value, options, error)) {
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
292 "Array must have at least * items."; 315 "Array must have at least * items.";
293 const char JSONSchemaValidator::kArrayMaxItems[] = 316 const char JSONSchemaValidator::kArrayMaxItems[] =
294 "Array must not have more than * items."; 317 "Array must not have more than * items.";
295 const char JSONSchemaValidator::kArrayItemRequired[] = 318 const char JSONSchemaValidator::kArrayItemRequired[] =
296 "Item is required."; 319 "Item is required.";
297 const char JSONSchemaValidator::kStringMinLength[] = 320 const char JSONSchemaValidator::kStringMinLength[] =
298 "String must be at least * characters long."; 321 "String must be at least * characters long.";
299 const char JSONSchemaValidator::kStringMaxLength[] = 322 const char JSONSchemaValidator::kStringMaxLength[] =
300 "String must not be more than * characters long."; 323 "String must not be more than * characters long.";
301 const char JSONSchemaValidator::kStringPattern[] = 324 const char JSONSchemaValidator::kStringPattern[] =
302 "String must match the pattern: *."; 325 "String must match the pattern: *.";
Joao da Silva 2014/03/21 10:15:16 Change this to: "String doesn't match pattern /*/:
binjin 2014/03/21 14:57:43 I don't think the change is necessary, telling the
Joao da Silva 2014/03/21 15:06:40 sgtm
303 const char JSONSchemaValidator::kNumberMinimum[] = 326 const char JSONSchemaValidator::kNumberMinimum[] =
304 "Value must not be less than *."; 327 "Value must not be less than *.";
305 const char JSONSchemaValidator::kNumberMaximum[] = 328 const char JSONSchemaValidator::kNumberMaximum[] =
306 "Value must not be greater than *."; 329 "Value must not be greater than *.";
307 const char JSONSchemaValidator::kInvalidType[] = 330 const char JSONSchemaValidator::kInvalidType[] =
308 "Expected '*' but got '*'."; 331 "Expected '*' but got '*'.";
309 const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] = 332 const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] =
310 "Expected 'integer' but got 'number', consider using Math.round()."; 333 "Expected 'integer' but got 'number', consider using Math.round().";
334 const char JSONSchemaValidator::kInvalidRegex[] =
335 "Regular expression /*/ in Schema is invalid.";
Joao da Silva 2014/03/21 10:15:16 Change this to: "Regular expression /*/ is invalid
binjin 2014/03/21 14:57:43 Done. As a note I don't plan to add tests for this
311 336
312 337
313 // static 338 // static
314 std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) { 339 std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) {
315 switch (value->GetType()) { 340 switch (value->GetType()) {
316 case base::Value::TYPE_NULL: 341 case base::Value::TYPE_NULL:
317 return schema::kNull; 342 return schema::kNull;
318 case base::Value::TYPE_BOOLEAN: 343 case base::Value::TYPE_BOOLEAN:
319 return schema::kBoolean; 344 return schema::kBoolean;
320 case base::Value::TYPE_INTEGER: 345 case base::Value::TYPE_INTEGER:
(...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after
544 } 569 }
545 } 570 }
546 571
547 errors_.push_back(Error(path, kInvalidEnum)); 572 errors_.push_back(Error(path, kInvalidEnum));
548 } 573 }
549 574
550 void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance, 575 void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance,
551 const base::DictionaryValue* schema, 576 const base::DictionaryValue* schema,
552 const std::string& path) { 577 const std::string& path) {
553 const base::DictionaryValue* properties = NULL; 578 const base::DictionaryValue* properties = NULL;
554 schema->GetDictionary(schema::kProperties, &properties); 579 if (schema->GetDictionary(schema::kProperties, &properties)) {
555 if (properties) {
556 for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd(); 580 for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd();
557 it.Advance()) { 581 it.Advance()) {
558 std::string prop_path = path.empty() ? it.key() : (path + "." + it.key()); 582 std::string prop_path = path.empty() ? it.key() : (path + "." + it.key());
559 const base::DictionaryValue* prop_schema = NULL; 583 const base::DictionaryValue* prop_schema = NULL;
560 CHECK(it.value().GetAsDictionary(&prop_schema)); 584 CHECK(it.value().GetAsDictionary(&prop_schema));
561 585
562 const base::Value* prop_value = NULL; 586 const base::Value* prop_value = NULL;
563 if (instance->Get(it.key(), &prop_value)) { 587 if (instance->Get(it.key(), &prop_value)) {
564 Validate(prop_value, prop_schema, prop_path); 588 Validate(prop_value, prop_schema, prop_path);
565 } else { 589 } else {
566 // Properties are required unless there is an optional field set to 590 // Properties are required unless there is an optional field set to
567 // 'true'. 591 // 'true'.
568 bool is_optional = false; 592 bool is_optional = false;
569 prop_schema->GetBoolean(schema::kOptional, &is_optional); 593 prop_schema->GetBoolean(schema::kOptional, &is_optional);
570 if (!is_optional) { 594 if (!is_optional) {
571 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); 595 errors_.push_back(Error(prop_path, kObjectPropertyIsRequired));
572 } 596 }
573 } 597 }
574 } 598 }
575 } 599 }
576 600
601 // Allowing any additional items will ignore pattern properties as well.
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, FormatErrorMessage(kInvalidRegex, it.key())));
Joao da Silva 2014/03/21 10:15:16 Include prop_pattern->error() in this Error (see k
binjin 2014/03/21 14:57:43 Done.
619 }
620 const base::DictionaryValue* prop_schema = NULL;
621 CHECK(it.value().GetAsDictionary(&prop_schema));
622 pattern_properties_pattern.push_back(prop_pattern);
623 pattern_properties_schema.push_back(prop_schema);
624 }
625 }
626
627 // Validate pattern properties and additional properties.
582 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); 628 for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd();
583 it.Advance()) { 629 it.Advance()) {
584 if (properties && properties->HasKey(it.key())) 630 std::string prop_path = path.empty() ? it.key() : path + "." + it.key();
631
632 bool found_matching_pattern = false;
633 for (size_t index = 0; index < pattern_properties_pattern.size(); ++index) {
634 if (pattern_properties_pattern[index]->ok() &&
635 re2::RE2::PartialMatch(it.key(),
636 *pattern_properties_pattern[index])) {
637 found_matching_pattern = true;
638 Validate(&it.value(), pattern_properties_schema[index], prop_path);
639 break;
640 }
641 }
642
643 if (found_matching_pattern || allow_any_additional_properties ||
644 (properties && properties->HasKey(it.key())))
585 continue; 645 continue;
586 646
587 std::string prop_path = path.empty() ? it.key() : path + "." + it.key();
588 if (!additional_properties_schema) { 647 if (!additional_properties_schema) {
589 errors_.push_back(Error(prop_path, kUnexpectedProperty)); 648 errors_.push_back(Error(prop_path, kUnexpectedProperty));
590 } else { 649 } else {
591 Validate(&it.value(), additional_properties_schema, prop_path); 650 Validate(&it.value(), additional_properties_schema, prop_path);
592 } 651 }
593 } 652 }
594 } 653 }
595 654
596 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, 655 void JSONSchemaValidator::ValidateArray(const base::ListValue* instance,
597 const base::DictionaryValue* schema, 656 const base::DictionaryValue* schema,
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
700 759
701 int max_length = 0; 760 int max_length = 0;
702 if (schema->GetInteger(schema::kMaxLength, &max_length)) { 761 if (schema->GetInteger(schema::kMaxLength, &max_length)) {
703 CHECK(max_length >= 0); 762 CHECK(max_length >= 0);
704 if (value.size() > static_cast<size_t>(max_length)) { 763 if (value.size() > static_cast<size_t>(max_length)) {
705 errors_.push_back(Error(path, FormatErrorMessage( 764 errors_.push_back(Error(path, FormatErrorMessage(
706 kStringMaxLength, base::IntToString(max_length)))); 765 kStringMaxLength, base::IntToString(max_length))));
707 } 766 }
708 } 767 }
709 768
710 CHECK(!schema->HasKey(schema::kPattern)) << "Pattern is not supported."; 769 std::string pattern;
770 if (schema->GetString(schema::kPattern, &pattern)) {
771 re2::RE2 compiled_regex(pattern);
772 if (!compiled_regex.ok()) {
773 LOG(WARNING) << "Regular expression /" << pattern
774 << "/ is invalid: " << compiled_regex.error() << ".";
775 errors_.push_back(
776 Error(path, FormatErrorMessage(kInvalidRegex, pattern)));
Joao da Silva 2014/03/21 10:15:16 Include compiled_reger.error() in this Error too (
binjin 2014/03/21 14:57:43 Done.
777 } else if (!re2::RE2::PartialMatch(value, compiled_regex)) {
778 errors_.push_back(
779 Error(path, FormatErrorMessage(kStringPattern, pattern)));
Joao da Silva 2014/03/21 10:15:16 Include compiled_reger.error() in this Error too (
binjin 2014/03/21 14:57:43 Not changed, as mentioned above.
780 }
781 }
711 } 782 }
712 783
713 void JSONSchemaValidator::ValidateNumber(const base::Value* instance, 784 void JSONSchemaValidator::ValidateNumber(const base::Value* instance,
714 const base::DictionaryValue* schema, 785 const base::DictionaryValue* schema,
715 const std::string& path) { 786 const std::string& path) {
716 double value = GetNumberValue(instance); 787 double value = GetNumberValue(instance);
717 788
718 // TODO(aa): It would be good to test that the double is not infinity or nan, 789 // 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. 790 // but isnan and isinf aren't defined on Windows.
720 791
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
761 832
762 if (*additional_properties_schema) { 833 if (*additional_properties_schema) {
763 std::string additional_properties_type(schema::kAny); 834 std::string additional_properties_type(schema::kAny);
764 CHECK((*additional_properties_schema)->GetString( 835 CHECK((*additional_properties_schema)->GetString(
765 schema::kType, &additional_properties_type)); 836 schema::kType, &additional_properties_type));
766 return additional_properties_type == schema::kAny; 837 return additional_properties_type == schema::kAny;
767 } else { 838 } else {
768 return default_allow_additional_properties_; 839 return default_allow_additional_properties_;
769 } 840 }
770 } 841 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698