Add C++ support for retention attribute

PiperOrigin-RevId: 506345156
pull/11698/head
Adam Cozzette 2 years ago committed by Copybara-Service
parent d6895b548e
commit 8f882e7f3d
  1. 26
      src/google/protobuf/BUILD.bazel
  2. 11
      src/google/protobuf/compiler/BUILD.bazel
  3. 1
      src/google/protobuf/compiler/cpp/BUILD.bazel
  4. 4
      src/google/protobuf/compiler/cpp/file.cc
  5. 104
      src/google/protobuf/compiler/retention.cc
  6. 49
      src/google/protobuf/compiler/retention.h
  7. 246
      src/google/protobuf/retention_test.cc
  8. 206
      src/google/protobuf/unittest_retention.proto

@ -551,6 +551,7 @@ filegroup(
"unittest_proto3_arena_lite.proto",
"unittest_proto3_lite.proto",
"unittest_proto3_optional.proto",
"unittest_retention.proto",
"unittest_well_known_types.proto",
],
visibility = ["//:__subpackages__"],
@ -1277,6 +1278,31 @@ cc_test(
],
)
proto_library(
name = "unittest_retention_proto",
testonly = True,
srcs = ["unittest_retention.proto"],
strip_import_prefix = "/src",
deps = [":descriptor_proto"],
)
cc_proto_library(
name = "unittest_retention_cc_proto",
testonly = True,
deps = ["unittest_retention_proto"],
)
cc_test(
name = "retention_test",
srcs = ["retention_test.cc"],
deps = [
":protobuf",
":unittest_retention_cc_proto",
"//src/google/protobuf/compiler:retention",
"@com_google_googletest//:gtest_main",
],
)
################################################################################
# Helper targets for Kotlin tests
################################################################################

@ -310,6 +310,17 @@ cc_test(
],
)
cc_library(
name = "retention",
srcs = ["retention.cc"],
hdrs = ["retention.h"],
include_prefix = "google/protobuf/compiler",
visibility = ["//src/google/protobuf:__subpackages__"],
deps = [
"//src/google/protobuf:protobuf_nowkt",
],
)
################################################################################
# Generates protoc release artifacts.
################################################################################

