proto 1.2.4

This package contains a collection of utilities around Google's Protocolbuffer. The functions offered in this packages are widely used across Google's C++ code base and have saved tens of thousands of engineering hours. Some of these functions were originally implemented by the author and later re-implemented or cloned (see below).

The project works with Google's proto library version 27, 28, 29 and 30. Packages are available at Bazel Central Registry and GitHub.

Test

Parse Proto

Usage

BUILD.bazel:

cc_test(
    name = "test",
    srcs = ["test.cc"],
    deps = ["@mboworks_proto//mbo/proto:parse_text_proto_cc"],
)

Source test.cc:

#include "mbo/proto/parse_text_proto.h"

using ::mbo::proto::ParseTextProtoOrDie;

TEST(Foo, Test) {
    MyProto msg = ParseTextProtoOrDie(R"pb(
      field: "name"
      what: "Just dump the plain text-proto as C++ raw-string."
      )pb");
    // ...
}

Note:

The ParseTextProtoOrDie function dies if the input text-proto is not valid. That is done because the function emulates type safety this way. That is the author will likely only have to fix this once while many people will read the code. Further, this is test input that is supposed to be correct as is. If the input is of dynamic nature, then ParseText<ProtoType>(std::string_view) has to be used.

Proto Matchers

Proto Matcher Wrappers

Usage

BUILD.bazel:

cc_test(
    name = "test",
    srcs = ["test.cc"],
    deps = ["@mboworks_proto//mbo/proto:matchers_cc"],
)

Source test.cc:

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "mbo/proto/matchers.h"

using ::mbo::proto::EqualsProto;
using ::mbo::proto::IgnoringRepeatedFieldOrdering;

