| // 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 <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| |
| #include <functional> |
| #include <string> |
| |
| #include <fbl/unique_fd.h> |
| #include <rapidjson/document.h> |
| #include <rapidjson/error/en.h> |
| |
| #include "lib/fit/function.h" |
| #include "src/lib/files/directory.h" |
| #include "src/lib/files/file.h" |
| #include "src/lib/fxl/macros.h" |
| #include "src/lib/fxl/strings/join_strings.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace json_parser { |
| namespace { |
| |
| using ErrorCallback = fit::function<void(size_t, const std::string&)>; |
| using fxl::StringPrintf; |
| |
| void GetLineAndColumnForOffset(const std::string& input, size_t offset, int32_t* output_line, |
| int32_t* output_column) { |
| if (offset == 0) { |
| // Errors at position 0 are assumed to be related to the whole file. |
| *output_line = 0; |
| *output_column = 0; |
| return; |
| } |
| *output_line = 1; |
| *output_column = 1; |
| for (size_t i = 0; i < input.size() && i < offset; i++) { |
| if (input[i] == '\n') { |
| *output_line += 1; |
| *output_column = 1; |
| } else { |
| *output_column += 1; |
| } |
| } |
| } |
| } // namespace |
| |
| rapidjson::Document JSONParser::ParseFromFile(const std::string& file) { |
| file_ = file; |
| std::string data; |
| if (!files::ReadFileToString(file, &data)) { |
| errors_.push_back(StringPrintf("Failed to read file: %s", file.c_str())); |
| return rapidjson::Document(); |
| } |
| return ParseFromString(data, file); |
| } |
| |
| rapidjson::Document JSONParser::ParseFromFileAt(int dirfd, const std::string& file) { |
| file_ = file; |
| std::string data; |
| if (!files::ReadFileToStringAt(dirfd, file, &data)) { |
| errors_.push_back(StringPrintf("Failed to read file: %s", file.c_str())); |
| return rapidjson::Document(); |
| } |
| return ParseFromString(data, file); |
| } |
| |
| rapidjson::Document JSONParser::ParseFromString(const std::string& data, const std::string& file) { |
| data_ = data; |
| file_ = file; |
| rapidjson::Document document; |
| document.Parse<rapidjson::kParseIterativeFlag>(data_); |
| if (document.HasParseError()) { |
| ReportErrorInternal(document.GetErrorOffset(), GetParseError_En(document.GetParseError())); |
| } |
| return document; |
| } |
| |
| void JSONParser::ParseFromDirectory(const std::string& path, |
| fit::function<void(rapidjson::Document)> cb) { |
| ParseFromDirectoryAt(AT_FDCWD, path, std::move(cb)); |
| } |
| |
| void JSONParser::ParseFromDirectoryAt(int dirfd, const std::string& path, |
| fit::function<void(rapidjson::Document)> cb) { |
| fbl::unique_fd dir_fd(openat(dirfd, path.c_str(), O_RDONLY | O_DIRECTORY)); |
| if (!dir_fd.is_valid()) { |
| // A non-existent directory is not an error. |
| return; |
| } |
| |
| std::vector<std::string> dir_entries; |
| if (!files::ReadDirContentsAt(dir_fd.get(), ".", &dir_entries)) { |
| ReportError(fxl::StringPrintf("Could not read directory contents from path %s error %s", |
| path.c_str(), strerror(errno))); |
| return; |
| } |
| for (const auto& entry : dir_entries) { |
| if (!files::IsFileAt(dir_fd.get(), entry)) |
| continue; |
| |
| rapidjson::Document document = ParseFromFileAt(dir_fd.get(), entry); |
| if (!document.HasParseError() && !document.IsNull()) { |
| cb(std::move(document)); |
| } |
| } |
| } |
| |
| void JSONParser::CopyStringArray(const std::string& name, const rapidjson::Value& value, |
| std::vector<std::string>* out) { |
| out->clear(); |
| if (!value.IsArray()) { |
| ReportError(fxl::StringPrintf("'%s' is not an array.", name.c_str())); |
| return; |
| } |
| for (const auto& entry : value.GetArray()) { |
| if (!entry.IsString()) { |
| ReportError(fxl::StringPrintf("'%s' contains an item that's not a string", name.c_str())); |
| out->clear(); |
| return; |
| } |
| out->push_back(entry.GetString()); |
| } |
| } |
| |
| void JSONParser::ReportError(const std::string& error) { ReportErrorInternal(0, error); } |
| |
| void JSONParser::ReportErrorInternal(size_t offset, const std::string& error) { |
| int32_t line; |
| int32_t column; |
| GetLineAndColumnForOffset(data_, offset, &line, &column); |
| if (line == 0) { |
| errors_.push_back(StringPrintf("%s: %s", file_.c_str(), error.c_str())); |
| } else { |
| errors_.push_back(StringPrintf("%s:%d:%d: %s", file_.c_str(), line, column, error.c_str())); |
| } |
| } |
| |
| bool JSONParser::HasError() const { return !errors_.empty(); } |
| |
| std::string JSONParser::error_str() const { return fxl::JoinStrings(errors_, "\n"); } |
| |
| } // namespace json_parser |