blob: 7eecff9dcdaf0c0517cbd022ee084f9bf7b2b302 [file] [log] [blame]
/*
* Copyright 2015 The Kythe Authors. All rights reserved.
*
* 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 "json_proto.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "glog/logging.h"
#include "google/protobuf/io/coded_stream.h"
#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "google/protobuf/message.h"
#include "google/protobuf/util/json_util.h"
#include "google/protobuf/util/type_resolver.h"
#include "google/protobuf/util/type_resolver_util.h"
#include "rapidjson/document.h"
#include "rapidjson/filewritestream.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
namespace kythe {
namespace {
using ::google::protobuf::DescriptorPool;
using ::google::protobuf::util::JsonParseOptions;
using ::google::protobuf::util::TypeResolver;
class PermissiveTypeResolver : public TypeResolver {
public:
explicit PermissiveTypeResolver(const DescriptorPool* pool)
: impl_(google::protobuf::util::NewTypeResolverForDescriptorPool("",
pool)) {}
google::protobuf::util::Status ResolveMessageType(
const std::string& type_url,
google::protobuf::Type* message_type) override {
absl::string_view adjusted = type_url;
adjusted.remove_prefix(type_url.rfind('/') + 1);
return impl_->ResolveMessageType(absl::StrCat("/", adjusted), message_type);
}
google::protobuf::util::Status ResolveEnumType(
const std::string& type_url, google::protobuf::Enum* enum_type) override {
absl::string_view adjusted = type_url;
adjusted.remove_prefix(type_url.rfind('/') + 1);
return impl_->ResolveEnumType(absl::StrCat("/", adjusted), enum_type);
}
private:
std::unique_ptr<TypeResolver> impl_;
};
TypeResolver* GetGeneratedTypeResolver() {
static TypeResolver* generated_resolver =
new PermissiveTypeResolver(DescriptorPool::generated_pool());
return generated_resolver;
}
struct MaybeDeleteResolver {
void operator()(TypeResolver* resolver) const {
if (resolver != GetGeneratedTypeResolver()) {
delete resolver;
}
}
};
std::unique_ptr<TypeResolver, MaybeDeleteResolver> MakeTypeResolverForPool(
const DescriptorPool* pool) {
if (pool == DescriptorPool::generated_pool()) {
return std::unique_ptr<TypeResolver, MaybeDeleteResolver>(
GetGeneratedTypeResolver());
}
return std::unique_ptr<TypeResolver, MaybeDeleteResolver>(
new PermissiveTypeResolver(pool));
}
absl::Status WriteMessageAsJsonToStringInternal(
const google::protobuf::Message& message, std::string* out) {
auto resolver =
MakeTypeResolverForPool(message.GetDescriptor()->file()->pool());
google::protobuf::util::JsonPrintOptions options;
options.preserve_proto_field_names = true;
auto status = google::protobuf::util::BinaryToJsonString(
resolver.get(), message.GetDescriptor()->full_name(),
message.SerializeAsString(), out, options);
if (!status.ok()) {
return absl::Status(static_cast<absl::StatusCode>(status.code()),
std::string(status.message()));
}
return absl::OkStatus();
}
JsonParseOptions DefaultParseOptions() {
JsonParseOptions options;
options.case_insensitive_enum_parsing = false;
return options;
}
} // namespace
bool WriteMessageAsJsonToString(const google::protobuf::Message& message,
std::string* out) {
auto status = WriteMessageAsJsonToStringInternal(message, out);
if (!status.ok()) {
LOG(ERROR) << status.ToString();
}
return status.ok();
}
absl::StatusOr<std::string> WriteMessageAsJsonToString(
const google::protobuf::Message& message) {
std::string result;
auto status = WriteMessageAsJsonToStringInternal(message, &result);
if (!status.ok()) {
return status;
}
return result;
}
bool WriteMessageAsJsonToString(const google::protobuf::Message& message,
const std::string& format_key,
std::string* out) {
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
writer.StartObject();
writer.Key("format");
writer.String(format_key.c_str());
writer.Key("content");
{
std::string content;
if (!WriteMessageAsJsonToString(message, &content)) {
return false;
}
writer.RawValue(content.data(), content.size(), rapidjson::kObjectType);
}
writer.EndObject();
*out = buffer.GetString();
return true;
}
bool MergeJsonWithMessage(const std::string& in, std::string* format_key,
google::protobuf::Message* message) {
rapidjson::Document document;
document.Parse(in.c_str());
if (document.HasParseError()) {
return false;
}
if (!document.IsObject() || !document.HasMember("format") ||
!document.HasMember("content") || !document["format"].IsString() ||
!document["content"].IsObject()) {
return false;
}
std::string in_format = document["format"].GetString();
if (format_key) {
*format_key = in_format;
}
if (in_format == "kythe") {
std::string content = [&] {
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
CHECK(document["content"].Accept(writer));
return std::string(buffer.GetString());
}();
std::string binary;
auto resolver =
MakeTypeResolverForPool(message->GetDescriptor()->file()->pool());
auto status = google::protobuf::util::JsonToBinaryString(
resolver.get(), message->GetDescriptor()->full_name(), content, &binary,
DefaultParseOptions());
if (!status.ok()) {
LOG(ERROR) << status.ToString() << ": " << content;
return false;
}
return message->ParseFromString(binary);
}
return false;
}
absl::Status ParseFromJsonStream(
google::protobuf::io::ZeroCopyInputStream* input,
const JsonParseOptions& options, google::protobuf::Message* message) {
auto resolver =
MakeTypeResolverForPool(message->GetDescriptor()->file()->pool());
std::string binary;
google::protobuf::io::StringOutputStream output(&binary);
auto status = google::protobuf::util::JsonToBinaryStream(
resolver.get(), message->GetDescriptor()->full_name(), input, &output,
options);
if (!status.ok()) {
return absl::Status(static_cast<absl::StatusCode>(status.code()),
std::string(status.message()));
}
if (!message->ParseFromString(binary)) {
return absl::InvalidArgumentError(
"JSON transcoder produced invalid protobuf output.");
}
return absl::OkStatus();
}
absl::Status ParseFromJsonStream(
google::protobuf::io::ZeroCopyInputStream* input,
google::protobuf::Message* message) {
return ParseFromJsonStream(input, DefaultParseOptions(), message);
}
absl::Status ParseFromJsonString(absl::string_view input,
const JsonParseOptions& options,
google::protobuf::Message* message) {
google::protobuf::io::ArrayInputStream stream(input.data(), input.size());
return ParseFromJsonStream(&stream, options, message);
}
absl::Status ParseFromJsonString(absl::string_view input,
google::protobuf::Message* message) {
return ParseFromJsonString(input, DefaultParseOptions(), message);
}
void PackAny(const google::protobuf::Message& message,
absl::string_view type_uri, google::protobuf::Any* out) {
out->set_type_url(type_uri.data(), type_uri.size());
google::protobuf::io::StringOutputStream stream(out->mutable_value());
google::protobuf::io::CodedOutputStream coded_output_stream(&stream);
message.SerializeToCodedStream(&coded_output_stream);
}
bool UnpackAny(const google::protobuf::Any& any,
google::protobuf::Message* result) {
google::protobuf::io::ArrayInputStream stream(any.value().data(),
any.value().size());
google::protobuf::io::CodedInputStream coded_input_stream(&stream);
return result->ParseFromCodedStream(&coded_input_stream);
}
} // namespace kythe