@ -85,6 +85,7 @@ cc_library(
":names",
":names_internal",
"//src/google/protobuf/compiler:code_generator",
"//src/google/protobuf/compiler:retention",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:btree",
"@com_google_absl//absl/container:flat_hash_map",

@ -57,6 +57,7 @@
#include "google/protobuf/compiler/cpp/message.h"
#include "google/protobuf/compiler/cpp/names.h"
#include "google/protobuf/compiler/cpp/service.h"
#include "google/protobuf/compiler/retention.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/io/printer.h"
@ -962,8 +963,7 @@ void FileGenerator::GenerateReflectionInitializationCode(io::Printer* p) {
// FileDescriptorProto/ and embed it as a string literal, which is parsed and
// built into real descriptors at initialization time.
FileDescriptorProto file_proto;
file_->CopyTo(&file_proto);
FileDescriptorProto file_proto = StripSourceRetentionOptions(*file_);
std::string file_data;
file_proto.SerializeToString(&file_data);

@ -0,0 +1,104 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "google/protobuf/compiler/retention.h"
#include <memory>
#include <string>
#include <vector>
#include "google/protobuf/descriptor.h"
#include "google/protobuf/dynamic_message.h"
namespace google {
namespace protobuf {
namespace compiler {
namespace {
// Recursively strips any options with source retention from the message.
void StripMessage(Message& m) {
const Reflection* reflection = m.GetReflection();
std::vector<const FieldDescriptor*> fields;
reflection->ListFields(m, &fields);
for (const FieldDescriptor* field : fields) {
if (field->options().retention() == FieldOptions::RETENTION_SOURCE) {
reflection->ClearField(&m, field);
} else if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
if (field->is_repeated()) {
int field_size = reflection->FieldSize(m, field);
for (int i = 0; i < field_size; ++i) {
StripMessage(*reflection->MutableRepeatedMessage(&m, field, i));
}
} else {
StripMessage(*reflection->MutableMessage(&m, field));
}
}
}
}
} // namespace
FileDescriptorProto StripSourceRetentionOptions(const FileDescriptor& file) {
FileDescriptorProto file_proto;
file.CopyTo(&file_proto);
// We need to look up the descriptor in file.pool() so that we can get a
// descriptor which knows about any custom options that were used in the
// .proto file.
const Descriptor* descriptor =
file.pool()->FindMessageTypeByName(FileDescriptorProto().GetTypeName());
if (descriptor == nullptr) {
// If the pool does not contain the descriptor for FileDescriptorProto,
// then this proto file does not transitively depend on descriptor.proto,
// in which case we know there are no custom options to worry about.
StripMessage(file_proto);
} else {
// The options message may have custom options set on it, and these would
// ordinarily appear as unknown fields since they are not linked into
// protoc. Using a dynamic message allows us to see these custom options.
// To convert back and forth between the generated type and the dynamic
// message, we have to serialize one and parse that into the other.
DynamicMessageFactory factory;
std::unique_ptr<Message> dynamic_message(
factory.GetPrototype(descriptor)->New());
std::string serialized;
ABSL_CHECK(file_proto.SerializeToString(&serialized));
ABSL_CHECK(dynamic_message->ParseFromString(serialized));
StripMessage(*dynamic_message);
ABSL_CHECK(dynamic_message->SerializeToString(&serialized));
ABSL_CHECK(file_proto.ParseFromString(serialized));
}
return file_proto;
}
} // namespace compiler
} // namespace protobuf
} // namespace google

@ -0,0 +1,49 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef GOOGLE_PROTOBUF_COMPILER_RETENTION_H__
#define GOOGLE_PROTOBUF_COMPILER_RETENTION_H__
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
namespace google {
namespace protobuf {
namespace compiler {
// Returns a FileDescriptorProto for this file, with all RETENTION_SOURCE
// options stripped out.
FileDescriptorProto StripSourceRetentionOptions(const FileDescriptor& file);
} // namespace compiler
} // namespace protobuf
} // namespace google
#endif // GOOGLE_PROTOBUF_COMPILER_RETENTION_H__

@ -0,0 +1,246 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "google/protobuf/compiler/retention.h"
#include <memory>
#include <string>
#include <vector>
#include "google/protobuf/descriptor.pb.h"
#include <gtest/gtest.h>
#include "absl/strings/substitute.h"
#include "google/protobuf/compiler/parser.h"
#include "google/protobuf/dynamic_message.h"
#include "google/protobuf/io/tokenizer.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "google/protobuf/text_format.h"
#include "google/protobuf/unittest_retention.pb.h"
#include "google/protobuf/util/message_differencer.h"
namespace google {
namespace protobuf {
namespace internal {
namespace {
TEST(RetentionTest, DirectOptions) {
const google::protobuf::FileOptions& file_options =
OptionsMessage::descriptor()->file()->options();
EXPECT_EQ(file_options.GetExtension(plain_option), 1);
EXPECT_EQ(file_options.GetExtension(runtime_retention_option), 2);
// RETENTION_SOURCE option should be stripped.
EXPECT_FALSE(file_options.HasExtension(source_retention_option));
EXPECT_EQ(file_options.GetExtension(source_retention_option), 0);
}
void CheckOptionsMessageIsStrippedCorrectly(const OptionsMessage& options) {
EXPECT_EQ(options.plain_field(), 1);
EXPECT_EQ(options.runtime_retention_field(), 2);
// RETENTION_SOURCE field should be stripped.
EXPECT_FALSE(options.has_source_retention_field());
EXPECT_EQ(options.source_retention_field(), 0);
}
TEST(RetentionTest, FieldsNestedInRepeatedMessage) {
const google::protobuf::FileOptions& file_options =
OptionsMessage::descriptor()->file()->options();
ASSERT_EQ(1, file_options.ExtensionSize(repeated_options));
const OptionsMessage& options_message =
file_options.GetRepeatedExtension(repeated_options)[0];
CheckOptionsMessageIsStrippedCorrectly(options_message);
}
TEST(RetentionTest, File) {
CheckOptionsMessageIsStrippedCorrectly(
OptionsMessage::descriptor()->file()->options().GetExtension(
file_option));
}
TEST(RetentionTest, TopLevelMessage) {
CheckOptionsMessageIsStrippedCorrectly(
TopLevelMessage::descriptor()->options().GetExtension(message_option));
}
TEST(RetentionTest, NestedMessage) {
CheckOptionsMessageIsStrippedCorrectly(
TopLevelMessage::NestedMessage::descriptor()->options().GetExtension(
message_option));
}
TEST(RetentionTest, TopLevelEnum) {
CheckOptionsMessageIsStrippedCorrectly(
TopLevelEnum_descriptor()->options().GetExtension(enum_option));
}
TEST(RetentionTest, NestedEnum) {
CheckOptionsMessageIsStrippedCorrectly(
TopLevelMessage::NestedEnum_descriptor()->options().GetExtension(
enum_option));
}
TEST(RetentionTest, EnumEntry) {
CheckOptionsMessageIsStrippedCorrectly(
TopLevelEnum_descriptor()->value(0)->options().GetExtension(
enum_entry_option));
}
TEST(RetentionTest, TopLevelExtension) {
CheckOptionsMessageIsStrippedCorrectly(TopLevelMessage::descriptor()
->file()
->FindExtensionByName("i")
->options()
.GetExtension(field_option));
}
TEST(RetentionTest, NestedExtension) {
CheckOptionsMessageIsStrippedCorrectly(
TopLevelMessage::descriptor()->extension(0)->options().GetExtension(
field_option));
}
TEST(RetentionTest, Field) {
CheckOptionsMessageIsStrippedCorrectly(
TopLevelMessage::descriptor()->field(0)->options().GetExtension(
field_option));
}
TEST(RetentionTest, Oneof) {
CheckOptionsMessageIsStrippedCorrectly(
TopLevelMessage::descriptor()->oneof_decl(0)->options().GetExtension(
oneof_option));
}
TEST(RetentionTest, ExtensionRange) {
CheckOptionsMessageIsStrippedCorrectly(
TopLevelMessage::descriptor()->extension_range(0)->options_->GetExtension(
extension_range_option));
}
TEST(RetentionTest, Service) {
CheckOptionsMessageIsStrippedCorrectly(
TopLevelMessage::descriptor()->file()->service(0)->options().GetExtension(
service_option));
}
TEST(RetentionTest, Method) {
CheckOptionsMessageIsStrippedCorrectly(TopLevelMessage::descriptor()
->file()
->service(0)
->method(0)
->options()
.GetExtension(method_option));
}
TEST(RetentionTest, StripSourceRetentionOptions) {
// The tests above make assertions against the generated code, but this test
// case directly examines the result of the StripSourceRetentionOptions()
// function instead.
std::string proto_file =
absl::Substitute(R"(
syntax = "proto2";
package google.protobuf.internal;
import "$0";
option (source_retention_option) = 123;
option (options) = {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
};
option (repeated_options) = {
i1: 111 i2: 222
};
message Options {
optional int32 i1 = 1 [retention = RETENTION_SOURCE];
optional int32 i2 = 2;
message ChildMessage {
optional string s = 1 [retention = RETENTION_SOURCE];
}
optional ChildMessage c = 3;
repeated ChildMessage rc = 4;
}
extend google.protobuf.FileOptions {
optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
optional Options options = 50001;
repeated Options repeated_options = 50002;
})",
FileDescriptorSet::descriptor()->file()->name());
io::ArrayInputStream input_stream(proto_file.data(),
static_cast<int>(proto_file.size()));
io::ErrorCollector error_collector;
io::Tokenizer tokenizer(&input_stream, &error_collector);
compiler::Parser parser;
FileDescriptorProto file_descriptor;
ASSERT_TRUE(parser.Parse(&tokenizer, &file_descriptor));
file_descriptor.set_name("retention.proto");
DescriptorPool pool;
FileDescriptorProto descriptor_proto_descriptor;
FileDescriptorSet::descriptor()->file()->CopyTo(&descriptor_proto_descriptor);
pool.BuildFile(descriptor_proto_descriptor);
pool.BuildFile(file_descriptor);
FileDescriptorProto stripped_file = compiler::StripSourceRetentionOptions(
*pool.FindFileByName("retention.proto"));
// We use a dynamic message to generate the expected options proto. This lets
// us parse the custom options in text format.
const Descriptor* file_options_descriptor =
pool.FindMessageTypeByName(FileOptions().GetTypeName());
DynamicMessageFactory factory;
std::unique_ptr<Message> dynamic_message(
factory.GetPrototype(file_options_descriptor)->New());
ASSERT_TRUE(TextFormat::ParseFromString(
R"([google.protobuf.internal.options] {
i2: 456
c {}
rc {}
}
[google.protobuf.internal.repeated_options] {
i2: 222
})",
dynamic_message.get()));
FileOptions expected_options;
ASSERT_TRUE(
expected_options.ParseFromString(dynamic_message->SerializeAsString()));
EXPECT_TRUE(util::MessageDifferencer::Equals(stripped_file.options(),
expected_options));
}
} // namespace
} // namespace internal
} // namespace protobuf
} // namespace google

