blob: cf0f557938f9bb3e8f927167fe3dd42cb4d21d75 [file] [log] [blame]
// 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