blob: 017aa9cd315e8c51062ba9a825db3d6472d31494 [file]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jsonpb/verify.h>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <absl/strings/str_cat.h>
#include <android-base/result.h>
#include <android-base/strings.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>
#include <google/protobuf/reflection.h>
#include <google/protobuf/util/json_util.h>
#include <json/reader.h>
#include <json/writer.h>
namespace android {
namespace jsonpb {
using google::protobuf::FieldDescriptor;
using google::protobuf::Message;
using google::protobuf::util::JsonPrintOptions;
// Return json_name of the field. If it is not set, return the name of the
// field.
std::string GetJsonName(const FieldDescriptor& field_descriptor) {
if (field_descriptor.has_json_name()) {
return std::string(field_descriptor.json_name());
}
return std::string(field_descriptor.name());
}
// Params:
// path: path to navigate inside JSON tree. For example, {"foo", "bar"}
// for the value "string" in {"foo": {"bar" : "string"}}
android::base::Result<void> AllFieldsAreKnown(const Message& message, const Json::Value& json,
std::vector<std::string>* path) {
if (!json.isObject()) {
return base::Error() << base::Join(*path, ".") << ": Not a JSON object";
}
auto&& descriptor = message.GetDescriptor();
auto json_members = json.getMemberNames();
std::set<std::string> json_keys{json_members.begin(), json_members.end()};
std::set<std::string> known_keys;
for (int i = 0; i < descriptor->field_count(); ++i) {
known_keys.insert(GetJsonName(*descriptor->field(i)));
}
std::set<std::string> unknown_keys;
std::set_difference(json_keys.begin(), json_keys.end(), known_keys.begin(), known_keys.end(),
std::inserter(unknown_keys, unknown_keys.begin()));
if (!unknown_keys.empty()) {
return base::Error()
<< base::Join(*path, ".") << ": contains unknown keys: ["
<< base::Join(unknown_keys, ", ") << "]. Keys must be a known field name of "
<< descriptor->full_name() << "(or its json_name option if set): ["
<< base::Join(known_keys, ", ") << "]";
}
std::stringstream errorss;
// Check message fields.
auto&& reflection = message.GetReflection();
std::vector<const FieldDescriptor*> set_field_descriptors;
reflection->ListFields(message, &set_field_descriptors);
for (auto&& field_descriptor : set_field_descriptors) {
if (field_descriptor->cpp_type() != FieldDescriptor::CppType::CPPTYPE_MESSAGE) {
continue;
}
if (field_descriptor->is_map()) {
continue;
}
std::string json_name = GetJsonName(*field_descriptor);
const Json::Value& json_value = json[json_name];
if (field_descriptor->is_repeated()) {
auto&& fields = reflection->GetRepeatedFieldRef<Message>(message, field_descriptor);
if (json_value.type() != Json::ValueType::arrayValue) {
errorss << base::Join(*path, ".") << ": not a JSON list. This should not happen.\n";
continue;
}
if (json_value.size() != static_cast<size_t>(fields.size())) {
errorss << base::Join(*path, ".") << ": JSON list has size " << json_value.size()
<< " but message has size " << fields.size() << ". This should not happen.\n";
continue;
}
std::unique_ptr<Message> scratch_space(fields.NewMessage());
for (int i = 0; i < fields.size(); ++i) {
path->push_back(absl::StrCat(json_name, "[", i, "]"));
auto res = AllFieldsAreKnown(fields.Get(i, scratch_space.get()), json_value[i], path);
path->pop_back();
if (!res.ok()) {
errorss << res.error().message();
}
}
} else {
auto&& field = reflection->GetMessage(message, field_descriptor);
path->push_back(json_name);
auto res = AllFieldsAreKnown(field, json_value, path);
path->pop_back();
if (!res.ok()) {
errorss << res.error().message();
}
}
}
if (errorss.tellp() > 0) {
return base::Error() << errorss.rdbuf();
}
return {};
}
android::base::Result<void> AllFieldsAreKnown(const google::protobuf::Message& message,
const std::string& json) {
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
Json::Value value;
std::string error;
if (!reader->parse(&*json.begin(), &*json.end(), &value, &error)) {
return base::Error() << error;
}
std::vector<std::string> json_tree_path{"<root>"};
return AllFieldsAreKnown(message, value, &json_tree_path);
}
android::base::Result<void> EqReformattedJson(const std::string& json,
google::protobuf::Message* scratch_space) {
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
Json::Value old_json;
std::string error;
if (!reader->parse(&*json.begin(), &*json.end(), &old_json, &error)) {
return base::Error() << error;
}
auto new_json_string = internal::FormatJson(json, scratch_space);
if (!new_json_string.ok()) {
return new_json_string.error();
}
Json::Value new_json;
if (!reader->parse(&*new_json_string->begin(), &*new_json_string->end(), &new_json, &error)) {
return base::Error() << error;
}
if (old_json != new_json) {
std::stringstream ss;
ss << "Formatted JSON tree does not match source. Possible reasons "
"include: \n"
"- JSON Integers (without quotes) are matched against 64-bit "
"integers in Prototype\n"
" (Reformatted integers will now have quotes.) Quote these integers "
"in source\n"
" JSON or use 32-bit integers instead.\n"
"- Enum values are stored as integers in source JSON file. Use enum "
"value name \n"
" string instead, or change schema field to string / integers.\n"
"- JSON keys are re-formatted to be lowerCamelCase. To fix, define "
"json_name "
"option\n"
" for appropriate fields.\n"
"\n"
"Reformatted JSON is printed below.\n";
Json::StreamWriterBuilder factory;
std::unique_ptr<Json::StreamWriter> const writer(factory.newStreamWriter());
writer->write(new_json, &ss);
return base::Error() << ss.str();
}
return {};
}
namespace internal {
android::base::Result<std::string> FormatJson(const std::string& json,
google::protobuf::Message* scratch_space) {
auto res = google::protobuf::util::JsonStringToMessage(json, scratch_space);
if (!res.ok()) {
return android::base::Error() << res;
}
std::string ret;
res = google::protobuf::util::MessageToJsonString(*scratch_space, &ret,
JsonPrintOptions{
.add_whitespace = true,
});
if (!res.ok()) {
return android::base::Error() << res;
}
return ret;
}
} // namespace internal
} // namespace jsonpb
} // namespace android