Commit f55e87d6 authored by Karandeep Bhatia's avatar Karandeep Bhatia Committed by Commit Bot

JsonSchemaCompiler: Better error generation for array parse failures.

This CL improves the error messages for array parse failures in
generated code.

BUG=1113513

Change-Id: Ic1edfab5e005f363b8670b1f798a241673b856ea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2520799Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Commit-Queue: Karan Bhatia <karandeepb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827471}
parent e896fe0f
...@@ -40,8 +40,9 @@ TEST_F(ContentScriptsManifestTest, MatchPattern) { ...@@ -40,8 +40,9 @@ TEST_F(ContentScriptsManifestTest, MatchPattern) {
// Match paterns must be strings. // Match paterns must be strings.
Testcase("content_script_match_pattern_not_string.json", Testcase("content_script_match_pattern_not_string.json",
"Error at key 'content_scripts'. Parsing array failed: expected " "Error at key 'content_scripts'. Parsing array failed at index "
"string, got integer; unable to populate array 'matches'.")}; "0: Error at key 'matches': Parsing array failed at index 0: "
"expected string, got integer")};
RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR); RunTestcases(testcases, base::size(testcases), EXPECT_TYPE_ERROR);
LoadAndExpectSuccess("ports_in_content_scripts.json"); LoadAndExpectSuccess("ports_in_content_scripts.json");
......
...@@ -21,8 +21,8 @@ TEST_F(ExcludeMatchesManifestTest, ExcludeMatchPatterns) { ...@@ -21,8 +21,8 @@ TEST_F(ExcludeMatchesManifestTest, ExcludeMatchPatterns) {
Testcase testcases2[] = { Testcase testcases2[] = {
Testcase("exclude_matches_not_list.json", Testcase("exclude_matches_not_list.json",
"Error at key 'content_scripts'. Parsing array failed: " "Error at key 'content_scripts'. Parsing array failed at index "
"'exclude_matches': expected list, got string."), "0: 'exclude_matches': expected list, got string"),
Testcase("exclude_matches_invalid_host.json", Testcase("exclude_matches_invalid_host.json",
"Invalid value for 'content_scripts[0].exclude_matches[0]': " "Invalid value for 'content_scripts[0].exclude_matches[0]': "
"Invalid host wildcard.")}; "Invalid host wildcard.")};
......
...@@ -52,37 +52,40 @@ TEST_F(InitValueManifestTest, InitFromValueInvalid) { ...@@ -52,37 +52,40 @@ TEST_F(InitValueManifestTest, InitFromValueInvalid) {
"Error at key 'content_scripts'. Type is invalid. Expected " "Error at key 'content_scripts'. Type is invalid. Expected "
"list, found integer."), "list, found integer."),
Testcase("init_invalid_script_item_invalid.json", Testcase("init_invalid_script_item_invalid.json",
"Error at key 'content_scripts'. Parsing array failed: expected " "Error at key 'content_scripts'. Parsing array failed at index "
"dictionary, got integer."), "0: expected dictionary, got integer"),
Testcase("init_invalid_script_matches_missing.json", Testcase("init_invalid_script_matches_missing.json",
"Error at key 'content_scripts'. Parsing array failed: " "Error at key 'content_scripts'. Parsing array failed at index "
"'matches' is required."), "0: 'matches' is required"),
Testcase("init_invalid_script_matches_invalid.json", Testcase("init_invalid_script_matches_invalid.json",
"Error at key 'content_scripts'. Parsing array failed: " "Error at key 'content_scripts'. Parsing array failed at index "
"'matches': expected list, got integer."), "0: 'matches': expected list, got integer"),
Testcase("init_invalid_script_matches_empty.json", Testcase("init_invalid_script_matches_empty.json",
errors::kInvalidMatchCount), errors::kInvalidMatchCount),
Testcase("init_invalid_script_match_item_invalid.json", Testcase("init_invalid_script_match_item_invalid.json",
"Error at key 'content_scripts'. Parsing array failed: expected " "Error at key 'content_scripts'. Parsing array failed at index "
"string, got integer; unable to populate array 'matches'."), "0: Error at key 'matches': Parsing array failed at index 0: "
"expected string, got integer"),
Testcase("init_invalid_script_match_item_invalid_2.json", Testcase("init_invalid_script_match_item_invalid_2.json",
errors::kInvalidMatch), errors::kInvalidMatch),
Testcase("init_invalid_script_files_missing.json", errors::kMissingFile), Testcase("init_invalid_script_files_missing.json", errors::kMissingFile),
Testcase("init_invalid_files_js_invalid.json", Testcase("init_invalid_files_js_invalid.json",
"Error at key 'content_scripts'. Parsing array failed: 'js': " "Error at key 'content_scripts'. Parsing array failed at index "
"expected list, got integer."), "0: 'js': expected list, got integer"),
Testcase("init_invalid_files_empty.json", errors::kMissingFile), Testcase("init_invalid_files_empty.json", errors::kMissingFile),
Testcase("init_invalid_files_js_empty_css_missing.json", Testcase("init_invalid_files_js_empty_css_missing.json",
errors::kMissingFile), errors::kMissingFile),
Testcase("init_invalid_files_js_item_invalid.json", Testcase("init_invalid_files_js_item_invalid.json",
"Error at key 'content_scripts'. Parsing array failed: expected " "Error at key 'content_scripts'. Parsing array failed at index "
"string, got integer; unable to populate array 'js'."), "0: Error at key 'js': Parsing array failed at index 0: "
"expected string, got integer"),
Testcase("init_invalid_files_css_invalid.json", Testcase("init_invalid_files_css_invalid.json",
"Error at key 'content_scripts'. Parsing array failed: 'css': " "Error at key 'content_scripts'. Parsing array failed at index "
"expected list, got integer."), "0: 'css': expected list, got integer"),
Testcase("init_invalid_files_css_item_invalid.json", Testcase("init_invalid_files_css_item_invalid.json",
"Error at key 'content_scripts'. Parsing array failed: expected " "Error at key 'content_scripts'. Parsing array failed at index "
"string, got integer; unable to populate array 'css'."), "0: Error at key 'css': Parsing array failed at index 0: "
"expected string, got integer"),
Testcase("init_invalid_permissions_invalid.json", Testcase("init_invalid_permissions_invalid.json",
errors::kInvalidPermissions), errors::kInvalidPermissions),
Testcase("init_invalid_host_permissions_invalid.json", Testcase("init_invalid_host_permissions_invalid.json",
......
...@@ -156,7 +156,7 @@ TEST_F(DNRManifestTest, InvalidRulesFileFormat) { ...@@ -156,7 +156,7 @@ TEST_F(DNRManifestTest, InvalidRulesFileFormat) {
LoadAndExpectError( LoadAndExpectError(
"Error at key 'declarative_net_request.rule_resources'. Parsing array " "Error at key 'declarative_net_request.rule_resources'. Parsing array "
"failed: expected dictionary, got string."); "failed at index 0: expected dictionary, got string");
} }
TEST_F(DNRManifestTest, ZeroRulesets) { TEST_F(DNRManifestTest, ZeroRulesets) {
......
...@@ -999,11 +999,21 @@ class _Generator(object): ...@@ -999,11 +999,21 @@ class _Generator(object):
failure_value, failure_value,
is_ptr=is_ptr)) is_ptr=is_ptr))
else: else:
c.Sblock('if (!%s(%s)) {' % ( args = ['*list', '&%(dst_var)s']
if self._generate_error_messages:
c.Append('base::string16 array_parse_error;')
args.append('&array_parse_error')
c.Append('if (!%s(%s)) {' % (
self._util_cc_helper.PopulateArrayFromListFunction(is_ptr), self._util_cc_helper.PopulateArrayFromListFunction(is_ptr),
self._GenerateArgs(('*list', '&%(dst_var)s')))) self._GenerateArgs(args, generate_error_messages=False)))
c.Concat(self._GenerateError( c.Sblock()
'"unable to populate array \'%%(key)s\'"')) if self._generate_error_messages:
c.Append(
'array_parse_error = base::UTF8ToUTF16("Error at key '
'\'%(key)s\': ") + array_parse_error;'
)
c.Concat(self._AppendError16('array_parse_error'))
c.Append('return %(failure_value)s;') c.Append('return %(failure_value)s;')
c.Eblock('}') c.Eblock('}')
c.Eblock('}') c.Eblock('}')
...@@ -1258,19 +1268,23 @@ class _Generator(object): ...@@ -1258,19 +1268,23 @@ class _Generator(object):
self._type_helper.GetEnumNoneValue(prop.type_))) self._type_helper.GetEnumNoneValue(prop.type_)))
return c return c
def _GenerateError(self, body): def _AppendError16(self, error16):
"""Generates an error message pertaining to population failure. """Appends the given |error16| expression/variable to |error|.
E.g 'expected bool, got int'
""" """
c = Code() c = Code()
if not self._generate_error_messages: if not self._generate_error_messages:
return c return c
(c.Append('if (error->length())') (c.Append('if (error->length())')
.Append(' error->append(UTF8ToUTF16("; "));') .Append(' error->append(UTF8ToUTF16("; "));')
.Append('error->append(UTF8ToUTF16(%s));' % body)) .Append('error->append(%s);' % error16))
return c return c
def _GenerateError(self, body):
"""Generates an error message pertaining to population failure.
E.g 'expected bool, got int'
"""
return self._AppendError16('UTF8ToUTF16(%s)' % body)
def _GenerateParams(self, params, generate_error_messages=None): def _GenerateParams(self, params, generate_error_messages=None):
"""Builds the parameter list for a function, given an array of parameters. """Builds the parameter list for a function, given an array of parameters.
If |generate_error_messages| is specified, it overrides If |generate_error_messages| is specified, it overrides
...@@ -1282,9 +1296,13 @@ class _Generator(object): ...@@ -1282,9 +1296,13 @@ class _Generator(object):
params = list(params) + ['base::string16* error'] params = list(params) + ['base::string16* error']
return ', '.join(str(p) for p in params) return ', '.join(str(p) for p in params)
def _GenerateArgs(self, args): def _GenerateArgs(self, args, generate_error_messages=None):
"""Builds the argument list for a function, given an array of arguments. """Builds the argument list for a function, given an array of arguments.
If |generate_error_messages| is specified, it overrides
|self._generate_error_messages|.
""" """
if self._generate_error_messages: if generate_error_messages is None:
generate_error_messages = self._generate_error_messages
if generate_error_messages:
args = list(args) + ['error'] args = list(args) + ['error']
return ', '.join(str(a) for a in args) return ', '.join(str(a) for a in args)
...@@ -40,20 +40,6 @@ bool ParseHelper(const base::DictionaryValue& dict, ...@@ -40,20 +40,6 @@ bool ParseHelper(const base::DictionaryValue& dict,
} // namespace } // namespace
void PopulateArrayParseError(
base::StringPiece key,
base::string16* error,
std::vector<base::StringPiece>* error_path_reversed) {
DCHECK(error);
DCHECK(error_path_reversed);
DCHECK(!error->empty());
DCHECK(error_path_reversed->empty());
error_path_reversed->push_back(key);
*error = base::ASCIIToUTF16(base::StringPrintf(
"Parsing array failed: %s.", base::UTF16ToASCII(*error).c_str()));
}
void PopulateInvalidEnumValueError( void PopulateInvalidEnumValueError(
base::StringPiece key, base::StringPiece key,
const std::string& value, const std::string& value,
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include "base/check.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/strings/string_piece_forward.h" #include "base/strings/string_piece_forward.h"
#include "base/values.h" #include "base/values.h"
...@@ -17,14 +18,6 @@ namespace manifest_parse_util { ...@@ -17,14 +18,6 @@ namespace manifest_parse_util {
// This file contains helpers used by auto-generated manifest parsing code. // This file contains helpers used by auto-generated manifest parsing code.
// Populates |error| and |error_path_reversed| denoting array parse error at the
// given |key|. Note |error| should already contain the specific parse error for
// the array item.
void PopulateArrayParseError(
base::StringPiece key,
base::string16* error,
std::vector<base::StringPiece>* error_path_reversed);
// Populates |error| and |error_path_reversed| denoting the given invalid enum // Populates |error| and |error_path_reversed| denoting the given invalid enum
// |value| at the given |key|. // |value| at the given |key|.
void PopulateInvalidEnumValueError( void PopulateInvalidEnumValueError(
...@@ -109,8 +102,11 @@ bool ParseFromDictionary(const base::DictionaryValue& dict, ...@@ -109,8 +102,11 @@ bool ParseFromDictionary(const base::DictionaryValue& dict,
bool result = json_schema_compiler::util::PopulateArrayFromList( bool result = json_schema_compiler::util::PopulateArrayFromList(
value->AsListValue(*value), out_ptr, error); value->AsListValue(*value), out_ptr, error);
if (!result) if (!result) {
PopulateArrayParseError(key, error, error_path_reversed); DCHECK(error_path_reversed);
DCHECK(error_path_reversed->empty());
error_path_reversed->push_back(key);
}
return result; return result;
} }
......
...@@ -174,7 +174,8 @@ TEST(JsonSchemaCompilerErrorTest, UnableToPopulateArray) { ...@@ -174,7 +174,8 @@ TEST(JsonSchemaCompilerErrorTest, UnableToPopulateArray) {
std::unique_ptr<base::ListValue> params_value = std::unique_ptr<base::ListValue> params_value =
List(std::make_unique<Value>(5), std::make_unique<Value>(false)); List(std::make_unique<Value>(5), std::make_unique<Value>(false));
EXPECT_TRUE(EqualsUtf16( EXPECT_TRUE(EqualsUtf16(
"expected integer, got boolean; unable to populate array 'integers'", "Error at key 'integers': Parsing array failed at index 1: expected "
"integer, got boolean",
GetPopulateError<errors::ChoiceType::Integers>(*params_value))); GetPopulateError<errors::ChoiceType::Integers>(*params_value)));
} }
} }
...@@ -300,9 +301,10 @@ TEST(JsonSchemaCompilerErrorTest, OptionalUnableToPopulateArray) { ...@@ -300,9 +301,10 @@ TEST(JsonSchemaCompilerErrorTest, OptionalUnableToPopulateArray) {
base::string16 error; base::string16 error;
EXPECT_FALSE(errors::OptionalChoiceType::Integers::Populate(*params_value, EXPECT_FALSE(errors::OptionalChoiceType::Integers::Populate(*params_value,
&out, &error)); &out, &error));
EXPECT_TRUE(EqualsUtf16( EXPECT_TRUE(
"expected integer, got boolean; unable to populate array 'integers'", EqualsUtf16("Error at key 'integers': Parsing array failed at index 1: "
error)); "expected integer, got boolean",
error));
EXPECT_EQ(NULL, out.as_integer.get()); EXPECT_EQ(NULL, out.as_integer.get());
} }
} }
......
...@@ -275,8 +275,8 @@ TEST(JsonSchemaCompilerSimpleTest, ManifestKeyParsing_ArrayParseError) { ...@@ -275,8 +275,8 @@ TEST(JsonSchemaCompilerSimpleTest, ManifestKeyParsing_ArrayParseError) {
std::string error; std::string error;
ASSERT_NO_FATAL_FAILURE(GetManifestParseError(kPartialManifestJson, &error)); ASSERT_NO_FATAL_FAILURE(GetManifestParseError(kPartialManifestJson, &error));
EXPECT_EQ( EXPECT_EQ(
"Error at key 'key_ref.array'. Parsing array failed: expected string, " "Error at key 'key_ref.array'. Parsing array failed at index 2: expected "
"got integer.", "string, got integer",
error); error);
} }
......
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/format_macros.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h" #include "base/values.h"
namespace json_schema_compiler { namespace json_schema_compiler {
...@@ -83,14 +86,22 @@ bool PopulateArrayFromList(const base::ListValue& list, std::vector<T>* out) { ...@@ -83,14 +86,22 @@ bool PopulateArrayFromList(const base::ListValue& list, std::vector<T>* out) {
// Populates |out| with |list|. Returns false and sets |error| if there is no // Populates |out| with |list|. Returns false and sets |error| if there is no
// list at the specified key or if the list has anything other than |T|. // list at the specified key or if the list has anything other than |T|.
template <class T> template <class T>
bool PopulateArrayFromList(const base::ListValue& list, bool PopulateArrayFromList(const base::ListValue& list_value,
std::vector<T>* out, std::vector<T>* out,
base::string16* error) { base::string16* error) {
out->clear(); out->clear();
T item; T item;
for (const auto& value : list) { base::string16 item_error;
if (!PopulateItem(value, &item, error)) const auto& list = list_value.GetList();
for (size_t i = 0; i < list.size(); ++i) {
if (!PopulateItem(list[i], &item, &item_error)) {
if (!error->empty())
error->append(base::ASCIIToUTF16("; "));
error->append(base::ASCIIToUTF16(
base::StringPrintf("Parsing array failed at index %" PRIuS ": %s", i,
base::UTF16ToASCII(item_error).c_str())));
return false; return false;
}
out->push_back(std::move(item)); out->push_back(std::move(item));
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment