• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <jsonpb/verify.h>
18 
19 #include <iostream>
20 #include <memory>
21 #include <sstream>
22 #include <string>
23 
24 #include <android-base/strings.h>
25 #include <google/protobuf/descriptor.h>
26 #include <google/protobuf/descriptor.pb.h>
27 #include <google/protobuf/message.h>
28 #include <google/protobuf/reflection.h>
29 #include <json/reader.h>
30 #include <json/writer.h>
31 #include <jsonpb/jsonpb.h>
32 
33 namespace android {
34 namespace jsonpb {
35 
36 using google::protobuf::FieldDescriptor;
37 using google::protobuf::FieldDescriptorProto;
38 using google::protobuf::Message;
39 
40 // Return json_name of the field. If it is not set, return the name of the
41 // field.
GetJsonName(const FieldDescriptor & field_descriptor)42 const std::string& GetJsonName(const FieldDescriptor& field_descriptor) {
43   // The current version of libprotobuf does not define
44   // FieldDescriptor::has_json_name() yet. Use a workaround.
45   // TODO: use field_descriptor.has_json_name() when libprotobuf version is
46   // bumped.
47   FieldDescriptorProto proto;
48   field_descriptor.CopyTo(&proto);
49   return proto.has_json_name() ? field_descriptor.json_name() : field_descriptor.name();
50 }
51 
AllFieldsAreKnown(const Message & message,const Json::Value & json,std::vector<std::string> * path,std::stringstream * error)52 bool AllFieldsAreKnown(const Message& message, const Json::Value& json,
53                        std::vector<std::string>* path, std::stringstream* error) {
54   if (!json.isObject()) {
55     *error << base::Join(*path, ".") << ": Not a JSON object\n";
56     return false;
57   }
58   auto&& descriptor = message.GetDescriptor();
59 
60   auto json_members = json.getMemberNames();
61   std::set<std::string> json_keys{json_members.begin(), json_members.end()};
62 
63   std::set<std::string> known_keys;
64   for (int i = 0; i < descriptor->field_count(); ++i) {
65     known_keys.insert(GetJsonName(*descriptor->field(i)));
66   }
67 
68   std::set<std::string> unknown_keys;
69   std::set_difference(json_keys.begin(), json_keys.end(), known_keys.begin(), known_keys.end(),
70                       std::inserter(unknown_keys, unknown_keys.begin()));
71 
72   if (!unknown_keys.empty()) {
73     *error << base::Join(*path, ".") << ": contains unknown keys: ["
74            << base::Join(unknown_keys, ", ") << "]. Keys must be a known field name of "
75            << descriptor->full_name() << "(or its json_name option if set): ["
76            << base::Join(known_keys, ", ") << "]\n";
77     return false;
78   }
79 
80   bool success = true;
81 
82   // Check message fields.
83   auto&& reflection = message.GetReflection();
84   std::vector<const FieldDescriptor*> set_field_descriptors;
85   reflection->ListFields(message, &set_field_descriptors);
86   for (auto&& field_descriptor : set_field_descriptors) {
87     if (field_descriptor->cpp_type() != FieldDescriptor::CppType::CPPTYPE_MESSAGE) {
88       continue;
89     }
90     if (field_descriptor->is_map()) {
91       continue;
92     }
93 
94     const std::string& json_name = GetJsonName(*field_descriptor);
95     const Json::Value& json_value = json[json_name];
96 
97     if (field_descriptor->is_repeated()) {
98       auto&& fields = reflection->GetRepeatedFieldRef<Message>(message, field_descriptor);
99 
100       if (json_value.type() != Json::ValueType::arrayValue) {
101         *error << base::Join(*path, ".") << ": not a JSON list. This should not happen.\n";
102         success = false;
103         continue;
104       }
105 
106       if (json_value.size() != static_cast<size_t>(fields.size())) {
107         *error << base::Join(*path, ".") << ": JSON list has size " << json_value.size()
108                << " but message has size " << fields.size() << ". This should not happen.\n";
109         success = false;
110         continue;
111       }
112 
113       std::unique_ptr<Message> scratch_space(fields.NewMessage());
114       for (int i = 0; i < fields.size(); ++i) {
115         path->push_back(json_name + "[" + std::to_string(i) + "]");
116         auto res =
117             AllFieldsAreKnown(fields.Get(i, scratch_space.get()), json_value[i], path, error);
118         path->pop_back();
119         if (!res) {
120           success = false;
121         }
122       }
123     } else {
124       auto&& field = reflection->GetMessage(message, field_descriptor);
125       path->push_back(json_name);
126       auto res = AllFieldsAreKnown(field, json_value, path, error);
127       path->pop_back();
128       if (!res) {
129         success = false;
130       }
131     }
132   }
133   return success;
134 }
135 
AllFieldsAreKnown(const google::protobuf::Message & message,const std::string & json,std::string * error)136 bool AllFieldsAreKnown(const google::protobuf::Message& message, const std::string& json,
137                        std::string* error) {
138   Json::CharReaderBuilder builder;
139   std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
140   Json::Value value;
141   if (!reader->parse(&*json.begin(), &*json.end(), &value, error)) {
142     return false;
143   }
144 
145   std::stringstream errorss;
146   std::vector<std::string> json_tree_path{"<root>"};
147   if (!AllFieldsAreKnown(message, value, &json_tree_path, &errorss)) {
148     *error = errorss.str();
149     return false;
150   }
151   return true;
152 }
153 
EqReformattedJson(const std::string & json,google::protobuf::Message * scratch_space,std::string * error)154 bool EqReformattedJson(const std::string& json, google::protobuf::Message* scratch_space,
155                        std::string* error) {
156   Json::CharReaderBuilder builder;
157   std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
158   Json::Value old_json;
159   if (!reader->parse(&*json.begin(), &*json.end(), &old_json, error)) {
160     return false;
161   }
162 
163   auto new_json_string = internal::FormatJson(json, scratch_space);
164   if (!new_json_string.ok()) {
165     *error = new_json_string.error();
166     return false;
167   }
168   Json::Value new_json;
169   if (!reader->parse(&*new_json_string->begin(), &*new_json_string->end(), &new_json, error)) {
170     return false;
171   }
172 
173   if (old_json != new_json) {
174     std::stringstream ss;
175     ss << "Formatted JSON tree does not match source. Possible reasons "
176           "include: \n"
177           "- JSON Integers (without quotes) are matched against 64-bit "
178           "integers in Prototype\n"
179           "  (Reformatted integers will now have quotes.) Quote these integers "
180           "in source\n"
181           "  JSON or use 32-bit integers instead.\n"
182           "- Enum values are stored as integers in source JSON file. Use enum "
183           "value name \n"
184           "  string instead, or change schema field to string / integers.\n"
185           "- JSON keys are re-formatted to be lowerCamelCase. To fix, define "
186           "json_name "
187           "option\n"
188           "  for appropriate fields.\n"
189           "\n"
190           "Reformatted JSON is printed below.\n";
191     Json::StreamWriterBuilder factory;
192     std::unique_ptr<Json::StreamWriter> const writer(factory.newStreamWriter());
193     writer->write(new_json, &ss);
194     *error = ss.str();
195     return false;
196   }
197   return true;
198 }
199 
200 namespace internal {
FormatJson(const std::string & json,google::protobuf::Message * scratch_space)201 ErrorOr<std::string> FormatJson(const std::string& json, google::protobuf::Message* scratch_space) {
202   auto res = internal::JsonStringToMessage(json, scratch_space);
203   if (!res.ok()) {
204     return MakeError<std::string>(res.error());
205   }
206   return MessageToJsonString(*scratch_space);
207 }
208 }  // namespace internal
209 
210 }  // namespace jsonpb
211 }  // namespace android
212