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