diff --git a/generator/generator_config.proto b/generator/generator_config.proto index 40e94d9b00290..0dc865a2dd285 100644 --- a/generator/generator_config.proto +++ b/generator/generator_config.proto @@ -167,6 +167,21 @@ message ServiceConfiguration { // RPCs. If set to false (the default), a no-op resumption function will be // generated. bool omit_streaming_updater = 29; + + message BespokeMethod { + string name = 1; + string return_type = 2; + string parameters = 3; + } + + // Only added to maintain feature parity when migrating from the handwritten + // Bigtable Table Admin class to the generated class. While some attempts were + // made to generalize this feature, it currently only supports the + // WaitForConsistency method. This functionality can be enhanced later if we + // ever need to use this again. + // The implementation for this method in the ConnectionImpl class is not + // generated and must be handwritten in a separate .cc file. + repeated BespokeMethod bespoke_methods = 30; } message DiscoveryDocumentDefinedProduct { diff --git a/generator/integration_tests/golden/v1/golden_rest_only_client.cc b/generator/integration_tests/golden/v1/golden_rest_only_client.cc index 59b55bebb84bf..321cb26e741d7 100644 --- a/generator/integration_tests/golden/v1/golden_rest_only_client.cc +++ b/generator/integration_tests/golden/v1/golden_rest_only_client.cc @@ -38,6 +38,11 @@ GoldenRestOnlyClient::Noop(google::protobuf::Empty const& request, Options opts) return connection_->Noop(request); } +StatusOr GoldenRestOnlyClient::WaitForConsistency(google::protobuf::Empty const& request, Options opts) { + internal::OptionsSpan span(internal::MergeOptions(std::move(opts), options_)); + return connection_->WaitForConsistency(request); +} + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace golden_v1 } // namespace cloud diff --git a/generator/integration_tests/golden/v1/golden_rest_only_client.h b/generator/integration_tests/golden/v1/golden_rest_only_client.h index 8da2941554009..5b299490d2335 100644 --- a/generator/integration_tests/golden/v1/golden_rest_only_client.h +++ b/generator/integration_tests/golden/v1/golden_rest_only_client.h @@ -109,6 +109,37 @@ class GoldenRestOnlyClient { Status Noop(google::protobuf::Empty const& request, Options opts = {}); + + // clang-format off + /// + /// Polls a table until it is consistent or the RetryPolicy is exhausted based + /// on a consistency token, that is, if replication has caught up based on the + /// provided conditions specified in the token and the check request. + /// + /// @param request Unary RPCs, such as the one wrapped by this + /// function, receive a single `request` proto message which includes all + /// the inputs for the RPC. In this case, the proto message is a + /// [google.bigtable.admin.v2.CheckConsistencyRequest]. + /// Proto messages are converted to C++ classes by Protobuf, using the + /// [Protobuf mapping rules]. + /// @param opts Optional. Override the class-level options, such as retry and + /// backoff policies. + /// @return the result of the RPC. The response message type + /// ([google.bigtable.admin.v2.CheckConsistencyResponse]) + /// is mapped to a C++ class using the [Protobuf mapping rules]. + /// If the request fails, the [`StatusOr`] contains the error details. + /// + /// [Protobuf mapping rules]: https://protobuf.dev/reference/cpp/cpp-generated/ + /// [input iterator requirements]: https://en.cppreference.com/w/cpp/named_req/InputIterator + /// [`std::string`]: https://en.cppreference.com/w/cpp/string/basic_string + /// [`future`]: @ref google::cloud::future + /// [`StatusOr`]: @ref google::cloud::StatusOr + /// [`Status`]: @ref google::cloud::Status + /// [google.bigtable.admin.v2.CheckConsistencyRequest]: @googleapis_reference_link{google/bigtable/admin/v2/bigtable_table_admin.proto#L909} + /// [google.bigtable.admin.v2.CheckConsistencyResponse]: @googleapis_reference_link{google/bigtable/admin/v2/bigtable_table_admin.proto#L948} + /// + // clang-format on +StatusOr WaitForConsistency(google::protobuf::Empty const& request, Options opts = {}); private: std::shared_ptr connection_; Options options_; diff --git a/generator/integration_tests/golden/v1/golden_rest_only_connection.cc b/generator/integration_tests/golden/v1/golden_rest_only_connection.cc index 4c88d9c0b35f9..44651b4a554e0 100644 --- a/generator/integration_tests/golden/v1/golden_rest_only_connection.cc +++ b/generator/integration_tests/golden/v1/golden_rest_only_connection.cc @@ -41,6 +41,11 @@ GoldenRestOnlyConnection::Noop( return Status(StatusCode::kUnimplemented, "not implemented"); } +StatusOr GoldenRestOnlyConnection::WaitForConsistency(google::protobuf::Empty const&) { + return StatusOr( + Status(StatusCode::kUnimplemented, "not implemented")); +} + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace golden_v1 } // namespace cloud diff --git a/generator/integration_tests/golden/v1/golden_rest_only_connection.h b/generator/integration_tests/golden/v1/golden_rest_only_connection.h index 283afea78c8d1..d485875264e1b 100644 --- a/generator/integration_tests/golden/v1/golden_rest_only_connection.h +++ b/generator/integration_tests/golden/v1/golden_rest_only_connection.h @@ -176,6 +176,8 @@ class GoldenRestOnlyConnection { virtual Status Noop(google::protobuf::Empty const& request); + + virtual StatusOr WaitForConsistency(google::protobuf::Empty const& request); }; GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/generator/integration_tests/golden_config.textproto b/generator/integration_tests/golden_config.textproto index 9ba74a94ccc3e..894e152e87be0 100644 --- a/generator/integration_tests/golden_config.textproto +++ b/generator/integration_tests/golden_config.textproto @@ -59,6 +59,13 @@ service { ] override_service_config_yaml_name: "generator/integration_tests/test2.yaml" endpoint_location_style: LOCATION_OPTIONALLY_DEPENDENT + bespoke_methods : [ + { + name: "WaitForConsistency", + return_type: "StatusOr", + parameters: "(google::protobuf::Empty const& request, Options opts = {})" + } + ] } service { diff --git a/generator/internal/client_generator.cc b/generator/internal/client_generator.cc index 2e81d9431f5a6..c2585c255ee97 100644 --- a/generator/internal/client_generator.cc +++ b/generator/internal/client_generator.cc @@ -21,12 +21,51 @@ #include "generator/internal/predicate_utils.h" #include "generator/internal/printer.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_replace.h" #include "google/api/client.pb.h" #include namespace google { namespace cloud { namespace generator_internal { +namespace { +std::string FormatBespokeMethodComments(std::string const& method_name) { + if (method_name == "WaitForConsistency") { + return R"""( + // clang-format off + /// + /// Polls a table until it is consistent or the RetryPolicy is exhausted based + /// on a consistency token, that is, if replication has caught up based on the + /// provided conditions specified in the token and the check request. + /// + /// @param request Unary RPCs, such as the one wrapped by this + /// function, receive a single `request` proto message which includes all + /// the inputs for the RPC. In this case, the proto message is a + /// [google.bigtable.admin.v2.CheckConsistencyRequest]. + /// Proto messages are converted to C++ classes by Protobuf, using the + /// [Protobuf mapping rules]. + /// @param opts Optional. Override the class-level options, such as retry and + /// backoff policies. + /// @return the result of the RPC. The response message type + /// ([google.bigtable.admin.v2.CheckConsistencyResponse]) + /// is mapped to a C++ class using the [Protobuf mapping rules]. + /// If the request fails, the [`StatusOr`] contains the error details. + /// + /// [Protobuf mapping rules]: https://protobuf.dev/reference/cpp/cpp-generated/ + /// [input iterator requirements]: https://en.cppreference.com/w/cpp/named_req/InputIterator + /// [`std::string`]: https://en.cppreference.com/w/cpp/string/basic_string + /// [`future`]: @ref google::cloud::future + /// [`StatusOr`]: @ref google::cloud::StatusOr + /// [`Status`]: @ref google::cloud::Status + /// [google.bigtable.admin.v2.CheckConsistencyRequest]: @googleapis_reference_link{google/bigtable/admin/v2/bigtable_table_admin.proto#L909} + /// [google.bigtable.admin.v2.CheckConsistencyResponse]: @googleapis_reference_link{google/bigtable/admin/v2/bigtable_table_admin.proto#L948} + /// + // clang-format on +)"""; + } + return ""; +} +} // namespace ClientGenerator::ClientGenerator( google::protobuf::ServiceDescriptor const* service_descriptor, @@ -380,6 +419,13 @@ R"""( std::unique_ptr<::google::cloud::AsyncStreamingReadWriteRpc< __FILE__, __LINE__); } + for (auto const& method : bespoke_methods()) { + HeaderPrint("\n"); + HeaderPrint(FormatBespokeMethodComments(method.name())); + HeaderPrint(absl::StrCat(method.return_type(), " ", method.name(), + method.parameters(), ";")); + } + HeaderPrint( // clang-format off "\n" " private:\n" @@ -716,6 +762,19 @@ std::unique_ptr<::google::cloud::AsyncStreamingReadWriteRpc< __FILE__, __LINE__); } + for (auto const& method : bespoke_methods()) { + CcPrint("\n"); + CcPrint(absl::StrCat( + method.return_type(), R"""( $client_class_name$::)""", method.name(), + absl::StrReplaceAll(method.parameters(), {{" = {}", ""}}), + absl::StrFormat(R"""( { + internal::OptionsSpan span(internal::MergeOptions(std::move(opts), options_)); + return connection_->%s(request); +} +)""", + method.name()))); + } + CcCloseNamespaces(); return {}; } diff --git a/generator/internal/codegen_utils.cc b/generator/internal/codegen_utils.cc index 5be555f1918e2..3e5be23b867d1 100644 --- a/generator/internal/codegen_utils.cc +++ b/generator/internal/codegen_utils.cc @@ -111,6 +111,11 @@ void ProcessArgOmitRpc( ProcessRepeated("omit_rpc", "omitted_rpcs", command_line_args); } +void ProcessArgBespokeMethod( + std::vector>& command_line_args) { + ProcessRepeated("bespoke_method", "bespoke_methods", command_line_args); +} + void ProcessArgServiceEndpointEnvVar( std::vector>& command_line_args) { auto service_endpoint_env_var = @@ -269,6 +274,7 @@ ProcessCommandLineArgs(std::string const& parameters) { ProcessArgCopyrightYear(command_line_args); ProcessArgOmitService(command_line_args); ProcessArgOmitRpc(command_line_args); + ProcessArgBespokeMethod(command_line_args); ProcessArgServiceEndpointEnvVar(command_line_args); ProcessArgEmulatorEndpointEnvVar(command_line_args); ProcessArgEndpointLocationStyle(command_line_args); diff --git a/generator/internal/connection_generator.cc b/generator/internal/connection_generator.cc index 65c34c7f855cf..b45cc3d46b638 100644 --- a/generator/internal/connection_generator.cc +++ b/generator/internal/connection_generator.cc @@ -19,6 +19,7 @@ #include "generator/internal/pagination.h" #include "generator/internal/predicate_utils.h" #include "generator/internal/printer.h" +#include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" #include @@ -315,6 +316,14 @@ class $connection_class_name$ { __FILE__, __LINE__); } + for (auto const& method : bespoke_methods()) { + HeaderPrint("\n"); + HeaderPrint(absl::StrCat( + " virtual ", method.return_type(), " ", method.name(), + absl::StrReplaceAll(method.parameters(), {{", Options opts = {}", ""}}), + ";\n")); + } + // close abstract interface Connection base class HeaderPrint("};\n"); @@ -490,6 +499,26 @@ future> __FILE__, __LINE__); } + for (auto const& method : bespoke_methods()) { + CcPrint("\n"); + std::string make_return = + absl::StrContains(method.return_type(), "future") + ? absl::StrCat("google::cloud::make_ready_", method.return_type()) + : method.return_type(); + + CcPrint( + absl::StrCat(method.return_type(), R"""( $connection_class_name$::)""", + method.name(), + absl::StrReplaceAll(method.parameters(), + {{" request, Options opts = {}", ""}}), + " {\n", + absl::StrFormat(R"""( return %s( + Status(StatusCode::kUnimplemented, "not implemented")); +} +)""", + make_return))); + } + if (HasGenerateGrpcTransport()) { EmitFactoryFunctionDefinition(EndpointLocationStyle()); } diff --git a/generator/internal/connection_impl_generator.cc b/generator/internal/connection_impl_generator.cc index b1cac8680e984..cb1ac65aa51a7 100644 --- a/generator/internal/connection_impl_generator.cc +++ b/generator/internal/connection_impl_generator.cc @@ -19,6 +19,7 @@ #include "generator/internal/predicate_utils.h" #include "generator/internal/printer.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_replace.h" #include namespace google { @@ -120,6 +121,14 @@ class $connection_class_name$Impl HeaderPrintMethod(method, __FILE__, __LINE__, AsyncMethodDeclaration()); } + for (auto const& method : bespoke_methods()) { + HeaderPrint("\n"); + HeaderPrint(absl::StrCat( + method.return_type(), " ", method.name(), + absl::StrReplaceAll(method.parameters(), {{", Options opts = {}", ""}}), + " override;")); + } + HeaderPrint(R"""( private: std::unique_ptr background_; diff --git a/generator/internal/service_code_generator.cc b/generator/internal/service_code_generator.cc index 8f9f22f226699..6295df3bc6581 100644 --- a/generator/internal/service_code_generator.cc +++ b/generator/internal/service_code_generator.cc @@ -482,6 +482,20 @@ void ServiceCodeGenerator::SetMethods() { for (auto const& mixin_method : mixin_methods_) { methods_.emplace_back(mixin_method.method.get()); } + + auto bespoke_methods_var = service_vars_.find("bespoke_methods"); + if (bespoke_methods_var != service_vars_.end()) { + auto methods = absl::StrSplit(bespoke_methods_var->second, ','); + for (auto const& method : methods) { + std::vector pieces = absl::StrSplit(method, "@@"); + assert(pieces.size() == 3); + cpp::generator::ServiceConfiguration::BespokeMethod bespoke_method; + bespoke_method.set_name(SafeReplaceAll(pieces[0], "@", ",")); + bespoke_method.set_return_type(SafeReplaceAll(pieces[1], "@", ",")); + bespoke_method.set_parameters(SafeReplaceAll(pieces[2], "@", ",")); + bespoke_methods_.emplace_back(std::move(bespoke_method)); + } + } } std::string ServiceCodeGenerator::GetPbIncludeByTransport() const { diff --git a/generator/internal/service_code_generator.h b/generator/internal/service_code_generator.h index 691140d74b274..c267430e08397 100644 --- a/generator/internal/service_code_generator.h +++ b/generator/internal/service_code_generator.h @@ -68,6 +68,10 @@ class ServiceCodeGenerator : public GeneratorInterface { std::string vars(std::string const& key) const; MethodDescriptorList const& methods() const { return methods_; } MethodDescriptorList const& async_methods() const { return async_methods_; } + std::vector const& + bespoke_methods() const { + return bespoke_methods_; + } void SetVars(absl::string_view header_path); VarsDictionary MergeServiceAndMethodVars( google::protobuf::MethodDescriptor const& method) const; @@ -268,6 +272,8 @@ class ServiceCodeGenerator : public GeneratorInterface { bool pb_h_system_includes_ = false; MethodDescriptorList methods_; MethodDescriptorList async_methods_; + std::vector + bespoke_methods_; Printer header_; Printer cc_; std::vector mixin_methods_; diff --git a/generator/standalone_main.cc b/generator/standalone_main.cc index af80d03c3f031..f94a944d760b1 100644 --- a/generator/standalone_main.cc +++ b/generator/standalone_main.cc @@ -281,6 +281,14 @@ std::vector> GenerateCodeFromProtos( args.emplace_back(absl::StrCat("--cpp_codegen_opt=omit_rpc=", SafeReplaceAll(omit_rpc, ",", "@"))); } + for (auto const& bespoke_method : service.bespoke_methods()) { + args.emplace_back(absl::StrCat( + "--cpp_codegen_opt=bespoke_method=", + absl::StrJoin({SafeReplaceAll(bespoke_method.name(), ",", "@"), + SafeReplaceAll(bespoke_method.return_type(), ",", "@"), + SafeReplaceAll(bespoke_method.parameters(), ",", "@")}, + "@@"))); + } for (auto const& retry_code : service.retryable_status_codes()) { args.emplace_back("--cpp_codegen_opt=retry_status_code=" + retry_code); }