| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/lib/json_parser/json_parser.h" |
| |
| #include <fcntl.h> |
| #include <stdio.h> |
| |
| #include <functional> |
| #include <string> |
| |
| #include <gtest/gtest.h> |
| #include <rapidjson/document.h> |
| |
| #include <fbl/unique_fd.h> |
| #include "lib/fit/function.h" |
| #include "src/lib/files/file.h" |
| #include "src/lib/files/path.h" |
| #include "src/lib/files/scoped_temp_dir.h" |
| #include "src/lib/fxl/strings/concatenate.h" |
| #include "src/lib/fxl/strings/substitute.h" |
| |
| namespace json_parser { |
| namespace { |
| |
| class JSONParserTest : public ::testing::Test { |
| protected: |
| // ExpectFailedParse() will replace '$0' with the JSON filename, if present. |
| void ExpectFailedParse(JSONParser* parser, const std::string& json, std::string expected_error) { |
| props_found_ = 0; |
| const std::string json_file = NewJSONFile(json); |
| std::string error; |
| EXPECT_FALSE(ParseFromFile(parser, json_file, &error)); |
| EXPECT_EQ(error, fxl::Substitute(expected_error, json_file)); |
| EXPECT_EQ(0, props_found_); |
| } |
| |
| bool ParseFromFile(JSONParser* parser, const std::string& file, std::string* error) { |
| rapidjson::Document document = parser->ParseFromFile(file); |
| if (!parser->HasError()) { |
| InterpretDocument(parser, std::move(document)); |
| } |
| *error = parser->error_str(); |
| return !parser->HasError(); |
| } |
| |
| bool ParseFromFileAt(JSONParser* parser, int dirfd, const std::string& file, std::string* error) { |
| rapidjson::Document document = parser->ParseFromFileAt(dirfd, file); |
| if (!parser->HasError()) { |
| InterpretDocument(parser, std::move(document)); |
| } |
| *error = parser->error_str(); |
| return !parser->HasError(); |
| } |
| |
| bool ParseFromDirectory(JSONParser* parser, const std::string& dir, std::string* error) { |
| fit::function<void(rapidjson::Document)> cb = |
| std::bind(&JSONParserTest::InterpretDocument, this, parser, std::placeholders::_1); |
| parser->ParseFromDirectory(dir, std::move(cb)); |
| *error = parser->error_str(); |
| return !parser->HasError(); |
| } |
| |
| bool ParseFromDirectoryAt(fbl::unique_fd& dir_fd, JSONParser* parser, |
| const std::string& dir, std::string* error) { |
| fit::function<void(rapidjson::Document)> cb = |
| std::bind(&JSONParserTest::InterpretDocument, this, parser, std::placeholders::_1); |
| parser->ParseFromDirectoryAt(dir_fd.get(), dir, std::move(cb)); |
| *error = parser->error_str(); |
| return !parser->HasError(); |
| } |
| |
| std::string NewJSONFile(const std::string& json) { |
| std::string json_file; |
| if (!tmp_dir_.NewTempFileWithData(json, &json_file)) { |
| return ""; |
| } |
| return json_file; |
| } |
| |
| std::string NewJSONFileInDir(const std::string& dir, const std::string& json) { |
| const std::string json_file = "json_file" + std::to_string(unique_id_++); |
| if (!files::WriteFile(dir + "/" + json_file, json.data(), json.size())) { |
| return ""; |
| } |
| return json_file; |
| } |
| |
| void InterpretDocument(JSONParser* parser, rapidjson::Document document) { |
| if (!document.IsObject()) { |
| parser->ReportError("Document is not an object."); |
| return; |
| } |
| |
| auto prop1 = document.FindMember("prop1"); |
| if (prop1 == document.MemberEnd()) { |
| // Allow missing. |
| } else if (!prop1->value.IsString()) { |
| parser->ReportError("prop1 has wrong type"); |
| } else { |
| ++props_found_; |
| } |
| |
| auto prop2 = document.FindMember("prop2"); |
| if (prop2 == document.MemberEnd()) { |
| // Allow missing. |
| } else if (!prop2->value.IsInt()) { |
| parser->ReportError("prop2 has wrong type"); |
| } else { |
| ++props_found_; |
| } |
| } |
| |
| files::ScopedTempDir tmp_dir_; |
| int props_found_ = 0; |
| |
| private: |
| int unique_id_ = 1; |
| }; |
| |
| TEST_F(JSONParserTest, ReadInvalidFile) { |
| const std::string invalid_path = fxl::Substitute("$0/does_not_exist", tmp_dir_.path()); |
| std::string error; |
| JSONParser parser; |
| EXPECT_FALSE(ParseFromFile(&parser, invalid_path, &error)); |
| EXPECT_EQ(error, fxl::Substitute("Failed to read file: $0", invalid_path)); |
| } |
| |
| TEST_F(JSONParserTest, ParseWithErrors) { |
| std::string json; |
| |
| // One error, in parsing. |
| { |
| const std::string json = R"JSON({ |
| "prop1": "missing closing quote, |
| "prop2": 42 |
| })JSON"; |
| JSONParser parser; |
| ExpectFailedParse(&parser, json, "$0:2:35: Invalid encoding in string."); |
| } |
| |
| // Multiple errors, after parsing. |
| { |
| const std::string json = R"JSON({ |
| "prop1": 42, |
| "prop2": "wrong_type" |
| })JSON"; |
| JSONParser parser; |
| ExpectFailedParse(&parser, json, "$0: prop1 has wrong type\n$0: prop2 has wrong type"); |
| } |
| } |
| |
| TEST_F(JSONParserTest, ParseFromString) { |
| const std::string json = R"JSON({ |
| "prop1": "missing closing quote |
| })JSON"; |
| JSONParser parser; |
| parser.ParseFromString(json, "test_file"); |
| EXPECT_TRUE(parser.HasError()); |
| EXPECT_EQ(parser.error_str(), "test_file:2:34: Invalid encoding in string."); |
| } |
| |
| TEST_F(JSONParserTest, ParseTwice) { |
| std::string json; |
| JSONParser parser; |
| |
| // Two failed parses. Errors should accumulate. |
| json = R"JSON({ |
| "prop1": invalid_value, |
| })JSON"; |
| parser.ParseFromString(json, "test_file"); |
| |
| json = R"JSON({ |
| "prop1": "missing closing quote |
| })JSON"; |
| parser.ParseFromString(json, "test_file"); |
| |
| EXPECT_TRUE(parser.HasError()); |
| EXPECT_EQ(parser.error_str(), |
| "test_file:2:12: Invalid value.\n" |
| "test_file:2:34: Invalid encoding in string."); |
| EXPECT_EQ(0, props_found_); |
| } |
| |
| TEST_F(JSONParserTest, ParseValid) { |
| const std::string json = R"JSON({ |
| "prop1": "foo", |
| "prop2": 42 |
| })JSON"; |
| const std::string file = NewJSONFile(json); |
| std::string error; |
| JSONParser parser; |
| EXPECT_TRUE(ParseFromFile(&parser, file, &error)); |
| EXPECT_EQ("", error); |
| EXPECT_EQ(2, props_found_); |
| } |
| |
| TEST_F(JSONParserTest, ParseFromFileAt) { |
| const std::string json = R"JSON({ |
| "prop1": "foo", |
| "prop2": 42 |
| })JSON"; |
| const std::string file = NewJSONFile(json); |
| const std::string basename = files::GetBaseName(file); |
| const int dirfd = open(tmp_dir_.path().c_str(), O_RDONLY); |
| ASSERT_GT(dirfd, 0); |
| |
| std::string error; |
| JSONParser parser; |
| EXPECT_TRUE(ParseFromFileAt(&parser, dirfd, basename, &error)); |
| EXPECT_EQ("", error); |
| EXPECT_EQ(2, props_found_); |
| } |
| |
| TEST_F(JSONParserTest, ParseFromDirectory) { |
| const std::string json1 = R"JSON({ |
| "prop1": "foo" |
| })JSON"; |
| const std::string json2 = R"JSON({ |
| "prop2": 42 |
| })JSON"; |
| std::string dir; |
| ASSERT_TRUE(tmp_dir_.NewTempDir(&dir)); |
| NewJSONFileInDir(dir, json1); |
| NewJSONFileInDir(dir, json2); |
| |
| std::string error; |
| JSONParser parser; |
| EXPECT_TRUE(ParseFromDirectory(&parser, dir, &error)); |
| EXPECT_EQ("", error); |
| EXPECT_EQ(2, props_found_); |
| } |
| |
| TEST_F(JSONParserTest, ParseFromDirectoryWithErrors) { |
| const std::string json1 = R"JSON({,,,})JSON"; |
| const std::string json2 = R"JSON({ |
| "prop2": 42 |
| })JSON"; |
| std::string dir; |
| ASSERT_TRUE(tmp_dir_.NewTempDir(&dir)); |
| const std::string json_file1 = NewJSONFileInDir(dir, json1); |
| NewJSONFileInDir(dir, json2); |
| |
| // Parsing should continue even when one file fails to parse. |
| std::string error; |
| JSONParser parser; |
| EXPECT_FALSE(ParseFromDirectory(&parser, dir, &error)); |
| EXPECT_EQ(error, fxl::Concatenate({json_file1, ":1:2: Missing a name for object member."})); |
| EXPECT_EQ(1, props_found_); |
| } |
| |
| TEST_F(JSONParserTest, ParseFromDirectoryDoesNotExist) { |
| std::string dir = "do_not_exist"; |
| |
| JSONParser parser; |
| std::string error; |
| EXPECT_TRUE(ParseFromDirectory(&parser, dir, &error)); |
| EXPECT_EQ("", error); |
| EXPECT_EQ(0, props_found_); |
| } |
| |
| TEST_F(JSONParserTest, ParseFromDirectoryAt) { |
| const std::string json1 = R"JSON({ |
| "prop1": "foo" |
| })JSON"; |
| const std::string json2 = R"JSON({ |
| "prop2": 42 |
| })JSON"; |
| std::string dir; |
| ASSERT_TRUE(tmp_dir_.NewTempDir(&dir)); |
| NewJSONFileInDir(dir, json1); |
| NewJSONFileInDir(dir, json2); |
| |
| fbl::unique_fd dirfd(open(dir.c_str(), O_RDONLY)); |
| std::string error; |
| JSONParser parser; |
| EXPECT_TRUE(ParseFromDirectoryAt(dirfd, &parser, ".", &error)); |
| EXPECT_EQ("", error); |
| EXPECT_EQ(2, props_found_); |
| } |
| |
| TEST_F(JSONParserTest, CopyArrayToVectorNonArray) { |
| const std::string json = R"JSON({ |
| "foo": 0 |
| })JSON"; |
| JSONParser parser; |
| auto document = parser.ParseFromString(json, "test_file"); |
| ASSERT_FALSE(parser.HasError()); |
| std::vector<std::string> vec = {"foo"}; |
| parser.CopyStringArray("foo", document["foo"], &vec); |
| EXPECT_TRUE(parser.HasError()); |
| EXPECT_EQ(parser.error_str(), "test_file: 'foo' is not an array."); |
| EXPECT_EQ(vec.size(), 0u); |
| } |
| |
| TEST_F(JSONParserTest, CopyArrayToVectorNonString) { |
| const std::string json = R"JSON({ |
| "bar": [ "1", 2, "3" ] |
| })JSON"; |
| JSONParser parser; |
| auto document = parser.ParseFromString(json, "test_file"); |
| ASSERT_FALSE(parser.HasError()); |
| std::vector<std::string> vec = {"foo"}; |
| parser.CopyStringArray("bar", document["bar"], &vec); |
| EXPECT_TRUE(parser.HasError()); |
| EXPECT_EQ(parser.error_str(), "test_file: 'bar' contains an item that's not a string"); |
| EXPECT_EQ(vec.size(), 0u); |
| } |
| |
| TEST_F(JSONParserTest, CopyArrayToVectorEmpty) { |
| const std::string json = R"JSON({ |
| "baz": [] |
| })JSON"; |
| JSONParser parser; |
| auto document = parser.ParseFromString(json, "test_file"); |
| ASSERT_FALSE(parser.HasError()); |
| std::vector<std::string> vec = {"foo"}; |
| parser.CopyStringArray("baz", document["baz"], &vec); |
| EXPECT_FALSE(parser.HasError()); |
| EXPECT_EQ(vec.size(), 0u); |
| } |
| |
| TEST_F(JSONParserTest, CopyArrayToVector) { |
| const std::string json = R"JSON({ |
| "qux": [ "quux", "quuz" ] |
| })JSON"; |
| JSONParser parser; |
| auto document = parser.ParseFromString(json, "test_file"); |
| ASSERT_FALSE(parser.HasError()); |
| std::vector<std::string> vec = {"foo"}; |
| parser.CopyStringArray("qux", document["qux"], &vec); |
| EXPECT_FALSE(parser.HasError()); |
| EXPECT_EQ(vec.size(), 2u); |
| EXPECT_EQ(vec[0], "quux"); |
| EXPECT_EQ(vec[1], "quuz"); |
| } |
| |
| } // namespace |
| } // namespace json_parser |