TEST(Foo, EqualsProto) {
    MyProto msg;
    msg.set_field("name");
    EXPECT_THAT(msg, EqualsProto(R"pb(
      field: "name"
      )pb"));
}

In the above example EqualsProto takes the text-proto as a C++ raw-string.

The matchers can of course be combined with the parse functions. The below shows how a FunctionUnderTest can be tested. It receives the proto input directly from the parse function and the matcher compares it directly to the expected golden result text-proto. Note how there is no field-by-field processing anywhere. No distraction from what is being tested and what the expectations are. Or in other words the test avoids misleading and error prone in-test logic. And because the function-under-test is called inside the EXPECT_THAT macro the gtest failure messages will show what actually failed (and not something like "Input: temp_var").

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "mbo/proto/matchers.h"
#include "mbo/proto/parse_text_proto.h"

using ::mbo::proto::EqualsProto;
using ::mbo::proto::IgnoringRepeatedFieldOrdering;
using ::mbo::proto::ParseTextProtoOrDie;

MyProto FunctionUnderTest(const MyProto& proto) {
  return proto;
}

TEST(Foo, Wrapper) {
    const MyProto input = ParseTextProtoOrDie(R"pb(
      number: 1
      number: 2
      number: 3
    )pb");
    EXPECT_THAT(
      FunctionUnderTest(input),
      IgnoringRepeatedFieldOrdering(EqualsProto(R"pb(
        number: 1
        number: 2
        number: 3
      )pb")));
}

Proto Files

Usage

#include <filesystem>
#include <iostream>

#include "mbo/proto/file.h"
#include "my_protos/my_proto.pb.h"  # Containing `MyProto`

using ::mbo::proto::ReadBinaryProtoFile;
using ::mbo::proto::ReadTextProtoFile;
using ::mbo::proto::WriteBinaryProtoFile;
using ::mbo::proto::WriteTextProtoFile;

int UseBinaryProto(const MyProto& my_proto, const std::filesystem::path& filename) {
  const auto result = WriteBinaryProtoFile(filename, my_proto);
  if (!auto.ok()) {
    std::cerr << "Error: " << result.status() << "\n";
    return 1;
  }
  // Reading with type-erased interface.
  const absl::StatusOr<MyProto> proto_or_status = ReadBinaryProtoFile(filename);
  if (!proto_or_status.ok()) {
    std::cerr << "Error: " << proto_or_status.status() << "\n";
    return 2;
  }
  // Reading with given template type parameter.
  {
    const auto proto_or_status = ReadBinaryProtoFile::As<MyProto>(filename);
    const auto proto = ReadBinaryProtoFile::OrDie<MyProto>(filename);
  }
  return 0;
}

int UseTextProto(const MyProto& my_proto, const std::filesystem::path& filename) {
  const auto result = WriteTextProtoFile(filename, my_proto);
  if (!auto.ok()) {
    std::cerr << "Error: " << result.status() << "\n";
    return 4;
  }
  // Reading with type-erased interface.
  const absl::StatusOr<MyProto> proto_or_status = ReadTextProtoFile(filename);
  if (!proto_or_status.ok()) {
    std::cerr << "Error: " << proto_or_status.status() << "\n";
    return 8;
  }
  // Reading with given template type parameter.
  {
    const auto proto_or_status = ReadTextProtoFile::As<MyProto>(filename);
    const auto proto = ReadTextProtoFile::OrDie<MyProto>(filename);
  }
  return 0;
}

int main() {
  const MyProto my_proto;
  return UseBinaryProto(my_proto, "my_file.pb") + UseTextProto(my_proto, "my_file.textproto");
}

Installation and requirements

This repository requires a C++20 compiler (in case of MacOS XCode 15 is needed) and Bazel 8 or newer. Required CI uses GCC 14 and LLVM/Clang 22.1.8 on Linux/Ubuntu and macOS, with Bazel 8 and 9 compatibility rungs. A scheduled compatibility workflow tests Google's proto libraries in versions [32, 33, 34, 35].

The reliance on a C++20 compiler is because it uses std::source_location since Google's Abseil absl::SourceLocation has not been open sourced.

The project only comes with a Bazel BUILD.bazel file and can be added to other Bazel projects.

The project is formatted with specific clang-format settings which require clang 16+ (in case of MacOs LLVM 16+ can be installed using brew). For simplicity in dev mode the project pulls the appropriate clang tools and can be compiled with those tools using bazel [build|test] --config=clang ....

MODULES.bazel

The project is consumed via Bazel modules (bzlmod); WORKSPACE mode is no longer supported. The BCR version declares the dependency versions this module is pinned to; those can be bumped locally. The protobuf version can be overridden in your own MODULE.bazel (e.g. via single_version_override) to any release the project supports.

Check Releases for details. All that is needed is a bazel_dep instruction with the correct version.

bazel_dep(name = "mboworks_proto", version = "1.2.2")

Clone

The clone was made from Google's CPP-proto-builder, of which the project lead is the original author and lead for over a decade. That includes in particular the parse_proto components which were invented in their original form around 2012 and used widely throughout Google.

Parse Proto

The following files were cloned:

cp ../cpp-proto-builder/proto_builder/oss/parse_proto_text.* proto/mbo/proto
cp ../cpp-proto-builder/proto_builder/oss/parse_proto_text_test.cc proto/mbo/proto
cp ../cpp-proto-builder/proto_builder/oss/tests/simple_message.proto proto/mbo/proto
patch <proto/mbo/proto/parse_proto_text.diff

The diff files are available in the repository history.

Proto Matchers

The matchers are part of Google's CPP-proto-builder, (see above).
Alternatively this could have been done from:

The FHIR sources are stripped and the nucleus sources are older and finally inazarenko's clone was
modified to remove other Google dependencies which creates the issue that the GoogleTest docs do not
apply as for instance the regular expression library is different.

The following files were cloned:

cp ../cpp-proto-builder/proto_builder/oss/testing/proto_test_util.* proto/mbo/testing/proto
patch <proto/mbo/testing/proto_test_util.diff

The diff files are available in the repository history.

The include guards were updated and the namespace changed to testing::proto which allows to
import the whole namespace easily. Further logging was switched directly to
Abseil logging (this was not an option when I wrote
the proto Builder or when it was open sourced).

This clone was established 2023.07.15. The source has since been moved and modified but remains as
close to the original source as possible.