@ -0,0 +1,206 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto2";
package google.protobuf.internal;
import "google/protobuf/descriptor.proto";
// Retention attributes set directly on custom options
extend google.protobuf.FileOptions {
optional int32 plain_option = 505092806;
optional int32 runtime_retention_option = 505039132
[retention = RETENTION_RUNTIME];
optional int32 source_retention_option = 504878676
[retention = RETENTION_SOURCE];
}
option (plain_option) = 1;
option (runtime_retention_option) = 2;
option (source_retention_option) = 3;
// Retention attributes set on fields nested within a message
message OptionsMessage {
optional int32 plain_field = 1;
optional int32 runtime_retention_field = 2 [retention = RETENTION_RUNTIME];
optional int32 source_retention_field = 3 [retention = RETENTION_SOURCE];
}
extend google.protobuf.FileOptions {
optional OptionsMessage file_option = 504871168;
}
option (file_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
};
// Retention attribute nested inside a repeated message field
extend google.protobuf.FileOptions {
repeated OptionsMessage repeated_options = 504823570;
}
option (repeated_options) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
};
extend google.protobuf.ExtensionRangeOptions {
optional OptionsMessage extension_range_option = 504822148;
}
extend google.protobuf.MessageOptions {
optional OptionsMessage message_option = 504820819;
}
extend google.protobuf.FieldOptions {
optional OptionsMessage field_option = 504589219;
}
extend google.protobuf.OneofOptions {
optional OptionsMessage oneof_option = 504479153;
}
extend google.protobuf.EnumOptions {
optional OptionsMessage enum_option = 504451567;
}
extend google.protobuf.EnumValueOptions {
optional OptionsMessage enum_entry_option = 504450522;
}
extend google.protobuf.ServiceOptions {
optional OptionsMessage service_option = 504387709;
}
extend google.protobuf.MethodOptions {
optional OptionsMessage method_option = 504349420;
}
message Extendee {
extensions 1, 2;
}
extend Extendee {
optional int32 i = 1 [(field_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
}];
}
message TopLevelMessage {
option (message_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
};
message NestedMessage {
option (message_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
};
}
enum NestedEnum {
option (enum_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
};
NESTED_UNKNOWN = 0;
}
optional float f = 1 [(field_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
}];
oneof o {
option (oneof_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
};
int64 i = 2;
}
extensions 10 to 100 [(extension_range_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
}];
extend Extendee {
optional string s = 2 [(field_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
}];
}
}
enum TopLevelEnum {
option (enum_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
};
TOP_LEVEL_UNKNOWN = 0 [(enum_entry_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
}];
}
service Service {
option (service_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
};
rpc DoStuff(TopLevelMessage) returns (TopLevelMessage) {
option (method_option) = {
plain_field: 1
runtime_retention_field: 2
source_retention_field: 3
};
}
}
Loading…
Cancel
Save