Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# 1.1.1
# 1.2.0

* Added std::optional variants for file reading.
* Added `HasBinaryProtoExtension` and `HasTextProtoExtension`.

# 1.1.0

Expand Down
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# Due to flag and test references to the repo name we use the old name for now.
module(
name = "helly25_proto",
version = "1.1.1",
version = "1.2.0",
)

# For local development we include LLVM, Hedron-Compile-Commands, etc.
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ TEST(Foo, Wrapper) {
* `message` the protocol buffer to write.
* Returns `absl::OkStatus()` or an error status.

* function `HasBinaryProtoExtension`(`filesname`)
* Returns whether the filename ends with a well-known extension for binary proto files.

* function `HasTextProtoExtension`(`filesname`)
* Returns whether the filename ends with a well-known extension for text proto files.

## Usage

```c++
Expand Down
24 changes: 23 additions & 1 deletion mbo/proto/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ licenses(["notice"])
cc_library(
name = "file_cc",
srcs = ["file.cc"],
hdrs = ["file.h"],
hdrs = [
"file.h",
"file_impl.h",
],
implementation_deps = [
":silent_error_collector_cc",
"@com_google_absl//absl/log:absl_log",
Expand All @@ -44,6 +47,7 @@ cc_test(
deps = [
":file_cc",
":matchers_cc",
":status_matchers_cc",
"//mbo/proto/tests:simple_message_cc_proto",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
Expand Down Expand Up @@ -145,3 +149,21 @@ cc_test(
"@com_google_protobuf//src/google/protobuf/io:tokenizer",
],
)

cc_library(
name = "status_matchers_cc",
testonly = 1,
hdrs = ["status_matchers.h"],
visibility = [
# Do not use this elsewhere. Instead:
# - just upgrade your Abseil dependency.
# #include "absl/status/status_matchers"
# - use @helly25_mbo//mbo/testing:status_cc
# #include "mbo/testing/status.h"
"//visibility:private",
],
deps = [
"@com_google_absl//absl/status",
"@com_google_googletest//:gtest",
],
)
10 changes: 10 additions & 0 deletions mbo/proto/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ absl::Status ReadTextProtoFile(

} // namespace proto_internal

bool HasBinaryProtoExtension(std::string_view filename) {
return filename.ends_with(".binpb") || // NL
filename.ends_with(".pb");
}

bool HasTextProtoExtension(std::string_view filename) {
return filename.ends_with(".txtpb") || // NL
filename.ends_with(".textproto");
}

absl::Status WriteBinaryProtoFile(
const std::filesystem::path& filename,
const ::google::protobuf::Message& proto,
Expand Down
127 changes: 114 additions & 13 deletions mbo/proto/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,71 @@
#ifndef MBO_PROTO_FILE_H_
#define MBO_PROTO_FILE_H_

#include <concepts>
#include <filesystem>
#include <optional>
#include <source_location>

#include "absl/log/absl_log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "google/protobuf/message.h"
#include "mbo/proto/file_impl.h" // IWYU pragma: export

// Functionality for reading and writing protos:
// - functions Has(Binary|Text)ProtoExtension
// - concept IsProtoType
// - struct/function Read(Binary|Text)ProtoFile(::(As|OrDie|OrNullopt))?
// - struct/function Write(Binary|Text)ProtoFile

namespace mbo::proto {

template<typename ProtoType>
concept IsProtoType =
std::derived_from<ProtoType, ::google::protobuf::Message> && !std::same_as<ProtoType, ::google::protobuf::Message>;
// Identifies binary proto filenames.
// See https://protobuf.dev/programming-guides/techniques/
// See https://en.wikipedia.org/wiki/Filename_extension (extension with dot)
// Added [".pb"] which is also fairly common.
bool HasBinaryProtoExtension(std::string_view filename);

namespace proto_internal {
inline bool HasBinaryProtoExtension(const std::same_as<std::filesystem::path> auto& filename) {
return HasBinaryProtoExtension(filename.native());
}

absl::Status ReadBinaryProtoFile(
const std::filesystem::path& filename,
::google::protobuf::Message& result,
const std::source_location& src_loc);
// Identifies text proto filenames.
// See https://protobuf.dev/programming-guides/techniques/
// See https://en.wikipedia.org/wiki/Filename_extension (extension with dot)
bool HasTextProtoExtension(std::string_view filename);

absl::Status ReadTextProtoFile(
const std::filesystem::path& filename,
::google::protobuf::Message& result,
const std::source_location& src_loc);
inline bool HasTextProtoExtension(const std::same_as<std::filesystem::path> auto& filename) {
return HasTextProtoExtension(filename.native());
}

} // namespace proto_internal
// Concept that identifies Proto types as opposed to the base proto `Message`.
template<typename ProtoType>
concept IsProtoType =
std::derived_from<ProtoType, ::google::protobuf::Message> && !std::same_as<ProtoType, ::google::protobuf::Message>;

// Type erasure proto reader for binary proto files.
//
// Example:
//
// ```c++
// const MyProto proto = ReadBinaryProtoFile(proto_filename);
// const absl::StatusOr<MyProto> proto_or_error = ReadBinaryProtoFile(proto_filename);
// const std::optional<MyProto> proto_or_nullopt = ReadBinaryProtoFile(proto_filename);
// ```
//
//
// In the above the first call requires the file to be readable as a `MyProto`.
// The program will abort with a check violation if the file cannot be read.
//
// The second call returns either the read protocol buffer or an error status.
// In this version the return value forces the caller to handle any errors.
//
// The third call returns either the read protocol buffer ot std::nullopt.
// In this verion the caller is responsible for error handling.
//
// The class also supports static direct typed access by functions whose
// addresses can be taken.
class ReadBinaryProtoFile {
public:
// Static read function - so it's address can be taken.
Expand All @@ -59,6 +96,17 @@ class ReadBinaryProtoFile {
return result;
}

// Static read function - so it's address can be taken.
template<IsProtoType ProtoType>
static std::optional<ProtoType> OrNullopt(
const std::filesystem::path& filename,
const std::source_location& src_loc = std::source_location::current()) {
if (auto result = As<ProtoType>(filename, src_loc); result.ok()) {
return *std::move(result);
}
return std::nullopt;
}

// Static read function - so it's address can be taken.
template<IsProtoType ProtoType>
static ProtoType OrDie(
Expand Down Expand Up @@ -92,6 +140,15 @@ class ReadBinaryProtoFile {
return result;
}

template<int&..., IsProtoType ProtoType>
operator std::optional<ProtoType>() const { // NOLINT(*-explicit-*)
absl::StatusOr<ProtoType> proto = *this;
if (!proto.ok()) {
return std::nullopt;
}
return *proto;
}

template<int&..., IsProtoType ProtoType>
operator ProtoType() const { // NOLINT(*-explicit-*)
absl::StatusOr<ProtoType> proto = *this;
Expand All @@ -106,11 +163,34 @@ class ReadBinaryProtoFile {
mutable bool converted_ = false;
};

// Writes a binary proto file.
absl::Status WriteBinaryProtoFile(
const std::filesystem::path& filename,
const ::google::protobuf::Message& proto,
const std::source_location& src_loc = std::source_location::current());

// Type erasure proto reader for text proto files.
//
// Example:
//
// ```c++
// const MyProto proto = ReadTextProtoFile(filename);
// const absl::StatusOr<MyProto> proto_or_error = ReadTextProtoFile(filename);
// const std::optional<MyProto> proto_or_nullopt = ReadTextProtoFile(filename);
// ```
//
// In the above the first call requires the file to be readable as a `MyProto`.
// The program will abort with a check violation if the file cannot be read.
//
// The second call returns either the read protocol buffer or an error status.
// In this version the return value forces the caller to handle any errors.
//
// The third call returns either the read protocol buffer ot std::nullopt.
// In this verion the caller is responsible for error handling.
//
// The class also supports static direct typed access by functions whose
// addresses can be taken.

class ReadTextProtoFile {
public:
// Static read function - so it's address can be taken.
Expand All @@ -126,6 +206,17 @@ class ReadTextProtoFile {
return result;
}

// Static read function - so it's address can be taken.
template<IsProtoType ProtoType>
static std::optional<ProtoType> OrNullopt(
const std::filesystem::path& filename,
const std::source_location& src_loc = std::source_location::current()) {
if (auto result = As<ProtoType>(filename, src_loc); result.ok()) {
return *std::move(result);
}
return std::nullopt;
}

// Static read function - so it's address can be taken.
template<IsProtoType ProtoType>
static ProtoType OrDie(
Expand Down Expand Up @@ -159,6 +250,15 @@ class ReadTextProtoFile {
return result;
}

template<int&..., IsProtoType ProtoType>
operator std::optional<ProtoType>() const { // NOLINT(*-explicit-*)
absl::StatusOr<ProtoType> proto = *this;
if (!proto.ok()) {
return std::nullopt;
}
return *proto;
}

template<int&..., IsProtoType ProtoType>
operator ProtoType() const { // NOLINT(*-explicit-*)
absl::StatusOr<ProtoType> proto = *this;
Expand All @@ -173,6 +273,7 @@ class ReadTextProtoFile {
mutable bool converted_ = false;
};

// Writes a text proto file.
absl::Status WriteTextProtoFile(
const std::filesystem::path& filename,
const ::google::protobuf::Message& proto,
Expand Down
41 changes: 41 additions & 0 deletions mbo/proto/file_impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright (c) The helly25/mbo authors (helly25.com), The CPP Proto Builder Authors
// SPDX-License-Identifier: Apache-2.0
//
// 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.

#ifndef MBO_PROTO_FILE_IMPL_H_
#define MBO_PROTO_FILE_IMPL_H_

// IWYU pragma: private, include "mbo/proto/file.h"

#include <filesystem>
#include <source_location>

#include "absl/status/status.h"
#include "google/protobuf/message.h"

namespace mbo::proto::proto_internal {

absl::Status ReadBinaryProtoFile(
const std::filesystem::path& filename,
::google::protobuf::Message& result,
const std::source_location& src_loc);

absl::Status ReadTextProtoFile(
const std::filesystem::path& filename,
::google::protobuf::Message& result,
const std::source_location& src_loc);

} // namespace mbo::proto::proto_internal

#endif // MBO_PROTO_FILE_IMPL_H_
Loading