• 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 
18 #include <jsonpb/verify.h>
19 
20 #include <iostream>
21 #include <memory>
22 #include <sstream>
23 #include <string>
24 
25 #include <android-base/strings.h>
26 #include <google/protobuf/descriptor.h>
27 #include <google/protobuf/descriptor.pb.h>
28 #include <google/protobuf/message.h>
29 #include <google/protobuf/reflection.h>
30 #include <json/reader.h>
31 #include <json/writer.h>
32 #include <jsonpb/jsonpb.h>
33 
34 namespace android {
35 namespace jsonpb {
36 
37 using google::protobuf::FieldDescriptor;
38 using google::protobuf::FieldDescriptorProto;
39 using google::protobuf::Message;
40 
41 // Return json_name of the field. If it is not set, return the name of the
42 // field.
GetJsonName(const FieldDescriptor & field_descriptor)43 const std::string& GetJsonName(const FieldDescriptor& field_descriptor) {
44   // The current version of libprotobuf does not define
45   // FieldDescriptor::has_json_name() yet. Use a workaround.
46   // TODO: use field_descriptor.has_json_name() when libprotobuf version is
47   // bumped.
48   FieldDescriptorProto proto;
49   field_descriptor.CopyTo(&proto);
50   return proto.has_json_name() ? field_descriptor.json_name()
51                                : field_descriptor.name();
52 }
53 
AllFieldsAreKnown(const Message & message,const Json::Value & json,std::vector<std::string> * path,std::stringstream * error)54 bool AllFieldsAreKnown(const Message& message, const Json::Value& json,
55                        std::vector<std::string>* path,
56                        std::stringstream* error) {
57   if (!json.isObject()) {
58     *error << base::Join(*path, ".") << ": Not a JSON object\n";
59     return false;
60   }
61   auto&& descriptor = message.GetDescriptor();
62 
63   auto json_members = json.getMemberNames();
64   std::set<std::string> json_keys{json_members.begin(), json_members.end()};
65 
66   std::set<std::string> known_keys;
67   for (int i = 0; i < descriptor->field_count(); ++i) {
68     known_keys.insert(GetJsonName(*descriptor->field(i)));
69   }
70 
71   std::set<std::string> unknown_keys;
72   std::set_difference(json_keys.begin(), json_keys.end(), known_keys.begin(),
73                       known_keys.end(),
74                       std::inserter(unknown_keys, unknown_keys.begin()));
75 
76   if (!unknown_keys.empty()) {
77     *error << base::Join(*path, ".") << ": contains unknown keys: ["
78            << base::Join(unknown_keys, ", ")
79            << "]. Keys must be a known field name of "
80            << descriptor->full_name() << "(or its json_name option if set): ["
81            << base::Join(known_keys, ", ") << "]\n";
82     return false;
83   }
84 
85   bool success = true;
86 
87   // Check message fields.
88   auto&& reflection = message.GetReflection();
89   std::vector<const FieldDescriptor*> set_field_descriptors;
90   reflection->ListFields(message, &set_field_descriptors);
91   for (auto&& field_descriptor : set_field_descriptors) {
92     if (field_descriptor->cpp_type() !=
93         FieldDescriptor::CppType::CPPTYPE_MESSAGE) {
94       continue;
95     }
96     if (field_descriptor->is_map()) {
97       continue;
98     }
99 
100     const std::string& json_name = GetJsonName(*field_descriptor);
101     const Json::Value& json_value = json[json_name];
102 
103     if (field_descriptor->is_repeated()) {
104       auto&& fields =
105           reflection->GetRepeatedFieldRef<Message>(message, field_descriptor);
106 
107       if (json_value.type() != Json::ValueType::arrayValue) {
108         *error << base::Join(*path, ".")
109                << ": not a JSON list. This should not happen.\n";
110         success = false;
111         continue;
112       }
113 
114       if (json_value.size() != static_cast<size_t>(fields.size())) {
115         *error << base::Join(*path, ".") << ": JSON list has size "
116                << json_value.size() << " but message has size " << fields.size()
117                << ". This should not happen.\n";
118         success = false;
119         continue;
120       }
121 
122       std::unique_ptr<Message> scratch_space(fields.NewMessage());
123       for (int i = 0; i < fields.size(); ++i) {
124         path->push_back(json_name + "[" + std::to_string(i) + "]");
125         auto res = AllFieldsAreKnown(fields.Get(i, scratch_space.get()),
126                                      json_value[i], path, error);
127         path->pop_back();
128         if (!res) {
129           success = false;
130         }
131       }
132     } else {
133       auto&& field = reflection->GetMessage(message, field_descriptor);
134       path->push_back(json_name);
135       auto res = AllFieldsAreKnown(field, json_value, path, error);
136       path->pop_back();
137       if (!res) {
138         success = false;
139       }
140     }
141   }
142   return success;
143 }
144 
AllFieldsAreKnown(const google::protobuf::Message & message,const std::string & json,std::string * error)145 bool AllFieldsAreKnown(const google::protobuf::Message& message,
146                        const std::string& json, std::string* error) {
147   Json::Reader reader;
148   Json::Value value;
149   if (!reader.parse(json, value)) {
150     *error = reader.getFormattedErrorMessages();
151     return false;
152   }
153 
154   std::stringstream errorss;
155   std::vector<std::string> json_tree_path{"<root>"};
156   if (!AllFieldsAreKnown(message, value, &json_tree_path, &errorss)) {
157     *error = errorss.str();
158     return false;
159   }
160   return true;
161 }
162 
EqReformattedJson(const std::string & json,google::protobuf::Message * scratch_space,std::string * error)163 bool EqReformattedJson(const std::string& json,
164                        google::protobuf::Message* scratch_space,
165                        std::string* error) {
166   Json::Reader reader;
167   Json::Value old_json;
168   if (!reader.parse(json, old_json)) {
169     *error = reader.getFormattedErrorMessages();
170     return false;
171   }
172 
173   auto new_json_string = internal::FormatJson(json, scratch_space);
174   if (!new_json_string.ok()) {
175     *error = new_json_string.error();
176     return false;
177   }
178   Json::Value new_json;
179   if (!reader.parse(*new_json_string, new_json)) {
180     *error = reader.getFormattedErrorMessages();
181     return false;
182   }
183 
184   if (old_json != new_json) {
185     std::stringstream ss;
186     ss << "Formatted JSON tree does not match source. Possible reasons "
187           "include: \n"
188           "- JSON Integers (without quotes) are matched against 64-bit "
189           "integers in Prototype\n"
190           "  (Reformatted integers will now have quotes.) Quote these integers "
191           "in source\n"
192           "  JSON or use 32-bit integers instead.\n"
193           "- Enum values are stored as integers in source JSON file. Use enum "
194           "value name \n"
195           "  string instead, or change schema field to string / integers.\n"
196           "- JSON keys are re-formatted to be lowerCamelCase. To fix, define "
197           "json_name "
198           "option\n"
199           "  for appropriate fields.\n"
200           "\n"
201           "Reformatted JSON is printed below.\n"
202        << Json::StyledWriter().write(new_json);
203     *error = ss.str();
204     return false;
205   }
206   return true;
207 }
208 
209 namespace internal {
FormatJson(const std::string & json,google::protobuf::Message * scratch_space)210 ErrorOr<std::string> FormatJson(const std::string& json,
211                                 google::protobuf::Message* scratch_space) {
212   auto res = internal::JsonStringToMessage(json, scratch_space);
213   if (!res.ok()) {
214     return MakeError<std::string>(res.error());
215   }
216   return MessageToJsonString(*scratch_space);
217 }
218 }  // namespace internal
219 
220 }  // namespace jsonpb
221 }  // namespace android
222