| // 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(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 |