/* * 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 <android-base/strings.h> #include <google/protobuf/descriptor.h> #include <google/protobuf/descriptor.pb.h> #include <google/protobuf/message.h> #include <google/protobuf/reflection.h> #include <json/reader.h> #include <json/writer.h> #include <jsonpb/jsonpb.h> namespace android { namespace jsonpb { using google::protobuf::FieldDescriptor; using google::protobuf::FieldDescriptorProto; using google::protobuf::Message; // Return json_name of the field. If it is not set, return the name of the // field. const std::string& GetJsonName(const FieldDescriptor& field_descriptor) { // The current version of libprotobuf does not define // FieldDescriptor::has_json_name() yet. Use a workaround. // TODO: use field_descriptor.has_json_name() when libprotobuf version is // bumped. FieldDescriptorProto proto; field_descriptor.CopyTo(&proto); return proto.has_json_name() ? field_descriptor.json_name() : field_descriptor.name(); } bool AllFieldsAreKnown(const Message& message, const Json::Value& json, std::vector<std::string>* path, std::stringstream* error) { if (!json.isObject()) { *error << base::Join(*path, ".") << ": Not a JSON object\n"; return false; } 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()) { *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, ", ") << "]\n"; return false; } bool success = true; // 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; } const 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) { *error << base::Join(*path, ".") << ": not a JSON list. This should not happen.\n"; success = false; continue; } if (json_value.size() != static_cast<size_t>(fields.size())) { *error << base::Join(*path, ".") << ": JSON list has size " << json_value.size() << " but message has size " << fields.size() << ". This should not happen.\n"; success = false; continue; } std::unique_ptr<Message> scratch_space(fields.NewMessage()); for (int i = 0; i < fields.size(); ++i) { path->push_back(json_name + "[" + std::to_string(i) + "]"); auto res = AllFieldsAreKnown(fields.Get(i, scratch_space.get()), json_value[i], path, error); path->pop_back(); if (!res) { success = false; } } } else { auto&& field = reflection->GetMessage(message, field_descriptor); path->push_back(json_name); auto res = AllFieldsAreKnown(field, json_value, path, error); path->pop_back(); if (!res) { success = false; } } } return success; } bool AllFieldsAreKnown(const google::protobuf::Message& message, const std::string& json, std::string* error) { Json::Reader reader; Json::Value value; if (!reader.parse(json, value)) { *error = reader.getFormattedErrorMessages(); return false; } std::stringstream errorss; std::vector<std::string> json_tree_path{"<root>"}; if (!AllFieldsAreKnown(message, value, &json_tree_path, &errorss)) { *error = errorss.str(); return false; } return true; } bool EqReformattedJson(const std::string& json, google::protobuf::Message* scratch_space, std::string* error) { Json::Reader reader; Json::Value old_json; if (!reader.parse(json, old_json)) { *error = reader.getFormattedErrorMessages(); return false; } auto new_json_string = internal::FormatJson(json, scratch_space); if (!new_json_string.ok()) { *error = new_json_string.error(); return false; } Json::Value new_json; if (!reader.parse(*new_json_string, new_json)) { *error = reader.getFormattedErrorMessages(); return false; } 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::StyledWriter().write(new_json); *error = ss.str(); return false; } return true; } namespace internal { ErrorOr<std::string> FormatJson(const std::string& json, google::protobuf::Message* scratch_space) { auto res = internal::JsonStringToMessage(json, scratch_space); if (!res.ok()) { return MakeError<std::string>(res.error()); } return MessageToJsonString(*scratch_space); } } // namespace internal } // namespace jsonpb } // namespace android