blob: a533d567de26e9ba35059bc1a3ed6b2c63b59ff5 [file]
/*
* Copyright 2020 Google LLC
*
* 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.
*/
// Helpful functions for parsing text protobufs. This provide useful short forms
// that are useful in places like tests where you have known data and it's safe
// to just hard-terminate the program when parsing fails.
//
// ParseTextProtoOrDie(text):
// Parses "text" as a message where the type is automatically inferred from
// the type the result is being assigned to.
//
// ParseTextAsProtoOrDie<MessageType>(text):
// Parses "text" as the explicitly defined message type.
//
// ParseTextAsProtoVectorOrDie<MessageType>({text1, text2, ...}):
// Parses all of the "text1", "text2", and so on parameters as the explicitly
// defined message type and returns a vector of protos. Basically just a
// wrapper around ParseTextAsProtoOrDie, but useful for cutting down on a lot
// of repeated boilerplate when parsing N protos instead of 1.
//
// ParseTextFileAsProtoOrDie<MessageType>(filepath):
// Loads in the file path provided and parses contents as explicitly defined
// message type.
#ifndef THIRD_PARTY_ECCLESIA_LIB_PROTOBUF_PARSE_H_
#define THIRD_PARTY_ECCLESIA_LIB_PROTOBUF_PARSE_H_
#include <fstream>
#include <initializer_list>
#include <string>
#include <vector>
#include "absl/log/log.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "logging/interfaces.h"
#include "google/protobuf/io/tokenizer.h"
#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/text_format.h"
namespace ecclesia {
// Parse a text proto and terminate with a fatal log if parsing fails. This
// captures the source location of the caller so that failures are associated
// with the code doing the parse.
template <typename MessageType>
MessageType ParseTextAsProtoOrDie(
const std::string& text, SourceLocation loc = SourceLocation::current()) {
// Define a collector that just accumulates a list of error message strings.
class TextErrorCollector : public google::protobuf::io::ErrorCollector {
public:
const std::vector<std::string>& errors() const { return errors_; }
private:
void RecordError(int line, int column, absl::string_view message) override {
errors_.push_back(
absl::StrFormat("line %d, column %d: %s", line, column, message));
}
void RecordWarning(int line, int column,
absl::string_view message) override {
errors_.push_back(
absl::StrFormat("line %d, column %d: %s", line, column, message));
}
std::vector<std::string> errors_;
};
// Set up a parser using our collector.
TextErrorCollector collector;
google::protobuf::TextFormat::Parser parser;
parser.RecordErrorsTo(&collector);
// Try the actual parse. If it fails, terminate with all of the errors.
MessageType message;
if (!parser.ParseFromString(text, &message)) {
LOG(FATAL).AtLocation(loc.file_name(), loc.line())
<< "text proto parsing failed:\n"
<< absl::StrJoin(collector.errors(), "\n");
}
return message;
}
// Temporary object used by ParseTextProtoOrDie to hold the result. This defers
// the parsing until the result is assigned to a return type. Do not try to
// capture these objects yourself, either explicitly or via auto; if you're not
// assigning the result of ParseTextProtoOrDie to a protobuf value type then
// you're doing it wrong.
class ParseTextProtoOrDieTemporary {
public:
// Automatically convert this to the protobuf message type it is assigned to.
template <typename T>
operator T() {
return ParseTextAsProtoOrDie<T>(text_, loc_);
}
private:
// Only our parsing function should be able to construct these.
friend ParseTextProtoOrDieTemporary ParseTextProtoOrDie(
const std::string& /*text*/, SourceLocation /*loc*/);
ParseTextProtoOrDieTemporary(const std::string& text, SourceLocation loc)
: text_(text), loc_(loc) {}
// We don't want anyone copying or passing these objects around.
ParseTextProtoOrDieTemporary(const ParseTextProtoOrDieTemporary&) = delete;
ParseTextProtoOrDieTemporary& operator=(const ParseTextProtoOrDieTemporary&) =
delete;
// Normally we don't want to capture references but since this type is only
// ever supposed to be used as a temporary in an expression it should be safe.
const std::string& text_;
SourceLocation loc_;
};
// Untyped version of ParseTextAsProtoOrDie that uses the return type of the
// function to determine the type to parse to.
inline ParseTextProtoOrDieTemporary ParseTextProtoOrDie(
const std::string& text, SourceLocation loc = SourceLocation::current()) {
return ParseTextProtoOrDieTemporary(text, loc);
}
template <typename MessageType>
std::vector<MessageType> ParseTextAsProtoVectorOrDie(
std::initializer_list<absl::string_view> text_list,
SourceLocation loc = SourceLocation::current()) {
std::vector<MessageType> messages;
messages.reserve(text_list.size());
for (absl::string_view text : text_list) {
messages.push_back(
ParseTextAsProtoOrDie<MessageType>(std::string(text), loc));
}
return messages;
}
template <typename MessageType>
MessageType ParseTextFileAsProtoOrDie(
const std::string& path, SourceLocation loc = SourceLocation::current()) {
std::ifstream textfile(path);
if (textfile.fail()) {
LOG(FATAL).AtLocation(loc.file_name(), loc.line())
<< "failed to open file at path " << path;
}
google::protobuf::io::IstreamInputStream input_stream(&textfile);
MessageType message;
if (!google::protobuf::TextFormat::Parse(&input_stream, &message)) {
LOG(FATAL).AtLocation(loc.file_name(), loc.line())
<< "failed to parse text file at path " << path;
}
return message;
}
} // namespace ecclesia
#endif // THIRD_PARTY_ECCLESIA_LIB_PROTOBUF_PARSE_H_