diff --git a/.gcb/builds/triggers/main.tf b/.gcb/builds/triggers/main.tf index 7af2eaadbb..cbcb36617e 100644 --- a/.gcb/builds/triggers/main.tf +++ b/.gcb/builds/triggers/main.tf @@ -40,6 +40,8 @@ locals { "--cfg google_cloud_unstable_grpc_server_streaming", "--cfg google_cloud_unstable_tracing", "--cfg google_cloud_unstable_grpc_rust", + "--cfg google_cloud_unstable_bigquery_arrow", + "--cfg google_cloud_unstable_bigquery_storage_read", ]) tokio_unstable_flags = "--cfg tokio_unstable" diff --git a/.github/workflows/sdk.yaml b/.github/workflows/sdk.yaml index d8d37e9904..5d259a1455 100644 --- a/.github/workflows/sdk.yaml +++ b/.github/workflows/sdk.yaml @@ -25,7 +25,7 @@ env: GHA_RUST_VERSIONS: '{ "rust:current": "1.98" }' GHA_GO_VERSIONS: '{ "go:current": "1.26.7" }' # Define the unstable flags once - UNSTABLE_CFGS: &unstable_cfgs '--cfg google_cloud_unstable_trust_boundaries --cfg google_cloud_unstable_storage_bidi --cfg google_cloud_unstable_grpc_server_streaming --cfg google_cloud_unstable_tracing' + UNSTABLE_CFGS: &unstable_cfgs '--cfg google_cloud_unstable_trust_boundaries --cfg google_cloud_unstable_storage_bidi --cfg google_cloud_unstable_grpc_server_streaming --cfg google_cloud_unstable_tracing --cfg google_cloud_unstable_bigquery_arrow --cfg google_cloud_unstable_bigquery_storage_read' jobs: build: diff --git a/Cargo.lock b/Cargo.lock index 91a2950820..cd217e23c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5681,20 +5681,7 @@ dependencies = [ [[package]] name = "google-cloud-timeseriesinsights-v1" -version = "1.13.0" -dependencies = [ - "anyhow", - "async-trait", - "bytes", - "google-cloud-gax", - "google-cloud-gax-internal", - "google-cloud-rpc", - "google-cloud-wkt", - "serde", - "serde_json", - "serde_with", - "tracing", -] +version = "2.0.0" [[package]] name = "google-cloud-tpu-v2" diff --git a/Cargo.toml b/Cargo.toml index 5a4e509bcb..45e3014efb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -614,6 +614,8 @@ storage-samples = { path = "src/storage/examples" } [workspace.lints.rust] unexpected_cfgs = { level = "deny", check-cfg = [ + 'cfg(google_cloud_unstable_bigquery_arrow)', + 'cfg(google_cloud_unstable_bigquery_storage_read)', 'cfg(google_cloud_unstable_grpc_rust)', 'cfg(google_cloud_unstable_grpc_server_streaming)', 'cfg(google_cloud_unstable_storage_bidi)', diff --git a/librarian.yaml b/librarian.yaml index 57e6d0ee93..24de7dd567 100644 --- a/librarian.yaml +++ b/librarian.yaml @@ -1600,6 +1600,7 @@ libraries: - .google.storage.v2.ReadObjectRequest - .google.storage.v2.WriteObjectSpec include_grpc_only_methods: true + idempotency_hook: resolve_idempotency name_overrides: .google.storage.v2.Storage=StorageControl output: src/storage/src/generated/gapic routing_required: true @@ -1686,6 +1687,10 @@ libraries: - name: google-cloud-texttospeech-v1 version: 1.16.0 copyright_year: "2025" + - name: google-cloud-timeseriesinsights-v1 + version: 2.0.0 + copyright_year: "2025" + output: src/generated/cloud/timeseriesinsights/v1 - name: google-cloud-tpu-v2 version: 1.15.0 copyright_year: "2025" diff --git a/src/gax-internal/grpc-server/src/lib.rs b/src/gax-internal/grpc-server/src/lib.rs index f3ccd25df9..f9f3669aa2 100644 --- a/src/gax-internal/grpc-server/src/lib.rs +++ b/src/gax-internal/grpc-server/src/lib.rs @@ -48,7 +48,10 @@ pub async fn start_echo_server_with_address( let stream = tokio_stream::wrappers::TcpListenerStream::new(listener); let _ = Server::builder() - .add_service(google::test::v1::echo_service_server::EchoServiceServer::new(echo)) + .add_service( + google::test::v1::echo_service_server::EchoServiceServer::new(echo) + .max_decoding_message_size(usize::MAX), + ) .serve_with_incoming(stream) .await; }); diff --git a/src/gax-internal/src/grpc.rs b/src/gax-internal/src/grpc.rs index 6b1bb881ed..76649e2bb0 100644 --- a/src/gax-internal/src/grpc.rs +++ b/src/gax-internal/src/grpc.rs @@ -654,7 +654,7 @@ impl Client { instrumentation, }; - let inner_client = InnerClient::new(channel); + let inner_client = InnerClient::new(channel).max_decoding_message_size(usize::MAX); if tracing_enabled { Ok((inner_client, Some(attrs))) } else { diff --git a/src/gax-internal/tests/grpc_server_streaming_request.rs b/src/gax-internal/tests/grpc_server_streaming_request.rs index 5a5b8bd67c..1ff49a698a 100644 --- a/src/gax-internal/tests/grpc_server_streaming_request.rs +++ b/src/gax-internal/tests/grpc_server_streaming_request.rs @@ -345,4 +345,24 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn large_response() -> anyhow::Result<()> { + let (endpoint, _server) = start_echo_server().await?; + let client = builder(endpoint) + .with_credentials(test_credentials()) + .build() + .await?; + + // Tonic's default decoding limit is 4 MiB; verify streamed messages > 4 MiB succeed. + let large_msg = "a".repeat(5 * 1024 * 1024); + let response = + send_server_streaming_request(client.clone(), &large_msg, "resource=test").await?; + let (_metadata, mut stream, _extensions) = response.into_parts(); + let r = stream.message().await?.expect("expected a message"); + assert_eq!(r.message.len(), large_msg.len()); + assert!(stream.message().await?.is_none()); + + Ok(()) + } } diff --git a/src/gax-internal/tests/grpc_simple_request.rs b/src/gax-internal/tests/grpc_simple_request.rs index a3c22cee0e..940d071a7f 100644 --- a/src/gax-internal/tests/grpc_simple_request.rs +++ b/src/gax-internal/tests/grpc_simple_request.rs @@ -659,4 +659,19 @@ mod tests { assert!(!header.contains("gapic/"), "{header}"); Ok(()) } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn large_response() -> anyhow::Result<()> { + let (endpoint, _server) = start_echo_server().await?; + + let client = builder(endpoint) + .with_credentials(test_credentials()) + .build() + .await?; + // Tonic's default decoding limit is 4 MiB; verify messages > 4 MiB succeed. + let large_msg = "a".repeat(5 * 1024 * 1024); + let response = send_request(client, &large_msg, "").await?; + assert_eq!(response.message.len(), large_msg.len()); + Ok(()) + } } diff --git a/src/gax-internal/tests/grpc_streaming_request.rs b/src/gax-internal/tests/grpc_streaming_request.rs index aa6c446ad5..dc0020b7b8 100644 --- a/src/gax-internal/tests/grpc_streaming_request.rs +++ b/src/gax-internal/tests/grpc_streaming_request.rs @@ -357,4 +357,27 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn large_response() -> anyhow::Result<()> { + let (endpoint, _server) = start_echo_server().await?; + let client = builder(endpoint) + .with_credentials(test_credentials()) + .build() + .await?; + + // Tonic's default decoding limit is 4 MiB; verify streamed messages > 4 MiB succeed. + let large_msg = "a".repeat(5 * 1024 * 1024); + let (tx, rx) = tokio::sync::mpsc::channel(100); + tx.send(simple_request(&large_msg)).await?; + drop(tx); + + let response = send_streaming_request(client.clone(), rx, "resource=test").await?; + let (_metadata, mut stream, _) = response.into_parts(); + let r = stream.message().await?.expect("expected a message"); + assert_eq!(r.message.len(), large_msg.len()); + assert!(stream.message().await?.is_none()); + + Ok(()) + } } diff --git a/src/generated/cloud/timeseriesinsights/v1/.repo-metadata.json b/src/generated/cloud/timeseriesinsights/v1/.repo-metadata.json deleted file mode 100644 index 86147370f3..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/.repo-metadata.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "api_description": "Provides a Timeseries Insights service which operates over time series\ndata. Users can perform time series spike, trend, and anomaly detection.\nWith a straightforward API and easy to understand results, the service\nmakes it simple to gather insights from large amounts of time series data\n(e.g. monitoring datasets) and integrate these insights in their\napplications.", - "api_id": "timeseriesinsights.googleapis.com", - "api_shortname": "timeseriesinsights", - "client_documentation": "https://docs.rs/google-cloud-timeseriesinsights-v1/latest", - "distribution_name": "google-cloud-timeseriesinsights-v1", - "language": "rust", - "library_type": "GAPIC_AUTO", - "name": "timeseriesinsights", - "name_pretty": "Timeseries Insights", - "release_level": "stable", - "repo": "googleapis/google-cloud-rust" -} \ No newline at end of file diff --git a/src/generated/cloud/timeseriesinsights/v1/Cargo.toml b/src/generated/cloud/timeseriesinsights/v1/Cargo.toml index 1d0fcdc18f..eef6f6dda7 100644 --- a/src/generated/cloud/timeseriesinsights/v1/Cargo.toml +++ b/src/generated/cloud/timeseriesinsights/v1/Cargo.toml @@ -11,13 +11,11 @@ # 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. -# -# Code generated by sidekick. DO NOT EDIT. [package] name = "google-cloud-timeseriesinsights-v1" -version = "1.13.0" -description = "Google Cloud Client Libraries for Rust - Timeseries Insights API" +version = "2.0.0" +description = "Google Cloud Client Libraries for Rust - Timeseries Insights API (Decommissioned)" edition.workspace = true authors.workspace = true license.workspace = true @@ -29,28 +27,5 @@ rust-version.workspace = true [lints] workspace = true -[features] -default = ["default-rustls-provider"] -# Enabled by default. Use the default rustls crypto provider ([aws-lc-rs]) for -# TLS and authentication. Applications with specific requirements for -# cryptography (such as exclusively using the [ring] crate) should disable this -# default and call `rustls::crypto::CryptoProvider::install_default()`. -default-rustls-provider = ["gaxi/_default-rustls-provider"] - [package.metadata.docs.rs] all-features = true - -[dependencies] -async-trait.workspace = true -bytes.workspace = true -gaxi = { workspace = true, features = ["_internal-http-client"] } -google-cloud-gax.workspace = true -google-cloud-rpc.workspace = true -serde.workspace = true -serde_json.workspace = true -serde_with.workspace = true -tracing.workspace = true -wkt.workspace = true - -[dev-dependencies] -anyhow.workspace = true diff --git a/src/generated/cloud/timeseriesinsights/v1/README.md b/src/generated/cloud/timeseriesinsights/v1/README.md index 19363d1351..e562ab2961 100644 --- a/src/generated/cloud/timeseriesinsights/v1/README.md +++ b/src/generated/cloud/timeseriesinsights/v1/README.md @@ -1,38 +1,7 @@ # Google Cloud Client Libraries for Rust - Timeseries Insights API - +This service is decommissioned. The package will see no more updates. -Provides a Timeseries Insights service which operates over time series -data. Users can perform time series spike, trend, and anomaly detection. -With a straightforward API and easy to understand results, the service -makes it simple to gather insights from large amounts of time series data -(e.g. monitoring datasets) and integrate these insights in their -applications. +## License -The client library types and functions are stable and not expected to change. -Please note that Google Cloud services do change from time to time. The client -libraries are designed to preserve backwards compatibility when the service -changes in compatible ways. For example, adding RPCs, or fields to messages -should not introduce breaking changes to the client libraries. - -## Quickstart - -The main types to work with this crate are the clients: - -- [TimeseriesInsightsController] - -## Features - -- `default-rustls-provider`: enabled by default. Use the default rustls crypto - provider ([aws-lc-rs]) for TLS and authentication. Applications with specific - requirements for cryptography (such as exclusively using the [ring] crate) - should disable this default and call - `rustls::crypto::CryptoProvider::install_default()`. - -## More Information - -- Read the [crate's documentation](https://docs.rs/google-cloud-timeseriesinsights-v1/1.13.0) - -[aws-lc-rs]: https://crates.io/crates/aws-lc-rs -[ring]: https://crates.io/crates/ring -[TimeseriesInsightsController]: https://docs.rs/google-cloud-timeseriesinsights-v1/1.13.0/google_cloud_timeseriesinsights_v1/client/struct.TimeseriesInsightsController.html +Apache-2.0 diff --git a/src/generated/cloud/timeseriesinsights/v1/src/builder.rs b/src/generated/cloud/timeseriesinsights/v1/src/builder.rs deleted file mode 100644 index 9f21d142e9..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/src/builder.rs +++ /dev/null @@ -1,810 +0,0 @@ -// Copyright 2025 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 -// -// https://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. -// -// Code generated by sidekick. DO NOT EDIT. - -/// Request and client builders for [TimeseriesInsightsController][crate::client::TimeseriesInsightsController]. -pub mod timeseries_insights_controller { - use crate::Result; - - /// A builder for [TimeseriesInsightsController][crate::client::TimeseriesInsightsController]. - /// - /// ``` - /// # async fn sample() -> google_cloud_gax::client_builder::Result<()> { - /// # use google_cloud_timeseriesinsights_v1::*; - /// # use builder::timeseries_insights_controller::ClientBuilder; - /// # use client::TimeseriesInsightsController; - /// let builder : ClientBuilder = TimeseriesInsightsController::builder(); - /// let client = builder - /// .with_endpoint("https://timeseriesinsights.googleapis.com") - /// .build().await?; - /// # Ok(()) } - /// ``` - pub type ClientBuilder = crate::ClientBuilder; - - pub(crate) mod client { - use super::super::super::client::TimeseriesInsightsController; - pub struct Factory; - impl crate::ClientFactory for Factory { - type Client = TimeseriesInsightsController; - type Credentials = gaxi::options::Credentials; - async fn build( - self, - config: gaxi::options::ClientConfig, - ) -> crate::ClientBuilderResult { - Self::Client::new(config).await - } - } - } - - /// Common implementation for [crate::client::TimeseriesInsightsController] request builders. - #[derive(Clone, Debug)] - pub(crate) struct RequestBuilder { - stub: std::sync::Arc, - request: R, - options: crate::RequestOptions, - } - - impl RequestBuilder - where - R: std::default::Default, - { - pub(crate) fn new( - stub: std::sync::Arc, - ) -> Self { - Self { - stub, - request: R::default(), - options: crate::RequestOptions::default(), - } - } - } - - /// The request builder for [TimeseriesInsightsController::list_data_sets][crate::client::TimeseriesInsightsController::list_data_sets] calls. - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::builder::timeseries_insights_controller::ListDataSets; - /// # async fn sample() -> google_cloud_timeseriesinsights_v1::Result<()> { - /// use google_cloud_gax::paginator::ItemPaginator; - /// - /// let builder = prepare_request_builder(); - /// let mut items = builder.by_item(); - /// while let Some(result) = items.next().await { - /// let item = result?; - /// } - /// # Ok(()) } - /// - /// fn prepare_request_builder() -> ListDataSets { - /// # panic!(); - /// // ... details omitted ... - /// } - /// ``` - #[derive(Clone, Debug)] - pub struct ListDataSets(RequestBuilder); - - impl ListDataSets { - pub(crate) fn new( - stub: std::sync::Arc, - ) -> Self { - Self(RequestBuilder::new(stub)) - } - - /// Sets the full request, replacing any prior values. - pub fn with_request>(mut self, v: V) -> Self { - self.0.request = v.into(); - self - } - - /// Sets all the options, replacing any prior values. - pub fn with_options>(mut self, v: V) -> Self { - self.0.options = v.into(); - self - } - - /// Sends the request. - pub async fn send(self) -> Result { - (*self.0.stub) - .list_data_sets(self.0.request, self.0.options) - .await - .map(crate::Response::into_body) - } - - /// Streams each page in the collection. - pub fn by_page( - self, - ) -> impl google_cloud_gax::paginator::Paginator - { - use std::clone::Clone; - let token = self.0.request.page_token.clone(); - let execute = move |token: String| { - let mut builder = self.clone(); - builder.0.request = builder.0.request.set_page_token(token); - builder.send() - }; - google_cloud_gax::paginator::internal::new_paginator(token, execute) - } - - /// Streams each item in the collection. - pub fn by_item( - self, - ) -> impl google_cloud_gax::paginator::ItemPaginator< - crate::model::ListDataSetsResponse, - crate::Error, - > { - use google_cloud_gax::paginator::Paginator; - self.by_page().items() - } - - /// Sets the value of [parent][crate::model::ListDataSetsRequest::parent]. - /// - /// This is a **required** field for requests. - pub fn set_parent>(mut self, v: T) -> Self { - self.0.request.parent = v.into(); - self - } - - /// Sets the value of [page_size][crate::model::ListDataSetsRequest::page_size]. - pub fn set_page_size>(mut self, v: T) -> Self { - self.0.request.page_size = v.into(); - self - } - - /// Sets the value of [page_token][crate::model::ListDataSetsRequest::page_token]. - pub fn set_page_token>(mut self, v: T) -> Self { - self.0.request.page_token = v.into(); - self - } - } - - #[doc(hidden)] - impl crate::RequestBuilder for ListDataSets { - fn request_options(&mut self) -> &mut crate::RequestOptions { - &mut self.0.options - } - } - - /// The request builder for [TimeseriesInsightsController::create_data_set][crate::client::TimeseriesInsightsController::create_data_set] calls. - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::builder::timeseries_insights_controller::CreateDataSet; - /// # async fn sample() -> google_cloud_timeseriesinsights_v1::Result<()> { - /// - /// let builder = prepare_request_builder(); - /// let response = builder.send().await?; - /// # Ok(()) } - /// - /// fn prepare_request_builder() -> CreateDataSet { - /// # panic!(); - /// // ... details omitted ... - /// } - /// ``` - #[derive(Clone, Debug)] - pub struct CreateDataSet(RequestBuilder); - - impl CreateDataSet { - pub(crate) fn new( - stub: std::sync::Arc, - ) -> Self { - Self(RequestBuilder::new(stub)) - } - - /// Sets the full request, replacing any prior values. - pub fn with_request>(mut self, v: V) -> Self { - self.0.request = v.into(); - self - } - - /// Sets all the options, replacing any prior values. - pub fn with_options>(mut self, v: V) -> Self { - self.0.options = v.into(); - self - } - - /// Sends the request. - pub async fn send(self) -> Result { - (*self.0.stub) - .create_data_set(self.0.request, self.0.options) - .await - .map(crate::Response::into_body) - } - - /// Sets the value of [parent][crate::model::CreateDataSetRequest::parent]. - /// - /// This is a **required** field for requests. - pub fn set_parent>(mut self, v: T) -> Self { - self.0.request.parent = v.into(); - self - } - - /// Sets the value of [dataset][crate::model::CreateDataSetRequest::dataset]. - /// - /// This is a **required** field for requests. - pub fn set_dataset(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.dataset = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [dataset][crate::model::CreateDataSetRequest::dataset]. - /// - /// This is a **required** field for requests. - pub fn set_or_clear_dataset(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.dataset = v.map(|x| x.into()); - self - } - } - - #[doc(hidden)] - impl crate::RequestBuilder for CreateDataSet { - fn request_options(&mut self) -> &mut crate::RequestOptions { - &mut self.0.options - } - } - - /// The request builder for [TimeseriesInsightsController::delete_data_set][crate::client::TimeseriesInsightsController::delete_data_set] calls. - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::builder::timeseries_insights_controller::DeleteDataSet; - /// # async fn sample() -> google_cloud_timeseriesinsights_v1::Result<()> { - /// - /// let builder = prepare_request_builder(); - /// let response = builder.send().await?; - /// # Ok(()) } - /// - /// fn prepare_request_builder() -> DeleteDataSet { - /// # panic!(); - /// // ... details omitted ... - /// } - /// ``` - #[derive(Clone, Debug)] - pub struct DeleteDataSet(RequestBuilder); - - impl DeleteDataSet { - pub(crate) fn new( - stub: std::sync::Arc, - ) -> Self { - Self(RequestBuilder::new(stub)) - } - - /// Sets the full request, replacing any prior values. - pub fn with_request>(mut self, v: V) -> Self { - self.0.request = v.into(); - self - } - - /// Sets all the options, replacing any prior values. - pub fn with_options>(mut self, v: V) -> Self { - self.0.options = v.into(); - self - } - - /// Sends the request. - pub async fn send(self) -> Result<()> { - (*self.0.stub) - .delete_data_set(self.0.request, self.0.options) - .await - .map(crate::Response::into_body) - } - - /// Sets the value of [name][crate::model::DeleteDataSetRequest::name]. - /// - /// This is a **required** field for requests. - pub fn set_name>(mut self, v: T) -> Self { - self.0.request.name = v.into(); - self - } - } - - #[doc(hidden)] - impl crate::RequestBuilder for DeleteDataSet { - fn request_options(&mut self) -> &mut crate::RequestOptions { - &mut self.0.options - } - } - - /// The request builder for [TimeseriesInsightsController::append_events][crate::client::TimeseriesInsightsController::append_events] calls. - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::builder::timeseries_insights_controller::AppendEvents; - /// # async fn sample() -> google_cloud_timeseriesinsights_v1::Result<()> { - /// - /// let builder = prepare_request_builder(); - /// let response = builder.send().await?; - /// # Ok(()) } - /// - /// fn prepare_request_builder() -> AppendEvents { - /// # panic!(); - /// // ... details omitted ... - /// } - /// ``` - #[derive(Clone, Debug)] - pub struct AppendEvents(RequestBuilder); - - impl AppendEvents { - pub(crate) fn new( - stub: std::sync::Arc, - ) -> Self { - Self(RequestBuilder::new(stub)) - } - - /// Sets the full request, replacing any prior values. - pub fn with_request>(mut self, v: V) -> Self { - self.0.request = v.into(); - self - } - - /// Sets all the options, replacing any prior values. - pub fn with_options>(mut self, v: V) -> Self { - self.0.options = v.into(); - self - } - - /// Sends the request. - pub async fn send(self) -> Result { - (*self.0.stub) - .append_events(self.0.request, self.0.options) - .await - .map(crate::Response::into_body) - } - - /// Sets the value of [events][crate::model::AppendEventsRequest::events]. - pub fn set_events(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.0.request.events = v.into_iter().map(|i| i.into()).collect(); - self - } - - /// Sets the value of [dataset][crate::model::AppendEventsRequest::dataset]. - /// - /// This is a **required** field for requests. - pub fn set_dataset>(mut self, v: T) -> Self { - self.0.request.dataset = v.into(); - self - } - } - - #[doc(hidden)] - impl crate::RequestBuilder for AppendEvents { - fn request_options(&mut self) -> &mut crate::RequestOptions { - &mut self.0.options - } - } - - /// The request builder for [TimeseriesInsightsController::query_data_set][crate::client::TimeseriesInsightsController::query_data_set] calls. - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::builder::timeseries_insights_controller::QueryDataSet; - /// # async fn sample() -> google_cloud_timeseriesinsights_v1::Result<()> { - /// - /// let builder = prepare_request_builder(); - /// let response = builder.send().await?; - /// # Ok(()) } - /// - /// fn prepare_request_builder() -> QueryDataSet { - /// # panic!(); - /// // ... details omitted ... - /// } - /// ``` - #[derive(Clone, Debug)] - pub struct QueryDataSet(RequestBuilder); - - impl QueryDataSet { - pub(crate) fn new( - stub: std::sync::Arc, - ) -> Self { - Self(RequestBuilder::new(stub)) - } - - /// Sets the full request, replacing any prior values. - pub fn with_request>(mut self, v: V) -> Self { - self.0.request = v.into(); - self - } - - /// Sets all the options, replacing any prior values. - pub fn with_options>(mut self, v: V) -> Self { - self.0.options = v.into(); - self - } - - /// Sends the request. - pub async fn send(self) -> Result { - (*self.0.stub) - .query_data_set(self.0.request, self.0.options) - .await - .map(crate::Response::into_body) - } - - /// Sets the value of [name][crate::model::QueryDataSetRequest::name]. - /// - /// This is a **required** field for requests. - pub fn set_name>(mut self, v: T) -> Self { - self.0.request.name = v.into(); - self - } - - /// Sets the value of [detection_time][crate::model::QueryDataSetRequest::detection_time]. - /// - /// This is a **required** field for requests. - pub fn set_detection_time(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.detection_time = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [detection_time][crate::model::QueryDataSetRequest::detection_time]. - /// - /// This is a **required** field for requests. - pub fn set_or_clear_detection_time(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.detection_time = v.map(|x| x.into()); - self - } - - /// Sets the value of [num_returned_slices][crate::model::QueryDataSetRequest::num_returned_slices]. - pub fn set_num_returned_slices(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.num_returned_slices = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [num_returned_slices][crate::model::QueryDataSetRequest::num_returned_slices]. - pub fn set_or_clear_num_returned_slices(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.num_returned_slices = v.map(|x| x.into()); - self - } - - /// Sets the value of [slicing_params][crate::model::QueryDataSetRequest::slicing_params]. - pub fn set_slicing_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.slicing_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [slicing_params][crate::model::QueryDataSetRequest::slicing_params]. - pub fn set_or_clear_slicing_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.slicing_params = v.map(|x| x.into()); - self - } - - /// Sets the value of [timeseries_params][crate::model::QueryDataSetRequest::timeseries_params]. - pub fn set_timeseries_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.timeseries_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [timeseries_params][crate::model::QueryDataSetRequest::timeseries_params]. - pub fn set_or_clear_timeseries_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.timeseries_params = v.map(|x| x.into()); - self - } - - /// Sets the value of [forecast_params][crate::model::QueryDataSetRequest::forecast_params]. - pub fn set_forecast_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.forecast_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [forecast_params][crate::model::QueryDataSetRequest::forecast_params]. - pub fn set_or_clear_forecast_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.forecast_params = v.map(|x| x.into()); - self - } - - /// Sets the value of [return_timeseries][crate::model::QueryDataSetRequest::return_timeseries]. - pub fn set_return_timeseries>(mut self, v: T) -> Self { - self.0.request.return_timeseries = v.into(); - self - } - } - - #[doc(hidden)] - impl crate::RequestBuilder for QueryDataSet { - fn request_options(&mut self) -> &mut crate::RequestOptions { - &mut self.0.options - } - } - - /// The request builder for [TimeseriesInsightsController::evaluate_slice][crate::client::TimeseriesInsightsController::evaluate_slice] calls. - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::builder::timeseries_insights_controller::EvaluateSlice; - /// # async fn sample() -> google_cloud_timeseriesinsights_v1::Result<()> { - /// - /// let builder = prepare_request_builder(); - /// let response = builder.send().await?; - /// # Ok(()) } - /// - /// fn prepare_request_builder() -> EvaluateSlice { - /// # panic!(); - /// // ... details omitted ... - /// } - /// ``` - #[derive(Clone, Debug)] - pub struct EvaluateSlice(RequestBuilder); - - impl EvaluateSlice { - pub(crate) fn new( - stub: std::sync::Arc, - ) -> Self { - Self(RequestBuilder::new(stub)) - } - - /// Sets the full request, replacing any prior values. - pub fn with_request>(mut self, v: V) -> Self { - self.0.request = v.into(); - self - } - - /// Sets all the options, replacing any prior values. - pub fn with_options>(mut self, v: V) -> Self { - self.0.options = v.into(); - self - } - - /// Sends the request. - pub async fn send(self) -> Result { - (*self.0.stub) - .evaluate_slice(self.0.request, self.0.options) - .await - .map(crate::Response::into_body) - } - - /// Sets the value of [dataset][crate::model::EvaluateSliceRequest::dataset]. - /// - /// This is a **required** field for requests. - pub fn set_dataset>(mut self, v: T) -> Self { - self.0.request.dataset = v.into(); - self - } - - /// Sets the value of [pinned_dimensions][crate::model::EvaluateSliceRequest::pinned_dimensions]. - /// - /// This is a **required** field for requests. - pub fn set_pinned_dimensions(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.0.request.pinned_dimensions = v.into_iter().map(|i| i.into()).collect(); - self - } - - /// Sets the value of [detection_time][crate::model::EvaluateSliceRequest::detection_time]. - /// - /// This is a **required** field for requests. - pub fn set_detection_time(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.detection_time = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [detection_time][crate::model::EvaluateSliceRequest::detection_time]. - /// - /// This is a **required** field for requests. - pub fn set_or_clear_detection_time(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.detection_time = v.map(|x| x.into()); - self - } - - /// Sets the value of [timeseries_params][crate::model::EvaluateSliceRequest::timeseries_params]. - pub fn set_timeseries_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.timeseries_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [timeseries_params][crate::model::EvaluateSliceRequest::timeseries_params]. - pub fn set_or_clear_timeseries_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.timeseries_params = v.map(|x| x.into()); - self - } - - /// Sets the value of [forecast_params][crate::model::EvaluateSliceRequest::forecast_params]. - pub fn set_forecast_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.forecast_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [forecast_params][crate::model::EvaluateSliceRequest::forecast_params]. - pub fn set_or_clear_forecast_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.forecast_params = v.map(|x| x.into()); - self - } - } - - #[doc(hidden)] - impl crate::RequestBuilder for EvaluateSlice { - fn request_options(&mut self) -> &mut crate::RequestOptions { - &mut self.0.options - } - } - - /// The request builder for [TimeseriesInsightsController::evaluate_timeseries][crate::client::TimeseriesInsightsController::evaluate_timeseries] calls. - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::builder::timeseries_insights_controller::EvaluateTimeseries; - /// # async fn sample() -> google_cloud_timeseriesinsights_v1::Result<()> { - /// - /// let builder = prepare_request_builder(); - /// let response = builder.send().await?; - /// # Ok(()) } - /// - /// fn prepare_request_builder() -> EvaluateTimeseries { - /// # panic!(); - /// // ... details omitted ... - /// } - /// ``` - #[derive(Clone, Debug)] - pub struct EvaluateTimeseries(RequestBuilder); - - impl EvaluateTimeseries { - pub(crate) fn new( - stub: std::sync::Arc, - ) -> Self { - Self(RequestBuilder::new(stub)) - } - - /// Sets the full request, replacing any prior values. - pub fn with_request>( - mut self, - v: V, - ) -> Self { - self.0.request = v.into(); - self - } - - /// Sets all the options, replacing any prior values. - pub fn with_options>(mut self, v: V) -> Self { - self.0.options = v.into(); - self - } - - /// Sends the request. - pub async fn send(self) -> Result { - (*self.0.stub) - .evaluate_timeseries(self.0.request, self.0.options) - .await - .map(crate::Response::into_body) - } - - /// Sets the value of [parent][crate::model::EvaluateTimeseriesRequest::parent]. - /// - /// This is a **required** field for requests. - pub fn set_parent>(mut self, v: T) -> Self { - self.0.request.parent = v.into(); - self - } - - /// Sets the value of [timeseries][crate::model::EvaluateTimeseriesRequest::timeseries]. - pub fn set_timeseries(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.timeseries = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [timeseries][crate::model::EvaluateTimeseriesRequest::timeseries]. - pub fn set_or_clear_timeseries(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.timeseries = v.map(|x| x.into()); - self - } - - /// Sets the value of [granularity][crate::model::EvaluateTimeseriesRequest::granularity]. - pub fn set_granularity(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.granularity = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [granularity][crate::model::EvaluateTimeseriesRequest::granularity]. - pub fn set_or_clear_granularity(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.granularity = v.map(|x| x.into()); - self - } - - /// Sets the value of [forecast_params][crate::model::EvaluateTimeseriesRequest::forecast_params]. - pub fn set_forecast_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.0.request.forecast_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [forecast_params][crate::model::EvaluateTimeseriesRequest::forecast_params]. - pub fn set_or_clear_forecast_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.0.request.forecast_params = v.map(|x| x.into()); - self - } - } - - #[doc(hidden)] - impl crate::RequestBuilder for EvaluateTimeseries { - fn request_options(&mut self) -> &mut crate::RequestOptions { - &mut self.0.options - } - } -} diff --git a/src/generated/cloud/timeseriesinsights/v1/src/client.rs b/src/generated/cloud/timeseriesinsights/v1/src/client.rs deleted file mode 100644 index 12a89e350d..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/src/client.rs +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright 2025 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 -// -// https://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. -// -// Code generated by sidekick. DO NOT EDIT. -#![allow(rustdoc::bare_urls)] -#![allow(rustdoc::broken_intra_doc_links)] -#![allow(rustdoc::invalid_html_tags)] -#![allow(rustdoc::redundant_explicit_links)] - -/// Implements a client for the Timeseries Insights API. -/// -/// # Example -/// ``` -/// # use google_cloud_timeseriesinsights_v1::client::TimeseriesInsightsController; -/// use google_cloud_gax::paginator::ItemPaginator as _; -/// async fn sample( -/// parent: &str, -/// ) -> anyhow::Result<()> { -/// let client = TimeseriesInsightsController::builder().build().await?; -/// let mut list = client.list_data_sets() -/// .set_parent(parent) -/// .by_item(); -/// while let Some(item) = list.next().await.transpose()? { -/// println!("{:?}", item); -/// } -/// Ok(()) -/// } -/// ``` -/// -/// # Service Description -/// -/// -/// # Configuration -/// -/// To configure `TimeseriesInsightsController` use the `with_*` methods in the type returned -/// by [builder()][TimeseriesInsightsController::builder]. The default configuration should -/// work for most applications. Common configuration changes include -/// -/// * [with_endpoint()]: by default this client uses the global default endpoint -/// (`https://timeseriesinsights.googleapis.com`). Applications using regional -/// endpoints or running in restricted networks (e.g. a network configured -/// with [Private Google Access with VPC Service Controls]) may want to -/// override this default. -/// * [with_credentials()]: by default this client uses -/// [Application Default Credentials]. Applications using custom -/// authentication may need to override this default. -/// -/// [with_endpoint()]: super::builder::timeseries_insights_controller::ClientBuilder::with_endpoint -/// [with_credentials()]: super::builder::timeseries_insights_controller::ClientBuilder::with_credentials -/// [Private Google Access with VPC Service Controls]: https://cloud.google.com/vpc-service-controls/docs/private-connectivity -/// [Application Default Credentials]: https://cloud.google.com/docs/authentication#adc -/// -/// # Pooling and Cloning -/// -/// `TimeseriesInsightsController` holds a connection pool internally, it is advised to -/// create one and reuse it. You do not need to wrap `TimeseriesInsightsController` in -/// an [Rc](std::rc::Rc) or [Arc](std::sync::Arc) to reuse it, because it -/// already uses an `Arc` internally. -#[derive(Clone, Debug)] -pub struct TimeseriesInsightsController { - inner: std::sync::Arc, -} - -impl TimeseriesInsightsController { - /// Returns a builder for [TimeseriesInsightsController]. - /// - /// ``` - /// # async fn sample() -> google_cloud_gax::client_builder::Result<()> { - /// # use google_cloud_timeseriesinsights_v1::client::TimeseriesInsightsController; - /// let client = TimeseriesInsightsController::builder().build().await?; - /// # Ok(()) } - /// ``` - pub fn builder() -> super::builder::timeseries_insights_controller::ClientBuilder { - crate::new_client_builder(super::builder::timeseries_insights_controller::client::Factory) - } - - /// Creates a new client from the provided stub. - /// - /// The most common case for calling this function is in tests mocking the - /// client's behavior. - pub fn from_stub(stub: impl Into>) -> Self - where - T: super::stub::TimeseriesInsightsController + 'static, - { - Self { inner: stub.into() } - } - - pub(crate) async fn new( - config: gaxi::options::ClientConfig, - ) -> crate::ClientBuilderResult { - let inner = Self::build_inner(config).await?; - Ok(Self { inner }) - } - - async fn build_inner( - conf: gaxi::options::ClientConfig, - ) -> crate::ClientBuilderResult< - std::sync::Arc, - > { - if gaxi::options::tracing_enabled(&conf) { - return Ok(std::sync::Arc::new(Self::build_with_tracing(conf).await?)); - } - Ok(std::sync::Arc::new(Self::build_transport(conf).await?)) - } - - async fn build_transport( - conf: gaxi::options::ClientConfig, - ) -> crate::ClientBuilderResult { - super::transport::TimeseriesInsightsController::new(conf).await - } - - async fn build_with_tracing( - conf: gaxi::options::ClientConfig, - ) -> crate::ClientBuilderResult { - Self::build_transport(conf) - .await - .map(super::tracing::TimeseriesInsightsController::new) - } - - /// Lists [DataSets][google.cloud.timeseriesinsights.v1.DataSet] under the project. - /// - /// The order of the results is unspecified but deterministic. Newly created - /// [DataSets][google.cloud.timeseriesinsights.v1.DataSet] will not necessarily be added to the end - /// of this list. - /// - /// [google.cloud.timeseriesinsights.v1.DataSet]: crate::model::DataSet - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::client::TimeseriesInsightsController; - /// use google_cloud_gax::paginator::ItemPaginator as _; - /// use google_cloud_timeseriesinsights_v1::Result; - /// async fn sample( - /// client: &TimeseriesInsightsController, parent: &str - /// ) -> Result<()> { - /// let mut list = client.list_data_sets() - /// .set_parent(parent) - /// .by_item(); - /// while let Some(item) = list.next().await.transpose()? { - /// println!("{:?}", item); - /// } - /// Ok(()) - /// } - /// ``` - pub fn list_data_sets(&self) -> super::builder::timeseries_insights_controller::ListDataSets { - super::builder::timeseries_insights_controller::ListDataSets::new(self.inner.clone()) - } - - /// Create a [DataSet][google.cloud.timeseriesinsights.v1.DataSet] from data stored on Cloud - /// Storage. - /// - /// The data must stay immutable while we process the - /// [DataSet][google.cloud.timeseriesinsights.v1.DataSet] creation; otherwise, undefined outcomes - /// might result. For more information, see [DataSet][google.cloud.timeseriesinsights.v1.DataSet]. - /// - /// [google.cloud.timeseriesinsights.v1.DataSet]: crate::model::DataSet - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::client::TimeseriesInsightsController; - /// use google_cloud_timeseriesinsights_v1::model::DataSet; - /// use google_cloud_timeseriesinsights_v1::Result; - /// async fn sample( - /// client: &TimeseriesInsightsController, parent: &str - /// ) -> Result<()> { - /// let response = client.create_data_set() - /// .set_parent(parent) - /// .set_dataset( - /// DataSet::new()/* set fields */ - /// ) - /// .send().await?; - /// println!("response {:?}", response); - /// Ok(()) - /// } - /// ``` - pub fn create_data_set(&self) -> super::builder::timeseries_insights_controller::CreateDataSet { - super::builder::timeseries_insights_controller::CreateDataSet::new(self.inner.clone()) - } - - /// Delete a [DataSet][google.cloud.timeseriesinsights.v1.DataSet] from the system. - /// - /// **NOTE**: If the [DataSet][google.cloud.timeseriesinsights.v1.DataSet] is still being - /// processed, it will be aborted and deleted. - /// - /// [google.cloud.timeseriesinsights.v1.DataSet]: crate::model::DataSet - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::client::TimeseriesInsightsController; - /// use google_cloud_timeseriesinsights_v1::Result; - /// async fn sample( - /// client: &TimeseriesInsightsController, project_id: &str, dataset_id: &str - /// ) -> Result<()> { - /// client.delete_data_set() - /// .set_name(format!("projects/{project_id}/datasets/{dataset_id}")) - /// .send().await?; - /// Ok(()) - /// } - /// ``` - pub fn delete_data_set(&self) -> super::builder::timeseries_insights_controller::DeleteDataSet { - super::builder::timeseries_insights_controller::DeleteDataSet::new(self.inner.clone()) - } - - /// Append events to a `LOADED` [DataSet][google.cloud.timeseriesinsights.v1.DataSet]. - /// - /// [google.cloud.timeseriesinsights.v1.DataSet]: crate::model::DataSet - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::client::TimeseriesInsightsController; - /// use google_cloud_timeseriesinsights_v1::Result; - /// async fn sample( - /// client: &TimeseriesInsightsController - /// ) -> Result<()> { - /// let response = client.append_events() - /// /* set fields */ - /// .send().await?; - /// println!("response {:?}", response); - /// Ok(()) - /// } - /// ``` - pub fn append_events(&self) -> super::builder::timeseries_insights_controller::AppendEvents { - super::builder::timeseries_insights_controller::AppendEvents::new(self.inner.clone()) - } - - /// Execute a Timeseries Insights query over a loaded - /// [DataSet][google.cloud.timeseriesinsights.v1.DataSet]. - /// - /// [google.cloud.timeseriesinsights.v1.DataSet]: crate::model::DataSet - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::client::TimeseriesInsightsController; - /// use google_cloud_timeseriesinsights_v1::Result; - /// async fn sample( - /// client: &TimeseriesInsightsController - /// ) -> Result<()> { - /// let response = client.query_data_set() - /// /* set fields */ - /// .send().await?; - /// println!("response {:?}", response); - /// Ok(()) - /// } - /// ``` - pub fn query_data_set(&self) -> super::builder::timeseries_insights_controller::QueryDataSet { - super::builder::timeseries_insights_controller::QueryDataSet::new(self.inner.clone()) - } - - /// Evaluate an explicit slice from a loaded [DataSet][google.cloud.timeseriesinsights.v1.DataSet]. - /// - /// [google.cloud.timeseriesinsights.v1.DataSet]: crate::model::DataSet - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::client::TimeseriesInsightsController; - /// use google_cloud_timeseriesinsights_v1::Result; - /// async fn sample( - /// client: &TimeseriesInsightsController - /// ) -> Result<()> { - /// let response = client.evaluate_slice() - /// /* set fields */ - /// .send().await?; - /// println!("response {:?}", response); - /// Ok(()) - /// } - /// ``` - pub fn evaluate_slice(&self) -> super::builder::timeseries_insights_controller::EvaluateSlice { - super::builder::timeseries_insights_controller::EvaluateSlice::new(self.inner.clone()) - } - - /// Evaluate an explicit timeseries. - /// - /// # Example - /// ``` - /// # use google_cloud_timeseriesinsights_v1::client::TimeseriesInsightsController; - /// use google_cloud_timeseriesinsights_v1::Result; - /// async fn sample( - /// client: &TimeseriesInsightsController - /// ) -> Result<()> { - /// let response = client.evaluate_timeseries() - /// /* set fields */ - /// .send().await?; - /// println!("response {:?}", response); - /// Ok(()) - /// } - /// ``` - pub fn evaluate_timeseries( - &self, - ) -> super::builder::timeseries_insights_controller::EvaluateTimeseries { - super::builder::timeseries_insights_controller::EvaluateTimeseries::new(self.inner.clone()) - } -} diff --git a/src/generated/cloud/timeseriesinsights/v1/src/lib.rs b/src/generated/cloud/timeseriesinsights/v1/src/lib.rs index cac2cf7d0e..5c4c4231ab 100644 --- a/src/generated/cloud/timeseriesinsights/v1/src/lib.rs +++ b/src/generated/cloud/timeseriesinsights/v1/src/lib.rs @@ -11,98 +11,8 @@ // 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. -// -// Code generated by sidekick. DO NOT EDIT. -//! Google Cloud Client Libraries for Rust - Timeseries Insights API -//! -//! This crate contains traits, types, and functions to interact with Timeseries Insights API -//! Most applications will use the structs defined in the [client] module. -//! -//! The client library types and functions are stable and not expected to change. -//! Please note that Google Cloud services do change from time to time. The client -//! libraries are designed to preserve backwards compatibility when the service -//! changes in compatible ways. For example, adding RPCs, or fields to messages -//! should not introduce breaking changes to the client libraries. -//! -//! # Available Clients -//! -//! * [TimeseriesInsightsController](client/struct.TimeseriesInsightsController.html) +//! # Google Cloud Client Libraries for Rust - Timeseries Insights API //! -//! # Features -//! -//! - `default-rustls-provider`: enabled by default. Use the default rustls -//! crypto provider ([aws-lc-rs]) for TLS and authentication. Applications -//! with specific requirements for cryptography (such as exclusively using the -//! [ring] crate) should disable this default and call -//! `rustls::crypto::CryptoProvider::install_default()`. -//! -//! [aws-lc-rs]: https://crates.io/crates/aws-lc-rs -//! [ring]: https://crates.io/crates/ring - -/// The messages and enums that are part of this client library. -#[allow(clippy::module_inception)] -pub mod model; - -pub use google_cloud_gax::Result; -pub use google_cloud_gax::error::Error; - -#[allow(rustdoc::invalid_html_tags)] -#[allow(rustdoc::redundant_explicit_links)] -pub mod stub; - -/// Concrete implementations of client library traits. -/// -/// # Example -/// ``` -/// # use google_cloud_timeseriesinsights_v1::client::TimeseriesInsightsController; -/// use google_cloud_gax::paginator::ItemPaginator as _; -/// async fn sample( -/// parent: &str, -/// ) -> anyhow::Result<()> { -/// let client = TimeseriesInsightsController::builder().build().await?; -/// let mut list = client.list_data_sets() -/// .set_parent(parent) -/// .by_item(); -/// while let Some(item) = list.next().await.transpose()? { -/// println!("{:?}", item); -/// } -/// Ok(()) -/// } -/// ``` -pub mod client; - -/// Request builders. -pub mod builder; - -#[doc(hidden)] -pub(crate) mod tracing; - -#[doc(hidden)] -pub(crate) mod transport; - -/// The default host used by the service. -const DEFAULT_HOST: &str = "https://timeseriesinsights.googleapis.com/"; - -pub(crate) mod info { - const NAME: &str = env!("CARGO_PKG_NAME"); - const VERSION: &str = env!("CARGO_PKG_VERSION"); - pub(crate) static X_GOOG_API_CLIENT_HEADER: std::sync::LazyLock = - std::sync::LazyLock::new(|| { - let ac = gaxi::api_header::XGoogApiClient { - name: NAME, - version: VERSION, - library_type: gaxi::api_header::GAPIC, - }; - ac.rest_header_value() - }); -} - -// Define some shortcuts for imported crates. -pub(crate) use google_cloud_gax::client_builder::ClientBuilder; -pub(crate) use google_cloud_gax::client_builder::Result as ClientBuilderResult; -pub(crate) use google_cloud_gax::client_builder::internal::ClientFactory; -pub(crate) use google_cloud_gax::client_builder::internal::new_builder as new_client_builder; -pub(crate) use google_cloud_gax::options::RequestOptions; -pub(crate) use google_cloud_gax::options::internal::RequestBuilder; -pub(crate) use google_cloud_gax::response::Response; +//! **WARNING:** The Timeseries Insights API has been decommissioned. +//! This crate is no longer maintained or supported and will receive no further updates. diff --git a/src/generated/cloud/timeseriesinsights/v1/src/model.rs b/src/generated/cloud/timeseriesinsights/v1/src/model.rs deleted file mode 100644 index 713f690636..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/src/model.rs +++ /dev/null @@ -1,3453 +0,0 @@ -// Copyright 2025 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 -// -// https://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. -// -// Code generated by sidekick. DO NOT EDIT. - -#![allow(rustdoc::bare_urls)] -#![allow(rustdoc::broken_intra_doc_links)] -#![allow(rustdoc::invalid_html_tags)] -#![allow(rustdoc::redundant_explicit_links)] -#![no_implicit_prelude] -extern crate async_trait; -extern crate bytes; -extern crate gaxi; -extern crate google_cloud_gax; -extern crate google_cloud_rpc; -extern crate serde; -extern crate serde_json; -extern crate serde_with; -extern crate std; -extern crate tracing; -extern crate wkt; - -mod debug; -mod deserialize; -mod serialize; - -/// Mapping of BigQuery columns to timestamp, group_id and dimensions. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct BigqueryMapping { - /// The column which should be used as the event timestamps. If not specified - /// 'Timestamp' is used by default. The column may have TIMESTAMP or INT64 - /// type (the latter is interpreted as microseconds since the Unix epoch). - pub timestamp_column: std::string::String, - - /// The column which should be used as the group ID (grouping events into - /// sessions). If not specified 'GroupId' is used by default, if the input - /// table does not have such a column, random unique group IDs are - /// generated automatically (different group ID per input row). - pub group_id_column: std::string::String, - - /// The list of columns that should be translated to dimensions. If empty, - /// all columns are translated to dimensions. The timestamp and group_id - /// columns should not be listed here again. Columns are expected to have - /// primitive types (STRING, INT64, FLOAT64 or NUMERIC). - pub dimension_column: std::vec::Vec, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl BigqueryMapping { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [timestamp_column][crate::model::BigqueryMapping::timestamp_column]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::BigqueryMapping; - /// let x = BigqueryMapping::new().set_timestamp_column("example"); - /// ``` - pub fn set_timestamp_column>( - mut self, - v: T, - ) -> Self { - self.timestamp_column = v.into(); - self - } - - /// Sets the value of [group_id_column][crate::model::BigqueryMapping::group_id_column]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::BigqueryMapping; - /// let x = BigqueryMapping::new().set_group_id_column("example"); - /// ``` - pub fn set_group_id_column>(mut self, v: T) -> Self { - self.group_id_column = v.into(); - self - } - - /// Sets the value of [dimension_column][crate::model::BigqueryMapping::dimension_column]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::BigqueryMapping; - /// let x = BigqueryMapping::new().set_dimension_column(["a", "b", "c"]); - /// ``` - pub fn set_dimension_column(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.dimension_column = v.into_iter().map(|i| i.into()).collect(); - self - } -} - -impl wkt::message::Message for BigqueryMapping { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.BigqueryMapping" - } -} - -/// A data source consists of multiple [Event][google.cloud.timeseriesinsights.v1.Event] objects stored on -/// Cloud Storage. Each Event should be in JSON format, with one Event -/// per line, also known as JSON Lines format. -/// -/// [google.cloud.timeseriesinsights.v1.Event]: crate::model::Event -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct DataSource { - /// Data source URI. - /// - /// 1. Google Cloud Storage files (JSON) are defined in the following form. - /// `gs://bucket_name/object_name`. For more information on Cloud Storage URIs, - /// please see . - pub uri: std::string::String, - - /// For BigQuery inputs defines the columns that should be used for dimensions - /// (including time and group ID). - pub bq_mapping: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl DataSource { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [uri][crate::model::DataSource::uri]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSource; - /// let x = DataSource::new().set_uri("example"); - /// ``` - pub fn set_uri>(mut self, v: T) -> Self { - self.uri = v.into(); - self - } - - /// Sets the value of [bq_mapping][crate::model::DataSource::bq_mapping]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSource; - /// use google_cloud_timeseriesinsights_v1::model::BigqueryMapping; - /// let x = DataSource::new().set_bq_mapping(BigqueryMapping::default()/* use setters */); - /// ``` - pub fn set_bq_mapping(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.bq_mapping = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [bq_mapping][crate::model::DataSource::bq_mapping]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSource; - /// use google_cloud_timeseriesinsights_v1::model::BigqueryMapping; - /// let x = DataSource::new().set_or_clear_bq_mapping(Some(BigqueryMapping::default()/* use setters */)); - /// let x = DataSource::new().set_or_clear_bq_mapping(None::); - /// ``` - pub fn set_or_clear_bq_mapping(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.bq_mapping = v.map(|x| x.into()); - self - } -} - -impl wkt::message::Message for DataSource { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.DataSource" - } -} - -/// A collection of data sources sent for processing. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct DataSet { - /// The dataset name, which will be used for querying, status and unload - /// requests. This must be unique within a project. - pub name: std::string::String, - - /// [Data dimension names][google.cloud.timeseriesinsights.v1.EventDimension.name] allowed for this `DataSet`. - /// - /// If left empty, all dimension names are included. This field works as a - /// filter to avoid regenerating the data. - /// - /// [google.cloud.timeseriesinsights.v1.EventDimension.name]: crate::model::EventDimension::name - pub data_names: std::vec::Vec, - - /// Input data. - pub data_sources: std::vec::Vec, - - /// Dataset state in the system. - pub state: crate::model::data_set::State, - - /// Dataset processing status. - pub status: std::option::Option, - - /// Periodically we discard dataset [Event][google.cloud.timeseriesinsights.v1.Event] objects that have - /// timestamps older than 'ttl'. Omitting this field or a zero value means no - /// events are discarded. - /// - /// [google.cloud.timeseriesinsights.v1.Event]: crate::model::Event - pub ttl: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl DataSet { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [name][crate::model::DataSet::name]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSet; - /// # let project_id = "project_id"; - /// # let dataset_id = "dataset_id"; - /// let x = DataSet::new().set_name(format!("projects/{project_id}/datasets/{dataset_id}")); - /// ``` - pub fn set_name>(mut self, v: T) -> Self { - self.name = v.into(); - self - } - - /// Sets the value of [data_names][crate::model::DataSet::data_names]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSet; - /// let x = DataSet::new().set_data_names(["a", "b", "c"]); - /// ``` - pub fn set_data_names(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.data_names = v.into_iter().map(|i| i.into()).collect(); - self - } - - /// Sets the value of [data_sources][crate::model::DataSet::data_sources]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSet; - /// use google_cloud_timeseriesinsights_v1::model::DataSource; - /// let x = DataSet::new() - /// .set_data_sources([ - /// DataSource::default()/* use setters */, - /// DataSource::default()/* use (different) setters */, - /// ]); - /// ``` - pub fn set_data_sources(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.data_sources = v.into_iter().map(|i| i.into()).collect(); - self - } - - /// Sets the value of [state][crate::model::DataSet::state]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSet; - /// use google_cloud_timeseriesinsights_v1::model::data_set::State; - /// let x0 = DataSet::new().set_state(State::Unknown); - /// let x1 = DataSet::new().set_state(State::Pending); - /// let x2 = DataSet::new().set_state(State::Loading); - /// ``` - pub fn set_state>(mut self, v: T) -> Self { - self.state = v.into(); - self - } - - /// Sets the value of [status][crate::model::DataSet::status]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSet; - /// use google_cloud_rpc::model::Status; - /// let x = DataSet::new().set_status(Status::default()/* use setters */); - /// ``` - pub fn set_status(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.status = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [status][crate::model::DataSet::status]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSet; - /// use google_cloud_rpc::model::Status; - /// let x = DataSet::new().set_or_clear_status(Some(Status::default()/* use setters */)); - /// let x = DataSet::new().set_or_clear_status(None::); - /// ``` - pub fn set_or_clear_status(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.status = v.map(|x| x.into()); - self - } - - /// Sets the value of [ttl][crate::model::DataSet::ttl]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSet; - /// use wkt::Duration; - /// let x = DataSet::new().set_ttl(Duration::default()/* use setters */); - /// ``` - pub fn set_ttl(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.ttl = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [ttl][crate::model::DataSet::ttl]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DataSet; - /// use wkt::Duration; - /// let x = DataSet::new().set_or_clear_ttl(Some(Duration::default()/* use setters */)); - /// let x = DataSet::new().set_or_clear_ttl(None::); - /// ``` - pub fn set_or_clear_ttl(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.ttl = v.map(|x| x.into()); - self - } -} - -impl wkt::message::Message for DataSet { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.DataSet" - } -} - -/// Defines additional types related to [DataSet]. -pub mod data_set { - #[allow(unused_imports)] - use super::*; - - /// DataSet state. - /// - /// # Working with unknown values - /// - /// This enum is defined as `#[non_exhaustive]` because Google Cloud may add - /// additional enum variants at any time. Adding new variants is not considered - /// a breaking change. Applications should write their code in anticipation of: - /// - /// - New values appearing in future releases of the client library, **and** - /// - New values received dynamically, without application changes. - /// - /// Please consult the [Working with enums] section in the user guide for some - /// guidelines. - /// - /// [Working with enums]: https://googleapis.github.io/google-cloud-rust/working_with_enums.html - #[derive(Clone, Debug, PartialEq)] - #[non_exhaustive] - pub enum State { - /// Unspecified / undefined state. - Unspecified, - /// Dataset is unknown to the system; we have never seen this dataset before - /// or we have seen this dataset but have fully GC-ed it. - Unknown, - /// Dataset processing is pending. - Pending, - /// Dataset is loading. - Loading, - /// Dataset is loaded and can be queried. - Loaded, - /// Dataset is unloading. - Unloading, - /// Dataset is unloaded and is removed from the system. - Unloaded, - /// Dataset processing failed. - Failed, - /// If set, the enum was initialized with an unknown value. - /// - /// Applications can examine the value using [State::value] or - /// [State::name]. - UnknownValue(state::UnknownValue), - } - - #[doc(hidden)] - pub mod state { - #[allow(unused_imports)] - use super::*; - #[derive(Clone, Debug, PartialEq)] - pub struct UnknownValue(pub(crate) wkt::internal::UnknownEnumValue); - } - - impl State { - /// Gets the enum value. - /// - /// Returns `None` if the enum contains an unknown value deserialized from - /// the string representation of enums. - pub fn value(&self) -> std::option::Option { - match self { - Self::Unspecified => std::option::Option::Some(0), - Self::Unknown => std::option::Option::Some(1), - Self::Pending => std::option::Option::Some(2), - Self::Loading => std::option::Option::Some(3), - Self::Loaded => std::option::Option::Some(4), - Self::Unloading => std::option::Option::Some(5), - Self::Unloaded => std::option::Option::Some(6), - Self::Failed => std::option::Option::Some(7), - Self::UnknownValue(u) => u.0.value(), - } - } - - /// Gets the enum value as a string. - /// - /// Returns `None` if the enum contains an unknown value deserialized from - /// the integer representation of enums. - pub fn name(&self) -> std::option::Option<&str> { - match self { - Self::Unspecified => std::option::Option::Some("STATE_UNSPECIFIED"), - Self::Unknown => std::option::Option::Some("UNKNOWN"), - Self::Pending => std::option::Option::Some("PENDING"), - Self::Loading => std::option::Option::Some("LOADING"), - Self::Loaded => std::option::Option::Some("LOADED"), - Self::Unloading => std::option::Option::Some("UNLOADING"), - Self::Unloaded => std::option::Option::Some("UNLOADED"), - Self::Failed => std::option::Option::Some("FAILED"), - Self::UnknownValue(u) => u.0.name(), - } - } - } - - impl std::default::Default for State { - fn default() -> Self { - use std::convert::From; - Self::from(0) - } - } - - impl std::fmt::Display for State { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - wkt::internal::display_enum(f, self.name(), self.value()) - } - } - - impl std::convert::From for State { - fn from(value: i32) -> Self { - match value { - 0 => Self::Unspecified, - 1 => Self::Unknown, - 2 => Self::Pending, - 3 => Self::Loading, - 4 => Self::Loaded, - 5 => Self::Unloading, - 6 => Self::Unloaded, - 7 => Self::Failed, - _ => Self::UnknownValue(state::UnknownValue( - wkt::internal::UnknownEnumValue::Integer(value), - )), - } - } - } - - impl std::convert::From<&str> for State { - fn from(value: &str) -> Self { - use std::string::ToString; - match value { - "STATE_UNSPECIFIED" => Self::Unspecified, - "UNKNOWN" => Self::Unknown, - "PENDING" => Self::Pending, - "LOADING" => Self::Loading, - "LOADED" => Self::Loaded, - "UNLOADING" => Self::Unloading, - "UNLOADED" => Self::Unloaded, - "FAILED" => Self::Failed, - _ => Self::UnknownValue(state::UnknownValue( - wkt::internal::UnknownEnumValue::String(value.to_string()), - )), - } - } - } - - impl serde::ser::Serialize for State { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - match self { - Self::Unspecified => serializer.serialize_i32(0), - Self::Unknown => serializer.serialize_i32(1), - Self::Pending => serializer.serialize_i32(2), - Self::Loading => serializer.serialize_i32(3), - Self::Loaded => serializer.serialize_i32(4), - Self::Unloading => serializer.serialize_i32(5), - Self::Unloaded => serializer.serialize_i32(6), - Self::Failed => serializer.serialize_i32(7), - Self::UnknownValue(u) => u.0.serialize(serializer), - } - } - } - - impl<'de> serde::de::Deserialize<'de> for State { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_any(wkt::internal::EnumVisitor::::new( - ".google.cloud.timeseriesinsights.v1.DataSet.State", - )) - } - } -} - -/// Represents an event dimension. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct EventDimension { - /// Dimension name. - /// - /// **NOTE**: `EventDimension` names must be composed of alphanumeric - /// characters only, and are case insensitive. Unicode characters are *not* - /// supported. The underscore '_' is also allowed. - pub name: std::string::String, - - /// Dimension value. - /// - /// **NOTE**: All entries of the dimension `name` must have the same `value` - /// type. - pub value: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl EventDimension { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [name][crate::model::EventDimension::name]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EventDimension; - /// let x = EventDimension::new().set_name("example"); - /// ``` - pub fn set_name>(mut self, v: T) -> Self { - self.name = v.into(); - self - } - - /// Sets the value of [value][crate::model::EventDimension::value]. - /// - /// Note that all the setters affecting `value` are mutually - /// exclusive. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EventDimension; - /// use google_cloud_timeseriesinsights_v1::model::event_dimension::Value; - /// let x = EventDimension::new().set_value(Some(Value::StringVal("example".to_string()))); - /// ``` - pub fn set_value< - T: std::convert::Into>, - >( - mut self, - v: T, - ) -> Self { - self.value = v.into(); - self - } - - /// The value of [value][crate::model::EventDimension::value] - /// if it holds a `StringVal`, `None` if the field is not set or - /// holds a different branch. - pub fn string_val(&self) -> std::option::Option<&std::string::String> { - #[allow(unreachable_patterns)] - self.value.as_ref().and_then(|v| match v { - crate::model::event_dimension::Value::StringVal(v) => std::option::Option::Some(v), - _ => std::option::Option::None, - }) - } - - /// Sets the value of [value][crate::model::EventDimension::value] - /// to hold a `StringVal`. - /// - /// Note that all the setters affecting `value` are - /// mutually exclusive. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EventDimension; - /// let x = EventDimension::new().set_string_val("example"); - /// assert!(x.string_val().is_some()); - /// assert!(x.long_val().is_none()); - /// assert!(x.bool_val().is_none()); - /// assert!(x.double_val().is_none()); - /// ``` - pub fn set_string_val>(mut self, v: T) -> Self { - self.value = - std::option::Option::Some(crate::model::event_dimension::Value::StringVal(v.into())); - self - } - - /// The value of [value][crate::model::EventDimension::value] - /// if it holds a `LongVal`, `None` if the field is not set or - /// holds a different branch. - pub fn long_val(&self) -> std::option::Option<&i64> { - #[allow(unreachable_patterns)] - self.value.as_ref().and_then(|v| match v { - crate::model::event_dimension::Value::LongVal(v) => std::option::Option::Some(v), - _ => std::option::Option::None, - }) - } - - /// Sets the value of [value][crate::model::EventDimension::value] - /// to hold a `LongVal`. - /// - /// Note that all the setters affecting `value` are - /// mutually exclusive. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EventDimension; - /// let x = EventDimension::new().set_long_val(42); - /// assert!(x.long_val().is_some()); - /// assert!(x.string_val().is_none()); - /// assert!(x.bool_val().is_none()); - /// assert!(x.double_val().is_none()); - /// ``` - pub fn set_long_val>(mut self, v: T) -> Self { - self.value = - std::option::Option::Some(crate::model::event_dimension::Value::LongVal(v.into())); - self - } - - /// The value of [value][crate::model::EventDimension::value] - /// if it holds a `BoolVal`, `None` if the field is not set or - /// holds a different branch. - pub fn bool_val(&self) -> std::option::Option<&bool> { - #[allow(unreachable_patterns)] - self.value.as_ref().and_then(|v| match v { - crate::model::event_dimension::Value::BoolVal(v) => std::option::Option::Some(v), - _ => std::option::Option::None, - }) - } - - /// Sets the value of [value][crate::model::EventDimension::value] - /// to hold a `BoolVal`. - /// - /// Note that all the setters affecting `value` are - /// mutually exclusive. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EventDimension; - /// let x = EventDimension::new().set_bool_val(true); - /// assert!(x.bool_val().is_some()); - /// assert!(x.string_val().is_none()); - /// assert!(x.long_val().is_none()); - /// assert!(x.double_val().is_none()); - /// ``` - pub fn set_bool_val>(mut self, v: T) -> Self { - self.value = - std::option::Option::Some(crate::model::event_dimension::Value::BoolVal(v.into())); - self - } - - /// The value of [value][crate::model::EventDimension::value] - /// if it holds a `DoubleVal`, `None` if the field is not set or - /// holds a different branch. - pub fn double_val(&self) -> std::option::Option<&f64> { - #[allow(unreachable_patterns)] - self.value.as_ref().and_then(|v| match v { - crate::model::event_dimension::Value::DoubleVal(v) => std::option::Option::Some(v), - _ => std::option::Option::None, - }) - } - - /// Sets the value of [value][crate::model::EventDimension::value] - /// to hold a `DoubleVal`. - /// - /// Note that all the setters affecting `value` are - /// mutually exclusive. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EventDimension; - /// let x = EventDimension::new().set_double_val(42.0); - /// assert!(x.double_val().is_some()); - /// assert!(x.string_val().is_none()); - /// assert!(x.long_val().is_none()); - /// assert!(x.bool_val().is_none()); - /// ``` - pub fn set_double_val>(mut self, v: T) -> Self { - self.value = - std::option::Option::Some(crate::model::event_dimension::Value::DoubleVal(v.into())); - self - } -} - -impl wkt::message::Message for EventDimension { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.EventDimension" - } -} - -/// Defines additional types related to [EventDimension]. -pub mod event_dimension { - #[allow(unused_imports)] - use super::*; - - /// Dimension value. - /// - /// **NOTE**: All entries of the dimension `name` must have the same `value` - /// type. - #[derive(Clone, Debug, PartialEq)] - #[non_exhaustive] - pub enum Value { - /// String representation. - /// - /// **NOTE**: String values are case insensitive. Unicode characters are - /// supported. - StringVal(std::string::String), - /// Long representation. - LongVal(i64), - /// Bool representation. - BoolVal(bool), - /// Double representation. - DoubleVal(f64), - } -} - -/// Represents an entry in a data source. -/// -/// Each Event has: -/// -/// * A timestamp at which the event occurs. -/// * One or multiple dimensions. -/// * Optionally, a group ID that allows clients to group logically related -/// events (for example, all events representing payments transactions done by -/// a user in a day have the same group ID). If a group ID is not provided, an -/// internal one will be generated based on the content and `eventTime`. -/// -/// **NOTE**: -/// -/// * Internally, we discretize time in equal-sized chunks and we assume an -/// event has a 0 -/// [TimeseriesPoint.value][google.cloud.timeseriesinsights.v1.TimeseriesPoint.value] -/// in a chunk that does not contain any occurrences of an event in the input. -/// * The number of Events with the same group ID should be limited. -/// * Group ID *cannot* be queried. -/// * Group ID does *not* correspond to a user ID or the like. If a user ID is of -/// interest to be queried, use a user ID `dimension` instead. -/// -/// [google.cloud.timeseriesinsights.v1.TimeseriesPoint.value]: crate::model::TimeseriesPoint::value -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct Event { - /// Event dimensions. - pub dimensions: std::vec::Vec, - - /// Event group ID. - /// - /// **NOTE**: JSON encoding should use a string to hold a 64-bit integer value, - /// because a native JSON number holds only 53 binary bits for an integer. - pub group_id: i64, - - /// Event timestamp. - pub event_time: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl Event { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [dimensions][crate::model::Event::dimensions]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::Event; - /// use google_cloud_timeseriesinsights_v1::model::EventDimension; - /// let x = Event::new() - /// .set_dimensions([ - /// EventDimension::default()/* use setters */, - /// EventDimension::default()/* use (different) setters */, - /// ]); - /// ``` - pub fn set_dimensions(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.dimensions = v.into_iter().map(|i| i.into()).collect(); - self - } - - /// Sets the value of [group_id][crate::model::Event::group_id]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::Event; - /// let x = Event::new().set_group_id(42); - /// ``` - pub fn set_group_id>(mut self, v: T) -> Self { - self.group_id = v.into(); - self - } - - /// Sets the value of [event_time][crate::model::Event::event_time]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::Event; - /// use wkt::Timestamp; - /// let x = Event::new().set_event_time(Timestamp::default()/* use setters */); - /// ``` - pub fn set_event_time(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.event_time = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [event_time][crate::model::Event::event_time]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::Event; - /// use wkt::Timestamp; - /// let x = Event::new().set_or_clear_event_time(Some(Timestamp::default()/* use setters */)); - /// let x = Event::new().set_or_clear_event_time(None::); - /// ``` - pub fn set_or_clear_event_time(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.event_time = v.map(|x| x.into()); - self - } -} - -impl wkt::message::Message for Event { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.Event" - } -} - -/// Appends events to an existing DataSet. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct AppendEventsRequest { - /// Events to be appended. - /// - /// Note: - /// - /// 1. The [DataSet][google.cloud.timeseriesinsights.v1.DataSet] must be shown in a `LOADED` state - /// in the results of `list` method; otherwise, all events from - /// the append request will be dropped, and a `NOT_FOUND` status will be - /// returned. - /// 1. All events in a single request must have the same - /// [groupId][google.cloud.timeseriesinsights.v1.Event.group_id] if set; otherwise, an - /// `INVALID_ARGUMENT` status will be returned. - /// 1. If [groupId][google.cloud.timeseriesinsights.v1.Event.group_id] is not set (or 0), there - /// should be only 1 event; otherwise, an `INVALID_ARGUMENT` status will be - /// returned. - /// 1. The events must be newer than the current time minus - /// [DataSet TTL][google.cloud.timeseriesinsights.v1.DataSet.ttl] or they will be dropped. - /// - /// [google.cloud.timeseriesinsights.v1.DataSet]: crate::model::DataSet - /// [google.cloud.timeseriesinsights.v1.DataSet.ttl]: crate::model::DataSet::ttl - /// [google.cloud.timeseriesinsights.v1.Event.group_id]: crate::model::Event::group_id - pub events: std::vec::Vec, - - /// Required. The DataSet to which we want to append to in the format of - /// "projects/{project}/datasets/{dataset}" - pub dataset: std::string::String, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl AppendEventsRequest { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [events][crate::model::AppendEventsRequest::events]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::AppendEventsRequest; - /// use google_cloud_timeseriesinsights_v1::model::Event; - /// let x = AppendEventsRequest::new() - /// .set_events([ - /// Event::default()/* use setters */, - /// Event::default()/* use (different) setters */, - /// ]); - /// ``` - pub fn set_events(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.events = v.into_iter().map(|i| i.into()).collect(); - self - } - - /// Sets the value of [dataset][crate::model::AppendEventsRequest::dataset]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::AppendEventsRequest; - /// # let project_id = "project_id"; - /// # let dataset_id = "dataset_id"; - /// let x = AppendEventsRequest::new().set_dataset(format!("projects/{project_id}/datasets/{dataset_id}")); - /// ``` - pub fn set_dataset>(mut self, v: T) -> Self { - self.dataset = v.into(); - self - } -} - -impl wkt::message::Message for AppendEventsRequest { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.AppendEventsRequest" - } -} - -/// Response for an AppendEvents RPC. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct AppendEventsResponse { - /// Dropped events; empty if all events are successfully added. - pub dropped_events: std::vec::Vec, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl AppendEventsResponse { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [dropped_events][crate::model::AppendEventsResponse::dropped_events]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::AppendEventsResponse; - /// use google_cloud_timeseriesinsights_v1::model::Event; - /// let x = AppendEventsResponse::new() - /// .set_dropped_events([ - /// Event::default()/* use setters */, - /// Event::default()/* use (different) setters */, - /// ]); - /// ``` - pub fn set_dropped_events(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.dropped_events = v.into_iter().map(|i| i.into()).collect(); - self - } -} - -impl wkt::message::Message for AppendEventsResponse { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.AppendEventsResponse" - } -} - -/// Create a DataSet request. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct CreateDataSetRequest { - /// Required. Client project name which will own this DataSet in the format of - /// 'projects/{project}'. - pub parent: std::string::String, - - /// Required. Dataset to be loaded. - pub dataset: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl CreateDataSetRequest { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [parent][crate::model::CreateDataSetRequest::parent]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::CreateDataSetRequest; - /// let x = CreateDataSetRequest::new().set_parent("example"); - /// ``` - pub fn set_parent>(mut self, v: T) -> Self { - self.parent = v.into(); - self - } - - /// Sets the value of [dataset][crate::model::CreateDataSetRequest::dataset]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::CreateDataSetRequest; - /// use google_cloud_timeseriesinsights_v1::model::DataSet; - /// let x = CreateDataSetRequest::new().set_dataset(DataSet::default()/* use setters */); - /// ``` - pub fn set_dataset(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.dataset = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [dataset][crate::model::CreateDataSetRequest::dataset]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::CreateDataSetRequest; - /// use google_cloud_timeseriesinsights_v1::model::DataSet; - /// let x = CreateDataSetRequest::new().set_or_clear_dataset(Some(DataSet::default()/* use setters */)); - /// let x = CreateDataSetRequest::new().set_or_clear_dataset(None::); - /// ``` - pub fn set_or_clear_dataset(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.dataset = v.map(|x| x.into()); - self - } -} - -impl wkt::message::Message for CreateDataSetRequest { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.CreateDataSetRequest" - } -} - -/// Unload DataSet request from the serving system. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct DeleteDataSetRequest { - /// Required. Dataset name in the format of "projects/{project}/datasets/{dataset}" - pub name: std::string::String, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl DeleteDataSetRequest { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [name][crate::model::DeleteDataSetRequest::name]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::DeleteDataSetRequest; - /// # let project_id = "project_id"; - /// # let dataset_id = "dataset_id"; - /// let x = DeleteDataSetRequest::new().set_name(format!("projects/{project_id}/datasets/{dataset_id}")); - /// ``` - pub fn set_name>(mut self, v: T) -> Self { - self.name = v.into(); - self - } -} - -impl wkt::message::Message for DeleteDataSetRequest { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.DeleteDataSetRequest" - } -} - -/// List the DataSets created by the current project. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct ListDataSetsRequest { - /// Required. Project owning the DataSet in the format of "projects/{project}". - pub parent: std::string::String, - - /// Number of results to return in the list. - pub page_size: i32, - - /// Token to provide to skip to a particular spot in the list. - pub page_token: std::string::String, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl ListDataSetsRequest { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [parent][crate::model::ListDataSetsRequest::parent]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::ListDataSetsRequest; - /// let x = ListDataSetsRequest::new().set_parent("example"); - /// ``` - pub fn set_parent>(mut self, v: T) -> Self { - self.parent = v.into(); - self - } - - /// Sets the value of [page_size][crate::model::ListDataSetsRequest::page_size]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::ListDataSetsRequest; - /// let x = ListDataSetsRequest::new().set_page_size(42); - /// ``` - pub fn set_page_size>(mut self, v: T) -> Self { - self.page_size = v.into(); - self - } - - /// Sets the value of [page_token][crate::model::ListDataSetsRequest::page_token]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::ListDataSetsRequest; - /// let x = ListDataSetsRequest::new().set_page_token("example"); - /// ``` - pub fn set_page_token>(mut self, v: T) -> Self { - self.page_token = v.into(); - self - } -} - -impl wkt::message::Message for ListDataSetsRequest { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.ListDataSetsRequest" - } -} - -/// Created DataSets list response. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct ListDataSetsResponse { - /// The list of created DataSets. - pub datasets: std::vec::Vec, - - /// Token to receive the next page of results. - pub next_page_token: std::string::String, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl ListDataSetsResponse { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [datasets][crate::model::ListDataSetsResponse::datasets]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::ListDataSetsResponse; - /// use google_cloud_timeseriesinsights_v1::model::DataSet; - /// let x = ListDataSetsResponse::new() - /// .set_datasets([ - /// DataSet::default()/* use setters */, - /// DataSet::default()/* use (different) setters */, - /// ]); - /// ``` - pub fn set_datasets(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.datasets = v.into_iter().map(|i| i.into()).collect(); - self - } - - /// Sets the value of [next_page_token][crate::model::ListDataSetsResponse::next_page_token]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::ListDataSetsResponse; - /// let x = ListDataSetsResponse::new().set_next_page_token("example"); - /// ``` - pub fn set_next_page_token>(mut self, v: T) -> Self { - self.next_page_token = v.into(); - self - } -} - -impl wkt::message::Message for ListDataSetsResponse { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.ListDataSetsResponse" - } -} - -#[doc(hidden)] -impl google_cloud_gax::paginator::internal::PageableResponse for ListDataSetsResponse { - type PageItem = crate::model::DataSet; - - fn items(self) -> std::vec::Vec { - self.datasets - } - - fn next_page_token(&self) -> std::string::String { - use std::clone::Clone; - self.next_page_token.clone() - } -} - -/// A categorical dimension fixed to a certain value. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct PinnedDimension { - /// The name of the dimension for which we are fixing its value. - pub name: std::string::String, - - /// Dimension value. - /// - /// **NOTE**: The `value` type must match that in the data with the same - /// `dimension` as name. - pub value: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl PinnedDimension { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [name][crate::model::PinnedDimension::name]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::PinnedDimension; - /// let x = PinnedDimension::new().set_name("example"); - /// ``` - pub fn set_name>(mut self, v: T) -> Self { - self.name = v.into(); - self - } - - /// Sets the value of [value][crate::model::PinnedDimension::value]. - /// - /// Note that all the setters affecting `value` are mutually - /// exclusive. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::PinnedDimension; - /// use google_cloud_timeseriesinsights_v1::model::pinned_dimension::Value; - /// let x = PinnedDimension::new().set_value(Some(Value::StringVal("example".to_string()))); - /// ``` - pub fn set_value< - T: std::convert::Into>, - >( - mut self, - v: T, - ) -> Self { - self.value = v.into(); - self - } - - /// The value of [value][crate::model::PinnedDimension::value] - /// if it holds a `StringVal`, `None` if the field is not set or - /// holds a different branch. - pub fn string_val(&self) -> std::option::Option<&std::string::String> { - #[allow(unreachable_patterns)] - self.value.as_ref().and_then(|v| match v { - crate::model::pinned_dimension::Value::StringVal(v) => std::option::Option::Some(v), - _ => std::option::Option::None, - }) - } - - /// Sets the value of [value][crate::model::PinnedDimension::value] - /// to hold a `StringVal`. - /// - /// Note that all the setters affecting `value` are - /// mutually exclusive. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::PinnedDimension; - /// let x = PinnedDimension::new().set_string_val("example"); - /// assert!(x.string_val().is_some()); - /// assert!(x.bool_val().is_none()); - /// ``` - pub fn set_string_val>(mut self, v: T) -> Self { - self.value = - std::option::Option::Some(crate::model::pinned_dimension::Value::StringVal(v.into())); - self - } - - /// The value of [value][crate::model::PinnedDimension::value] - /// if it holds a `BoolVal`, `None` if the field is not set or - /// holds a different branch. - pub fn bool_val(&self) -> std::option::Option<&bool> { - #[allow(unreachable_patterns)] - self.value.as_ref().and_then(|v| match v { - crate::model::pinned_dimension::Value::BoolVal(v) => std::option::Option::Some(v), - _ => std::option::Option::None, - }) - } - - /// Sets the value of [value][crate::model::PinnedDimension::value] - /// to hold a `BoolVal`. - /// - /// Note that all the setters affecting `value` are - /// mutually exclusive. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::PinnedDimension; - /// let x = PinnedDimension::new().set_bool_val(true); - /// assert!(x.bool_val().is_some()); - /// assert!(x.string_val().is_none()); - /// ``` - pub fn set_bool_val>(mut self, v: T) -> Self { - self.value = - std::option::Option::Some(crate::model::pinned_dimension::Value::BoolVal(v.into())); - self - } -} - -impl wkt::message::Message for PinnedDimension { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.PinnedDimension" - } -} - -/// Defines additional types related to [PinnedDimension]. -pub mod pinned_dimension { - #[allow(unused_imports)] - use super::*; - - /// Dimension value. - /// - /// **NOTE**: The `value` type must match that in the data with the same - /// `dimension` as name. - #[derive(Clone, Debug, PartialEq)] - #[non_exhaustive] - pub enum Value { - /// A string value. This can be used for [dimensions][google.cloud.timeseriesinsights.v1.EventDimension], which - /// have their value field set to [string_val][google.cloud.timeseriesinsights.v1.EventDimension.string_val]. - /// - /// [google.cloud.timeseriesinsights.v1.EventDimension]: crate::model::EventDimension - /// [google.cloud.timeseriesinsights.v1.EventDimension.string_val]: crate::model::EventDimension::value - StringVal(std::string::String), - /// A bool value. This can be used for [dimensions][google.cloud.timeseriesinsights.v1.EventDimension], which - /// have their value field set to [bool_val][google.cloud.timeseriesinsights.v1.EventDimension.bool_val]. - /// - /// [google.cloud.timeseriesinsights.v1.EventDimension]: crate::model::EventDimension - /// [google.cloud.timeseriesinsights.v1.EventDimension.bool_val]: crate::model::EventDimension::value - BoolVal(bool), - } -} - -/// Parameters that control the sensitivity and other options for the time series -/// forecast. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct ForecastParams { - /// Optional. Penalize variations between the actual and forecasted values smaller than - /// this. For more information about how this parameter affects the score, see - /// the [anomalyScore](EvaluatedSlice.anomaly_score) formula. - /// - /// Intuitively, anomaly scores summarize how statistically significant the - /// change between the actual and forecasted value is compared with what we - /// expect the change to be (see - /// [expectedDeviation](EvaluatedSlice.expected_deviation)). However, in - /// practice, depending on the application, changes smaller than certain - /// absolute values, while statistically significant, may not be important. - /// - /// This parameter allows us to penalize such low absolute value changes. - /// - /// Must be in the (0.0, inf) range. - /// - /// If unspecified, it defaults to 0.000001. - pub noise_threshold: std::option::Option, - - /// Optional. Specifying any known seasonality/periodicity in the time series - /// for the slices we will analyze can improve the quality of the results. - /// - /// If unsure, simply leave it unspecified by not setting a value for this - /// field. - /// - /// If your time series has multiple seasonal patterns, then set it to the most - /// granular one (e.g. if it has daily and weekly patterns, set this to DAILY). - pub seasonality_hint: crate::model::forecast_params::Period, - - /// Optional. The length of the returned [forecasted - /// timeseries][EvaluatedSlice.forecast]. - /// - /// This duration is currently capped at 100 x - /// [granularity][google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity]. - /// - /// Example: If the detection point is set to "2020-12-27T00:00:00Z", the - /// [granularity][google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity] to "3600s" and the - /// horizon_duration to "10800s", then we will generate 3 time - /// series points (from "2020-12-27T01:00:00Z" to "2020-12-27T04:00:00Z"), for - /// which we will return their forecasted values. - /// - /// Note: The horizon time is only used for forecasting not for anormaly - /// detection. To detect anomalies for multiple points of time, - /// simply send multiple queries with those as - /// [detectionTime][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time]. - /// - /// [EvaluatedSlice.forecast]: crate::model::EvaluatedSlice::forecast - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time]: crate::model::QueryDataSetRequest::detection_time - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity]: crate::model::TimeseriesParams::granularity - pub horizon_duration: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl ForecastParams { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [noise_threshold][crate::model::ForecastParams::noise_threshold]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// let x = ForecastParams::new().set_noise_threshold(42.0); - /// ``` - pub fn set_noise_threshold(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.noise_threshold = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [noise_threshold][crate::model::ForecastParams::noise_threshold]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// let x = ForecastParams::new().set_or_clear_noise_threshold(Some(42.0)); - /// let x = ForecastParams::new().set_or_clear_noise_threshold(None::); - /// ``` - pub fn set_or_clear_noise_threshold(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.noise_threshold = v.map(|x| x.into()); - self - } - - /// Sets the value of [seasonality_hint][crate::model::ForecastParams::seasonality_hint]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// use google_cloud_timeseriesinsights_v1::model::forecast_params::Period; - /// let x0 = ForecastParams::new().set_seasonality_hint(Period::Hourly); - /// let x1 = ForecastParams::new().set_seasonality_hint(Period::Daily); - /// let x2 = ForecastParams::new().set_seasonality_hint(Period::Weekly); - /// ``` - pub fn set_seasonality_hint>( - mut self, - v: T, - ) -> Self { - self.seasonality_hint = v.into(); - self - } - - /// Sets the value of [horizon_duration][crate::model::ForecastParams::horizon_duration]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// use wkt::Duration; - /// let x = ForecastParams::new().set_horizon_duration(Duration::default()/* use setters */); - /// ``` - pub fn set_horizon_duration(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.horizon_duration = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [horizon_duration][crate::model::ForecastParams::horizon_duration]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// use wkt::Duration; - /// let x = ForecastParams::new().set_or_clear_horizon_duration(Some(Duration::default()/* use setters */)); - /// let x = ForecastParams::new().set_or_clear_horizon_duration(None::); - /// ``` - pub fn set_or_clear_horizon_duration(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.horizon_duration = v.map(|x| x.into()); - self - } -} - -impl wkt::message::Message for ForecastParams { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.ForecastParams" - } -} - -/// Defines additional types related to [ForecastParams]. -pub mod forecast_params { - #[allow(unused_imports)] - use super::*; - - /// A time period of a fixed interval. - /// - /// # Working with unknown values - /// - /// This enum is defined as `#[non_exhaustive]` because Google Cloud may add - /// additional enum variants at any time. Adding new variants is not considered - /// a breaking change. Applications should write their code in anticipation of: - /// - /// - New values appearing in future releases of the client library, **and** - /// - New values received dynamically, without application changes. - /// - /// Please consult the [Working with enums] section in the user guide for some - /// guidelines. - /// - /// [Working with enums]: https://googleapis.github.io/google-cloud-rust/working_with_enums.html - #[derive(Clone, Debug, PartialEq)] - #[non_exhaustive] - pub enum Period { - /// Unknown or simply not given. - Unspecified, - /// 1 hour - Hourly, - /// 24 hours - Daily, - /// 7 days - Weekly, - /// 30 days - Monthly, - /// 365 days - Yearly, - /// If set, the enum was initialized with an unknown value. - /// - /// Applications can examine the value using [Period::value] or - /// [Period::name]. - UnknownValue(period::UnknownValue), - } - - #[doc(hidden)] - pub mod period { - #[allow(unused_imports)] - use super::*; - #[derive(Clone, Debug, PartialEq)] - pub struct UnknownValue(pub(crate) wkt::internal::UnknownEnumValue); - } - - impl Period { - /// Gets the enum value. - /// - /// Returns `None` if the enum contains an unknown value deserialized from - /// the string representation of enums. - pub fn value(&self) -> std::option::Option { - match self { - Self::Unspecified => std::option::Option::Some(0), - Self::Hourly => std::option::Option::Some(5), - Self::Daily => std::option::Option::Some(1), - Self::Weekly => std::option::Option::Some(2), - Self::Monthly => std::option::Option::Some(3), - Self::Yearly => std::option::Option::Some(4), - Self::UnknownValue(u) => u.0.value(), - } - } - - /// Gets the enum value as a string. - /// - /// Returns `None` if the enum contains an unknown value deserialized from - /// the integer representation of enums. - pub fn name(&self) -> std::option::Option<&str> { - match self { - Self::Unspecified => std::option::Option::Some("PERIOD_UNSPECIFIED"), - Self::Hourly => std::option::Option::Some("HOURLY"), - Self::Daily => std::option::Option::Some("DAILY"), - Self::Weekly => std::option::Option::Some("WEEKLY"), - Self::Monthly => std::option::Option::Some("MONTHLY"), - Self::Yearly => std::option::Option::Some("YEARLY"), - Self::UnknownValue(u) => u.0.name(), - } - } - } - - impl std::default::Default for Period { - fn default() -> Self { - use std::convert::From; - Self::from(0) - } - } - - impl std::fmt::Display for Period { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - wkt::internal::display_enum(f, self.name(), self.value()) - } - } - - impl std::convert::From for Period { - fn from(value: i32) -> Self { - match value { - 0 => Self::Unspecified, - 1 => Self::Daily, - 2 => Self::Weekly, - 3 => Self::Monthly, - 4 => Self::Yearly, - 5 => Self::Hourly, - _ => Self::UnknownValue(period::UnknownValue( - wkt::internal::UnknownEnumValue::Integer(value), - )), - } - } - } - - impl std::convert::From<&str> for Period { - fn from(value: &str) -> Self { - use std::string::ToString; - match value { - "PERIOD_UNSPECIFIED" => Self::Unspecified, - "HOURLY" => Self::Hourly, - "DAILY" => Self::Daily, - "WEEKLY" => Self::Weekly, - "MONTHLY" => Self::Monthly, - "YEARLY" => Self::Yearly, - _ => Self::UnknownValue(period::UnknownValue( - wkt::internal::UnknownEnumValue::String(value.to_string()), - )), - } - } - } - - impl serde::ser::Serialize for Period { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - match self { - Self::Unspecified => serializer.serialize_i32(0), - Self::Hourly => serializer.serialize_i32(5), - Self::Daily => serializer.serialize_i32(1), - Self::Weekly => serializer.serialize_i32(2), - Self::Monthly => serializer.serialize_i32(3), - Self::Yearly => serializer.serialize_i32(4), - Self::UnknownValue(u) => u.0.serialize(serializer), - } - } - } - - impl<'de> serde::de::Deserialize<'de> for Period { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_any(wkt::internal::EnumVisitor::::new( - ".google.cloud.timeseriesinsights.v1.ForecastParams.Period", - )) - } - } -} - -/// A point in a time series. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct TimeseriesPoint { - /// The timestamp of this point. - pub time: std::option::Option, - - /// The value for this point. - /// - /// It is computed by aggregating all events in the associated slice that are - /// in the `[time, time + granularity]` range (see - /// [granularity][google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity]) using the specified - /// [metric][google.cloud.timeseriesinsights.v1.TimeseriesParams.metric]. - /// - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity]: crate::model::TimeseriesParams::granularity - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.metric]: crate::model::TimeseriesParams::metric - pub value: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl TimeseriesPoint { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [time][crate::model::TimeseriesPoint::time]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesPoint; - /// use wkt::Timestamp; - /// let x = TimeseriesPoint::new().set_time(Timestamp::default()/* use setters */); - /// ``` - pub fn set_time(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.time = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [time][crate::model::TimeseriesPoint::time]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesPoint; - /// use wkt::Timestamp; - /// let x = TimeseriesPoint::new().set_or_clear_time(Some(Timestamp::default()/* use setters */)); - /// let x = TimeseriesPoint::new().set_or_clear_time(None::); - /// ``` - pub fn set_or_clear_time(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.time = v.map(|x| x.into()); - self - } - - /// Sets the value of [value][crate::model::TimeseriesPoint::value]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesPoint; - /// let x = TimeseriesPoint::new().set_value(42.0); - /// ``` - pub fn set_value(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.value = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [value][crate::model::TimeseriesPoint::value]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesPoint; - /// let x = TimeseriesPoint::new().set_or_clear_value(Some(42.0)); - /// let x = TimeseriesPoint::new().set_or_clear_value(None::); - /// ``` - pub fn set_or_clear_value(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.value = v.map(|x| x.into()); - self - } -} - -impl wkt::message::Message for TimeseriesPoint { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.TimeseriesPoint" - } -} - -/// A time series. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct Timeseries { - /// The points in this time series, ordered by their timestamp. - pub point: std::vec::Vec, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl Timeseries { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [point][crate::model::Timeseries::point]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::Timeseries; - /// use google_cloud_timeseriesinsights_v1::model::TimeseriesPoint; - /// let x = Timeseries::new() - /// .set_point([ - /// TimeseriesPoint::default()/* use setters */, - /// TimeseriesPoint::default()/* use (different) setters */, - /// ]); - /// ``` - pub fn set_point(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.point = v.into_iter().map(|i| i.into()).collect(); - self - } -} - -impl wkt::message::Message for Timeseries { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.Timeseries" - } -} - -/// Forecast result for a given slice. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct EvaluatedSlice { - /// Values for all categorical dimensions that uniquely identify this slice. - pub dimensions: std::vec::Vec, - - /// The actual value at the detection time (see - /// [detectionTime][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time]). - /// - /// **NOTE**: This value can be an estimate, so it should not be used as a - /// source of truth. - /// - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time]: crate::model::QueryDataSetRequest::detection_time - pub detection_point_actual: std::option::Option, - - /// The expected value at the detection time, which is obtained by forecasting - /// on the historical time series. - pub detection_point_forecast: std::option::Option, - - /// How much our forecast model expects the detection point actual will - /// deviate from its forecasted value based on how well it fit the input time - /// series. - /// - /// In general, we expect the `detectionPointActual` to - /// be in the `[detectionPointForecast - expectedDeviation, - /// detectionPointForecast + expectedDeviation]` range. The more the actual - /// value is outside this range, the more statistically significant the - /// anomaly is. - /// - /// The expected deviation is always positive. - pub expected_deviation: std::option::Option, - - /// Summarizes how significant the change between the actual and forecasted - /// detection points are compared with the historical patterns observed on the - /// [history][google.cloud.timeseriesinsights.v1.EvaluatedSlice.history] time series. - /// - /// Defined as *|a - f| / (e + nt)*, where: - /// - /// - *a* is the [detectionPointActual][google.cloud.timeseriesinsights.v1.EvaluatedSlice.detection_point_actual]. - /// - *f* is the [detectionPointForecast][google.cloud.timeseriesinsights.v1.EvaluatedSlice.detection_point_forecast]. - /// - *e* is the [expectedDeviation][google.cloud.timeseriesinsights.v1.EvaluatedSlice.expected_deviation]. - /// - *nt` is the [noiseThreshold][google.cloud.timeseriesinsights.v1.ForecastParams.noise_threshold]. - /// - /// Anomaly scores between different requests and datasets are comparable. As - /// a guideline, the risk of a slice being an anomaly based on the anomaly - /// score is: - /// - /// - **Very High** if `anomalyScore` > 5. - /// - **High** if the `anomalyScore` is in the [2, 5] range. - /// - **Medium** if the `anomalyScore` is in the [1, 2) range. - /// - **Low** if the `anomalyScore` is < 1. - /// - /// If there were issues evaluating this slice, then the anomaly score will be - /// set to -1.0 and the [status][google.cloud.timeseriesinsights.v1.EvaluatedSlice.status] field will contain details on what - /// went wrong. - /// - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.detection_point_actual]: crate::model::EvaluatedSlice::detection_point_actual - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.detection_point_forecast]: crate::model::EvaluatedSlice::detection_point_forecast - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.expected_deviation]: crate::model::EvaluatedSlice::expected_deviation - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.history]: crate::model::EvaluatedSlice::history - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.status]: crate::model::EvaluatedSlice::status - /// [google.cloud.timeseriesinsights.v1.ForecastParams.noise_threshold]: crate::model::ForecastParams::noise_threshold - pub anomaly_score: std::option::Option, - - /// The actual values in the `[` - /// [detectionTime][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time] `-` - /// [forecastHistory][google.cloud.timeseriesinsights.v1.TimeseriesParams.forecast_history]`,` - /// [detectionTime][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time] `]` time - /// range. - /// - /// **NOTE**: This field is only populated if - /// [returnTimeseries][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.return_timeseries] is true. - /// - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time]: crate::model::QueryDataSetRequest::detection_time - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.return_timeseries]: crate::model::QueryDataSetRequest::return_timeseries - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.forecast_history]: crate::model::TimeseriesParams::forecast_history - pub history: std::option::Option, - - /// The forecasted values in the `[` - /// [detectionTime][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time] `+` - /// [granularity][google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity]`,` - /// [forecastParams.horizonTime][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.forecast_params] `]` time - /// range. - /// - /// **NOTE**: This field is only populated if - /// [returnTimeseries][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.return_timeseries] is true. - /// - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time]: crate::model::QueryDataSetRequest::detection_time - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.forecast_params]: crate::model::QueryDataSetRequest::forecast_params - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.return_timeseries]: crate::model::QueryDataSetRequest::return_timeseries - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity]: crate::model::TimeseriesParams::granularity - pub forecast: std::option::Option, - - /// Evaluation status. Contains an error message if the `anomalyScore` is < 0. - /// - /// Possible error messages: - /// - /// - **"Time series too sparse"**: The returned time series for this slice did - /// not contain enough data points (we require a minimum of 10). - /// - **"Not enough recent time series points"**: The time series contains the - /// minimum of 10 points, but there are not enough close in time to the - /// detection point. - /// - **"Missing detection point data"**: There were not events to be - /// aggregated within the `[detectionTime, detectionTime + granularity]` time - /// interval, so we don't have an actual value with which we can compare our - /// prediction. - /// - **"Data retrieval error"**: We failed to retrieve the time series data - /// for this slice and could not evaluate it successfully. Should be a - /// transient error. - /// - **"Internal server error"**: Internal unexpected error. - pub status: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl EvaluatedSlice { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [dimensions][crate::model::EvaluatedSlice::dimensions]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// use google_cloud_timeseriesinsights_v1::model::PinnedDimension; - /// let x = EvaluatedSlice::new() - /// .set_dimensions([ - /// PinnedDimension::default()/* use setters */, - /// PinnedDimension::default()/* use (different) setters */, - /// ]); - /// ``` - pub fn set_dimensions(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.dimensions = v.into_iter().map(|i| i.into()).collect(); - self - } - - /// Sets the value of [detection_point_actual][crate::model::EvaluatedSlice::detection_point_actual]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// let x = EvaluatedSlice::new().set_detection_point_actual(42.0); - /// ``` - pub fn set_detection_point_actual(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.detection_point_actual = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [detection_point_actual][crate::model::EvaluatedSlice::detection_point_actual]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// let x = EvaluatedSlice::new().set_or_clear_detection_point_actual(Some(42.0)); - /// let x = EvaluatedSlice::new().set_or_clear_detection_point_actual(None::); - /// ``` - pub fn set_or_clear_detection_point_actual(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.detection_point_actual = v.map(|x| x.into()); - self - } - - /// Sets the value of [detection_point_forecast][crate::model::EvaluatedSlice::detection_point_forecast]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// let x = EvaluatedSlice::new().set_detection_point_forecast(42.0); - /// ``` - pub fn set_detection_point_forecast(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.detection_point_forecast = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [detection_point_forecast][crate::model::EvaluatedSlice::detection_point_forecast]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// let x = EvaluatedSlice::new().set_or_clear_detection_point_forecast(Some(42.0)); - /// let x = EvaluatedSlice::new().set_or_clear_detection_point_forecast(None::); - /// ``` - pub fn set_or_clear_detection_point_forecast(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.detection_point_forecast = v.map(|x| x.into()); - self - } - - /// Sets the value of [expected_deviation][crate::model::EvaluatedSlice::expected_deviation]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// let x = EvaluatedSlice::new().set_expected_deviation(42.0); - /// ``` - pub fn set_expected_deviation(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.expected_deviation = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [expected_deviation][crate::model::EvaluatedSlice::expected_deviation]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// let x = EvaluatedSlice::new().set_or_clear_expected_deviation(Some(42.0)); - /// let x = EvaluatedSlice::new().set_or_clear_expected_deviation(None::); - /// ``` - pub fn set_or_clear_expected_deviation(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.expected_deviation = v.map(|x| x.into()); - self - } - - /// Sets the value of [anomaly_score][crate::model::EvaluatedSlice::anomaly_score]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// let x = EvaluatedSlice::new().set_anomaly_score(42.0); - /// ``` - pub fn set_anomaly_score(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.anomaly_score = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [anomaly_score][crate::model::EvaluatedSlice::anomaly_score]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// let x = EvaluatedSlice::new().set_or_clear_anomaly_score(Some(42.0)); - /// let x = EvaluatedSlice::new().set_or_clear_anomaly_score(None::); - /// ``` - pub fn set_or_clear_anomaly_score(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.anomaly_score = v.map(|x| x.into()); - self - } - - /// Sets the value of [history][crate::model::EvaluatedSlice::history]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// use google_cloud_timeseriesinsights_v1::model::Timeseries; - /// let x = EvaluatedSlice::new().set_history(Timeseries::default()/* use setters */); - /// ``` - pub fn set_history(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.history = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [history][crate::model::EvaluatedSlice::history]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// use google_cloud_timeseriesinsights_v1::model::Timeseries; - /// let x = EvaluatedSlice::new().set_or_clear_history(Some(Timeseries::default()/* use setters */)); - /// let x = EvaluatedSlice::new().set_or_clear_history(None::); - /// ``` - pub fn set_or_clear_history(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.history = v.map(|x| x.into()); - self - } - - /// Sets the value of [forecast][crate::model::EvaluatedSlice::forecast]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// use google_cloud_timeseriesinsights_v1::model::Timeseries; - /// let x = EvaluatedSlice::new().set_forecast(Timeseries::default()/* use setters */); - /// ``` - pub fn set_forecast(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.forecast = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [forecast][crate::model::EvaluatedSlice::forecast]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// use google_cloud_timeseriesinsights_v1::model::Timeseries; - /// let x = EvaluatedSlice::new().set_or_clear_forecast(Some(Timeseries::default()/* use setters */)); - /// let x = EvaluatedSlice::new().set_or_clear_forecast(None::); - /// ``` - pub fn set_or_clear_forecast(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.forecast = v.map(|x| x.into()); - self - } - - /// Sets the value of [status][crate::model::EvaluatedSlice::status]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// use google_cloud_rpc::model::Status; - /// let x = EvaluatedSlice::new().set_status(Status::default()/* use setters */); - /// ``` - pub fn set_status(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.status = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [status][crate::model::EvaluatedSlice::status]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// use google_cloud_rpc::model::Status; - /// let x = EvaluatedSlice::new().set_or_clear_status(Some(Status::default()/* use setters */)); - /// let x = EvaluatedSlice::new().set_or_clear_status(None::); - /// ``` - pub fn set_or_clear_status(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.status = v.map(|x| x.into()); - self - } -} - -impl wkt::message::Message for EvaluatedSlice { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.EvaluatedSlice" - } -} - -/// Parameters that control how we slice the dataset and, optionally, filter -/// slices that have some specific values on some dimensions (pinned dimensions). -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct SlicingParams { - /// Required. Dimensions over which we will group the events in slices. The names - /// specified here come from the - /// [EventDimension.name][google.cloud.timeseriesinsights.v1.EventDimension.name] field. At least - /// one dimension name must be specified. All dimension names that do not exist - /// in the queried `DataSet` will be ignored. - /// - /// Currently only dimensions that hold string values can be specified here. - /// - /// [google.cloud.timeseriesinsights.v1.EventDimension.name]: crate::model::EventDimension::name - pub dimension_names: std::vec::Vec, - - /// Optional. We will only analyze slices for which - /// [EvaluatedSlice.dimensions][google.cloud.timeseriesinsights.v1.EvaluatedSlice.dimensions] contain all of the - /// following pinned dimensions. A query with a pinned dimension `{ name: "d3" - /// stringVal: "v3" }` will only analyze events which contain the dimension `{ - /// name: "d3" stringVal: "v3" }`. - /// The [pinnedDimensions][google.cloud.timeseriesinsights.v1.SlicingParams.pinned_dimensions] and - /// [dimensionNames][google.cloud.timeseriesinsights.v1.SlicingParams.dimension_names] fields can **not** - /// share the same dimension names. - /// - /// Example a valid specification: - /// - /// ```norust - /// { - /// dimensionNames: ["d1", "d2"], - /// pinnedDimensions: [ - /// { name: "d3" stringVal: "v3" }, - /// { name: "d4" stringVal: "v4" } - /// ] - /// } - /// ``` - /// - /// In the previous example we will slice the dataset by dimensions "d1", - /// "d2", "d3" and "d4", but we will only analyze slices for which "d3=v3" and - /// "d4=v4". - /// - /// The following example is **invalid** as "d2" is present in both - /// dimensionNames and pinnedDimensions: - /// - /// ```norust - /// { - /// dimensionNames: ["d1", "d2"], - /// pinnedDimensions: [ - /// { name: "d2" stringVal: "v2" }, - /// { name: "d4" stringVal: "v4" } - /// ] - /// } - /// ``` - /// - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.dimensions]: crate::model::EvaluatedSlice::dimensions - /// [google.cloud.timeseriesinsights.v1.SlicingParams.dimension_names]: crate::model::SlicingParams::dimension_names - /// [google.cloud.timeseriesinsights.v1.SlicingParams.pinned_dimensions]: crate::model::SlicingParams::pinned_dimensions - pub pinned_dimensions: std::vec::Vec, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl SlicingParams { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [dimension_names][crate::model::SlicingParams::dimension_names]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::SlicingParams; - /// let x = SlicingParams::new().set_dimension_names(["a", "b", "c"]); - /// ``` - pub fn set_dimension_names(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.dimension_names = v.into_iter().map(|i| i.into()).collect(); - self - } - - /// Sets the value of [pinned_dimensions][crate::model::SlicingParams::pinned_dimensions]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::SlicingParams; - /// use google_cloud_timeseriesinsights_v1::model::PinnedDimension; - /// let x = SlicingParams::new() - /// .set_pinned_dimensions([ - /// PinnedDimension::default()/* use setters */, - /// PinnedDimension::default()/* use (different) setters */, - /// ]); - /// ``` - pub fn set_pinned_dimensions(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.pinned_dimensions = v.into_iter().map(|i| i.into()).collect(); - self - } -} - -impl wkt::message::Message for SlicingParams { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.SlicingParams" - } -} - -/// Parameters that control how we construct the time series for each slice. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct TimeseriesParams { - /// Required. How long should we go in the past when fetching the timeline used for - /// forecasting each slice. - /// - /// This is used in combination with the - /// [detectionTime][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time] parameter. - /// The time series we construct will have the following time range: - /// `[detectionTime - forecastHistory, detectionTime + granularity]`. - /// - /// The forecast history might be rounded up, so that a multiple of - /// `granularity` is used to process the query. - /// - /// Note: If there are not enough events in the - /// `[detectionTime - forecastHistory, detectionTime + granularity]` time - /// interval, the slice evaluation can fail. For more information, see - /// [EvaluatedSlice.status][google.cloud.timeseriesinsights.v1.EvaluatedSlice.status]. - /// - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.status]: crate::model::EvaluatedSlice::status - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time]: crate::model::QueryDataSetRequest::detection_time - pub forecast_history: std::option::Option, - - /// Required. The time granularity of the time series (on the x-axis). Each time series - /// point starting at time T will aggregate all events for a particular slice - /// in *[T, T + granularity)* time windows. - /// - /// Note: The aggregation is decided based on the - /// [metric][google.cloud.timeseriesinsights.v1.TimeseriesParams.metric] parameter. - /// - /// This granularity defines the query-time aggregation windows and is not - /// necessarily related to any event time granularity in the raw data (though - /// we do recommend that the query-time granularity is not finer than the - /// ingestion-time one). - /// - /// Currently, the minimal supported granularity is 10 seconds. - /// - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.metric]: crate::model::TimeseriesParams::metric - pub granularity: std::option::Option, - - /// Optional. Denotes the [name][google.cloud.timeseriesinsights.v1.EventDimension.name] of a numerical - /// dimension that will have its values aggregated to compute the y-axis of the - /// time series. - /// - /// The aggregation method must also be specified by setting the - /// [metricAggregationMethod][google.cloud.timeseriesinsights.v1.TimeseriesParams.metric_aggregation_method] - /// field. - /// - /// Note: Currently, if the aggregation method is unspecified, we will - /// default to SUM for backward compatibility reasons, but new implementations - /// should set the - /// [metricAggregationMethod][google.cloud.timeseriesinsights.v1.TimeseriesParams.metric_aggregation_method] - /// explicitly. - /// - /// If the metric is unspecified, we will use the number of events that each - /// time series point contains as the point value. - /// - /// Example: Let's assume we have the following three events in our dataset: - /// - /// ```norust - /// { - /// eventTime: "2020-12-27T00:00:00Z", - /// dimensions: [ - /// { name: "d1" stringVal: "v1" }, - /// { name: "d2" stringVal: "v2" } - /// { name: "m1" longVal: 100 } - /// { name: "m2" longVal: 11 } - /// ] - /// }, - /// { - /// eventTime: "2020-12-27T00:10:00Z", - /// dimensions: [ - /// { name: "d1" stringVal: "v1" }, - /// { name: "d2" stringVal: "v2" } - /// { name: "m1" longVal: 200 } - /// { name: "m2" longVal: 22 } - /// ] - /// }, - /// { - /// eventTime: "2020-12-27T00:20:00Z", - /// dimensions: [ - /// { name: "d1" stringVal: "v1" }, - /// { name: "d2" stringVal: "v2" } - /// { name: "m1" longVal: 300 } - /// { name: "m2" longVal: 33 } - /// ] - /// } - /// ``` - /// - /// These events are all within the same hour, spaced 10 minutes between each - /// of them. Assuming our [QueryDataSetRequest][google.cloud.timeseriesinsights.v1.QueryDataSetRequest] had set - /// [slicingParams.dimensionNames][google.cloud.timeseriesinsights.v1.SlicingParams.dimension_names] to ["d1", - /// "d2"] and [timeseries_params.granularity][google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity] to - /// "3600s", then all the previous events will be aggregated into the same - /// [timeseries point][google.cloud.timeseriesinsights.v1.TimeseriesPoint]. - /// - /// The time series point that they're all part of will have the - /// [time][google.cloud.timeseriesinsights.v1.TimeseriesPoint.time] set to "2020-12-27T00:00:00Z" and the - /// [value][google.cloud.timeseriesinsights.v1.TimeseriesPoint.value] populated based on this metric field: - /// - /// - If the metric is set to "m1" and metric_aggregation_method to SUM, then - /// the value of the point will be 600. - /// - If the metric is set to "m2" and metric_aggregation_method to SUM, then - /// the value of the point will be 66. - /// - If the metric is set to "m1" and metric_aggregation_method to AVERAGE, - /// then the value of the point will be 200. - /// - If the metric is set to "m2" and metric_aggregation_method to AVERAGE, - /// then the value of the point will be 22. - /// - If the metric field is "" or unspecified, then the value of the point - /// will be 3, as we will simply count the events. - /// - /// [google.cloud.timeseriesinsights.v1.EventDimension.name]: crate::model::EventDimension::name - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest]: crate::model::QueryDataSetRequest - /// [google.cloud.timeseriesinsights.v1.SlicingParams.dimension_names]: crate::model::SlicingParams::dimension_names - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity]: crate::model::TimeseriesParams::granularity - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.metric_aggregation_method]: crate::model::TimeseriesParams::metric_aggregation_method - /// [google.cloud.timeseriesinsights.v1.TimeseriesPoint]: crate::model::TimeseriesPoint - /// [google.cloud.timeseriesinsights.v1.TimeseriesPoint.time]: crate::model::TimeseriesPoint::time - /// [google.cloud.timeseriesinsights.v1.TimeseriesPoint.value]: crate::model::TimeseriesPoint::value - pub metric: std::option::Option, - - /// Optional. Together with the [metric][google.cloud.timeseriesinsights.v1.TimeseriesParams.metric] field, specifies how - /// we will aggregate multiple events to obtain the value of a time series - /// point. See the [metric][google.cloud.timeseriesinsights.v1.TimeseriesParams.metric] documentation for more - /// details. - /// - /// If the metric is not specified or "", then this field will be ignored. - /// - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.metric]: crate::model::TimeseriesParams::metric - pub metric_aggregation_method: crate::model::timeseries_params::AggregationMethod, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl TimeseriesParams { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [forecast_history][crate::model::TimeseriesParams::forecast_history]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// use wkt::Duration; - /// let x = TimeseriesParams::new().set_forecast_history(Duration::default()/* use setters */); - /// ``` - pub fn set_forecast_history(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.forecast_history = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [forecast_history][crate::model::TimeseriesParams::forecast_history]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// use wkt::Duration; - /// let x = TimeseriesParams::new().set_or_clear_forecast_history(Some(Duration::default()/* use setters */)); - /// let x = TimeseriesParams::new().set_or_clear_forecast_history(None::); - /// ``` - pub fn set_or_clear_forecast_history(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.forecast_history = v.map(|x| x.into()); - self - } - - /// Sets the value of [granularity][crate::model::TimeseriesParams::granularity]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// use wkt::Duration; - /// let x = TimeseriesParams::new().set_granularity(Duration::default()/* use setters */); - /// ``` - pub fn set_granularity(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.granularity = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [granularity][crate::model::TimeseriesParams::granularity]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// use wkt::Duration; - /// let x = TimeseriesParams::new().set_or_clear_granularity(Some(Duration::default()/* use setters */)); - /// let x = TimeseriesParams::new().set_or_clear_granularity(None::); - /// ``` - pub fn set_or_clear_granularity(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.granularity = v.map(|x| x.into()); - self - } - - /// Sets the value of [metric][crate::model::TimeseriesParams::metric]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// let x = TimeseriesParams::new().set_metric("example"); - /// ``` - pub fn set_metric(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.metric = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [metric][crate::model::TimeseriesParams::metric]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// let x = TimeseriesParams::new().set_or_clear_metric(Some("example")); - /// let x = TimeseriesParams::new().set_or_clear_metric(None::); - /// ``` - pub fn set_or_clear_metric(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.metric = v.map(|x| x.into()); - self - } - - /// Sets the value of [metric_aggregation_method][crate::model::TimeseriesParams::metric_aggregation_method]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// use google_cloud_timeseriesinsights_v1::model::timeseries_params::AggregationMethod; - /// let x0 = TimeseriesParams::new().set_metric_aggregation_method(AggregationMethod::Sum); - /// let x1 = TimeseriesParams::new().set_metric_aggregation_method(AggregationMethod::Average); - /// ``` - pub fn set_metric_aggregation_method< - T: std::convert::Into, - >( - mut self, - v: T, - ) -> Self { - self.metric_aggregation_method = v.into(); - self - } -} - -impl wkt::message::Message for TimeseriesParams { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.TimeseriesParams" - } -} - -/// Defines additional types related to [TimeseriesParams]. -pub mod timeseries_params { - #[allow(unused_imports)] - use super::*; - - /// Methods by which we can aggregate multiple events by a given - /// [metric][google.cloud.timeseriesinsights.v1.TimeseriesParams.metric]. - /// - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.metric]: crate::model::TimeseriesParams::metric - /// - /// # Working with unknown values - /// - /// This enum is defined as `#[non_exhaustive]` because Google Cloud may add - /// additional enum variants at any time. Adding new variants is not considered - /// a breaking change. Applications should write their code in anticipation of: - /// - /// - New values appearing in future releases of the client library, **and** - /// - New values received dynamically, without application changes. - /// - /// Please consult the [Working with enums] section in the user guide for some - /// guidelines. - /// - /// [Working with enums]: https://googleapis.github.io/google-cloud-rust/working_with_enums.html - #[derive(Clone, Debug, PartialEq)] - #[non_exhaustive] - pub enum AggregationMethod { - /// Unspecified. - Unspecified, - /// Aggregate multiple events by summing up the values found in the - /// [metric][google.cloud.timeseriesinsights.v1.TimeseriesParams.metric] dimension. - /// - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.metric]: crate::model::TimeseriesParams::metric - Sum, - /// Aggregate multiple events by averaging out the values found in the - /// [metric][google.cloud.timeseriesinsights.v1.TimeseriesParams.metric] dimension. - /// - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.metric]: crate::model::TimeseriesParams::metric - Average, - /// If set, the enum was initialized with an unknown value. - /// - /// Applications can examine the value using [AggregationMethod::value] or - /// [AggregationMethod::name]. - UnknownValue(aggregation_method::UnknownValue), - } - - #[doc(hidden)] - pub mod aggregation_method { - #[allow(unused_imports)] - use super::*; - #[derive(Clone, Debug, PartialEq)] - pub struct UnknownValue(pub(crate) wkt::internal::UnknownEnumValue); - } - - impl AggregationMethod { - /// Gets the enum value. - /// - /// Returns `None` if the enum contains an unknown value deserialized from - /// the string representation of enums. - pub fn value(&self) -> std::option::Option { - match self { - Self::Unspecified => std::option::Option::Some(0), - Self::Sum => std::option::Option::Some(1), - Self::Average => std::option::Option::Some(2), - Self::UnknownValue(u) => u.0.value(), - } - } - - /// Gets the enum value as a string. - /// - /// Returns `None` if the enum contains an unknown value deserialized from - /// the integer representation of enums. - pub fn name(&self) -> std::option::Option<&str> { - match self { - Self::Unspecified => std::option::Option::Some("AGGREGATION_METHOD_UNSPECIFIED"), - Self::Sum => std::option::Option::Some("SUM"), - Self::Average => std::option::Option::Some("AVERAGE"), - Self::UnknownValue(u) => u.0.name(), - } - } - } - - impl std::default::Default for AggregationMethod { - fn default() -> Self { - use std::convert::From; - Self::from(0) - } - } - - impl std::fmt::Display for AggregationMethod { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - wkt::internal::display_enum(f, self.name(), self.value()) - } - } - - impl std::convert::From for AggregationMethod { - fn from(value: i32) -> Self { - match value { - 0 => Self::Unspecified, - 1 => Self::Sum, - 2 => Self::Average, - _ => Self::UnknownValue(aggregation_method::UnknownValue( - wkt::internal::UnknownEnumValue::Integer(value), - )), - } - } - } - - impl std::convert::From<&str> for AggregationMethod { - fn from(value: &str) -> Self { - use std::string::ToString; - match value { - "AGGREGATION_METHOD_UNSPECIFIED" => Self::Unspecified, - "SUM" => Self::Sum, - "AVERAGE" => Self::Average, - _ => Self::UnknownValue(aggregation_method::UnknownValue( - wkt::internal::UnknownEnumValue::String(value.to_string()), - )), - } - } - } - - impl serde::ser::Serialize for AggregationMethod { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - match self { - Self::Unspecified => serializer.serialize_i32(0), - Self::Sum => serializer.serialize_i32(1), - Self::Average => serializer.serialize_i32(2), - Self::UnknownValue(u) => u.0.serialize(serializer), - } - } - } - - impl<'de> serde::de::Deserialize<'de> for AggregationMethod { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_any(wkt::internal::EnumVisitor::::new( - ".google.cloud.timeseriesinsights.v1.TimeseriesParams.AggregationMethod", - )) - } - } -} - -/// Request for performing a query against a loaded DataSet. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct QueryDataSetRequest { - /// Required. Loaded DataSet to be queried in the format of - /// "projects/{project}/datasets/{dataset}" - pub name: std::string::String, - - /// Required. This is the point in time that we want to probe for anomalies. - /// - /// The corresponding [TimeseriesPoint][google.cloud.timeseriesinsights.v1.TimeseriesPoint] is referred to as the - /// detection point. - /// - /// **NOTE**: As with any other time series point, the value is given by - /// aggregating all events in the slice that are in the - /// [detectionTime, detectionTime + granularity) time interval, where - /// the granularity is specified in the - /// [timeseriesParams.granularity][google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity] field. - /// - /// [google.cloud.timeseriesinsights.v1.TimeseriesParams.granularity]: crate::model::TimeseriesParams::granularity - /// [google.cloud.timeseriesinsights.v1.TimeseriesPoint]: crate::model::TimeseriesPoint - pub detection_time: std::option::Option, - - /// How many slices are returned in - /// [QueryDataSetResponse.slices][google.cloud.timeseriesinsights.v1.QueryDataSetResponse.slices]. - /// - /// The returned slices are tentatively the ones with the highest - /// [anomaly scores][google.cloud.timeseriesinsights.v1.EvaluatedSlice.anomaly_score] in the dataset that match - /// the query, but it is not guaranteed. - /// - /// Reducing this number will improve query performance, both in terms of - /// latency and resource usage. - /// - /// Defaults to 50. - /// - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.anomaly_score]: crate::model::EvaluatedSlice::anomaly_score - /// [google.cloud.timeseriesinsights.v1.QueryDataSetResponse.slices]: crate::model::QueryDataSetResponse::slices - pub num_returned_slices: std::option::Option, - - /// Parameters controlling how we will split the dataset into the slices that - /// we will analyze. - pub slicing_params: std::option::Option, - - /// Parameters controlling how we will build the time series used to predict - /// the [detectionTime][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time] value for each slice. - /// - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time]: crate::model::QueryDataSetRequest::detection_time - pub timeseries_params: std::option::Option, - - /// Parameters that control the time series forecasting models, such as the - /// sensitivity of the anomaly detection. - pub forecast_params: std::option::Option, - - /// If specified, we will return the actual and forecasted time for all - /// returned slices. - /// - /// The time series are returned in the - /// [EvaluatedSlice.history][google.cloud.timeseriesinsights.v1.EvaluatedSlice.history] and - /// [EvaluatedSlice.forecast][google.cloud.timeseriesinsights.v1.EvaluatedSlice.forecast] fields. - /// - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.forecast]: crate::model::EvaluatedSlice::forecast - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.history]: crate::model::EvaluatedSlice::history - pub return_timeseries: bool, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl QueryDataSetRequest { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [name][crate::model::QueryDataSetRequest::name]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// # let project_id = "project_id"; - /// # let dataset_id = "dataset_id"; - /// let x = QueryDataSetRequest::new().set_name(format!("projects/{project_id}/datasets/{dataset_id}")); - /// ``` - pub fn set_name>(mut self, v: T) -> Self { - self.name = v.into(); - self - } - - /// Sets the value of [detection_time][crate::model::QueryDataSetRequest::detection_time]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// use wkt::Timestamp; - /// let x = QueryDataSetRequest::new().set_detection_time(Timestamp::default()/* use setters */); - /// ``` - pub fn set_detection_time(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.detection_time = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [detection_time][crate::model::QueryDataSetRequest::detection_time]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// use wkt::Timestamp; - /// let x = QueryDataSetRequest::new().set_or_clear_detection_time(Some(Timestamp::default()/* use setters */)); - /// let x = QueryDataSetRequest::new().set_or_clear_detection_time(None::); - /// ``` - pub fn set_or_clear_detection_time(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.detection_time = v.map(|x| x.into()); - self - } - - /// Sets the value of [num_returned_slices][crate::model::QueryDataSetRequest::num_returned_slices]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// let x = QueryDataSetRequest::new().set_num_returned_slices(42); - /// ``` - pub fn set_num_returned_slices(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.num_returned_slices = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [num_returned_slices][crate::model::QueryDataSetRequest::num_returned_slices]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// let x = QueryDataSetRequest::new().set_or_clear_num_returned_slices(Some(42)); - /// let x = QueryDataSetRequest::new().set_or_clear_num_returned_slices(None::); - /// ``` - pub fn set_or_clear_num_returned_slices(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.num_returned_slices = v.map(|x| x.into()); - self - } - - /// Sets the value of [slicing_params][crate::model::QueryDataSetRequest::slicing_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// use google_cloud_timeseriesinsights_v1::model::SlicingParams; - /// let x = QueryDataSetRequest::new().set_slicing_params(SlicingParams::default()/* use setters */); - /// ``` - pub fn set_slicing_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.slicing_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [slicing_params][crate::model::QueryDataSetRequest::slicing_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// use google_cloud_timeseriesinsights_v1::model::SlicingParams; - /// let x = QueryDataSetRequest::new().set_or_clear_slicing_params(Some(SlicingParams::default()/* use setters */)); - /// let x = QueryDataSetRequest::new().set_or_clear_slicing_params(None::); - /// ``` - pub fn set_or_clear_slicing_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.slicing_params = v.map(|x| x.into()); - self - } - - /// Sets the value of [timeseries_params][crate::model::QueryDataSetRequest::timeseries_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// let x = QueryDataSetRequest::new().set_timeseries_params(TimeseriesParams::default()/* use setters */); - /// ``` - pub fn set_timeseries_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.timeseries_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [timeseries_params][crate::model::QueryDataSetRequest::timeseries_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// let x = QueryDataSetRequest::new().set_or_clear_timeseries_params(Some(TimeseriesParams::default()/* use setters */)); - /// let x = QueryDataSetRequest::new().set_or_clear_timeseries_params(None::); - /// ``` - pub fn set_or_clear_timeseries_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.timeseries_params = v.map(|x| x.into()); - self - } - - /// Sets the value of [forecast_params][crate::model::QueryDataSetRequest::forecast_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// let x = QueryDataSetRequest::new().set_forecast_params(ForecastParams::default()/* use setters */); - /// ``` - pub fn set_forecast_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.forecast_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [forecast_params][crate::model::QueryDataSetRequest::forecast_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// let x = QueryDataSetRequest::new().set_or_clear_forecast_params(Some(ForecastParams::default()/* use setters */)); - /// let x = QueryDataSetRequest::new().set_or_clear_forecast_params(None::); - /// ``` - pub fn set_or_clear_forecast_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.forecast_params = v.map(|x| x.into()); - self - } - - /// Sets the value of [return_timeseries][crate::model::QueryDataSetRequest::return_timeseries]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetRequest; - /// let x = QueryDataSetRequest::new().set_return_timeseries(true); - /// ``` - pub fn set_return_timeseries>(mut self, v: T) -> Self { - self.return_timeseries = v.into(); - self - } -} - -impl wkt::message::Message for QueryDataSetRequest { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.QueryDataSetRequest" - } -} - -/// Response for a query executed by the system. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct QueryDataSetResponse { - /// Loaded DataSet that was queried. - pub name: std::string::String, - - /// Slices sorted in descending order by their - /// [anomalyScore][google.cloud.timeseriesinsights.v1.EvaluatedSlice.anomaly_score]. - /// - /// At most [numReturnedSlices][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.num_returned_slices] - /// slices are present in this field. - /// - /// [google.cloud.timeseriesinsights.v1.EvaluatedSlice.anomaly_score]: crate::model::EvaluatedSlice::anomaly_score - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.num_returned_slices]: crate::model::QueryDataSetRequest::num_returned_slices - pub slices: std::vec::Vec, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl QueryDataSetResponse { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [name][crate::model::QueryDataSetResponse::name]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetResponse; - /// let x = QueryDataSetResponse::new().set_name("example"); - /// ``` - pub fn set_name>(mut self, v: T) -> Self { - self.name = v.into(); - self - } - - /// Sets the value of [slices][crate::model::QueryDataSetResponse::slices]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::QueryDataSetResponse; - /// use google_cloud_timeseriesinsights_v1::model::EvaluatedSlice; - /// let x = QueryDataSetResponse::new() - /// .set_slices([ - /// EvaluatedSlice::default()/* use setters */, - /// EvaluatedSlice::default()/* use (different) setters */, - /// ]); - /// ``` - pub fn set_slices(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.slices = v.into_iter().map(|i| i.into()).collect(); - self - } -} - -impl wkt::message::Message for QueryDataSetResponse { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.QueryDataSetResponse" - } -} - -/// Request for evaluateSlice. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct EvaluateSliceRequest { - /// Required. Loaded DataSet to be queried in the format of - /// "projects/{project}/datasets/{dataset}" - pub dataset: std::string::String, - - /// Required. Dimensions with pinned values that specify the slice for which we will - /// fetch the time series. - pub pinned_dimensions: std::vec::Vec, - - /// Required. This is the point in time that we want to probe for anomalies. - /// - /// See documentation for - /// [QueryDataSetRequest.detectionTime][google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time]. - /// - /// [google.cloud.timeseriesinsights.v1.QueryDataSetRequest.detection_time]: crate::model::QueryDataSetRequest::detection_time - pub detection_time: std::option::Option, - - /// Parameters controlling how we will build the time series used to predict - /// the [detectionTime][google.cloud.timeseriesinsights.v1.EvaluateSliceRequest.detection_time] value for this slice. - /// - /// [google.cloud.timeseriesinsights.v1.EvaluateSliceRequest.detection_time]: crate::model::EvaluateSliceRequest::detection_time - pub timeseries_params: std::option::Option, - - /// Parameters that control the time series forecasting models, such as the - /// sensitivity of the anomaly detection. - pub forecast_params: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl EvaluateSliceRequest { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [dataset][crate::model::EvaluateSliceRequest::dataset]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateSliceRequest; - /// # let project_id = "project_id"; - /// # let dataset_id = "dataset_id"; - /// let x = EvaluateSliceRequest::new().set_dataset(format!("projects/{project_id}/datasets/{dataset_id}")); - /// ``` - pub fn set_dataset>(mut self, v: T) -> Self { - self.dataset = v.into(); - self - } - - /// Sets the value of [pinned_dimensions][crate::model::EvaluateSliceRequest::pinned_dimensions]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateSliceRequest; - /// use google_cloud_timeseriesinsights_v1::model::PinnedDimension; - /// let x = EvaluateSliceRequest::new() - /// .set_pinned_dimensions([ - /// PinnedDimension::default()/* use setters */, - /// PinnedDimension::default()/* use (different) setters */, - /// ]); - /// ``` - pub fn set_pinned_dimensions(mut self, v: T) -> Self - where - T: std::iter::IntoIterator, - V: std::convert::Into, - { - use std::iter::Iterator; - self.pinned_dimensions = v.into_iter().map(|i| i.into()).collect(); - self - } - - /// Sets the value of [detection_time][crate::model::EvaluateSliceRequest::detection_time]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateSliceRequest; - /// use wkt::Timestamp; - /// let x = EvaluateSliceRequest::new().set_detection_time(Timestamp::default()/* use setters */); - /// ``` - pub fn set_detection_time(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.detection_time = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [detection_time][crate::model::EvaluateSliceRequest::detection_time]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateSliceRequest; - /// use wkt::Timestamp; - /// let x = EvaluateSliceRequest::new().set_or_clear_detection_time(Some(Timestamp::default()/* use setters */)); - /// let x = EvaluateSliceRequest::new().set_or_clear_detection_time(None::); - /// ``` - pub fn set_or_clear_detection_time(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.detection_time = v.map(|x| x.into()); - self - } - - /// Sets the value of [timeseries_params][crate::model::EvaluateSliceRequest::timeseries_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateSliceRequest; - /// use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// let x = EvaluateSliceRequest::new().set_timeseries_params(TimeseriesParams::default()/* use setters */); - /// ``` - pub fn set_timeseries_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.timeseries_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [timeseries_params][crate::model::EvaluateSliceRequest::timeseries_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateSliceRequest; - /// use google_cloud_timeseriesinsights_v1::model::TimeseriesParams; - /// let x = EvaluateSliceRequest::new().set_or_clear_timeseries_params(Some(TimeseriesParams::default()/* use setters */)); - /// let x = EvaluateSliceRequest::new().set_or_clear_timeseries_params(None::); - /// ``` - pub fn set_or_clear_timeseries_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.timeseries_params = v.map(|x| x.into()); - self - } - - /// Sets the value of [forecast_params][crate::model::EvaluateSliceRequest::forecast_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateSliceRequest; - /// use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// let x = EvaluateSliceRequest::new().set_forecast_params(ForecastParams::default()/* use setters */); - /// ``` - pub fn set_forecast_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.forecast_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [forecast_params][crate::model::EvaluateSliceRequest::forecast_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateSliceRequest; - /// use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// let x = EvaluateSliceRequest::new().set_or_clear_forecast_params(Some(ForecastParams::default()/* use setters */)); - /// let x = EvaluateSliceRequest::new().set_or_clear_forecast_params(None::); - /// ``` - pub fn set_or_clear_forecast_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.forecast_params = v.map(|x| x.into()); - self - } -} - -impl wkt::message::Message for EvaluateSliceRequest { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.EvaluateSliceRequest" - } -} - -/// Request for evaluateTimeseries. -#[derive(Clone, Default, PartialEq)] -#[non_exhaustive] -pub struct EvaluateTimeseriesRequest { - /// Required. Client project name in the format of 'projects/{project}'. - pub parent: std::string::String, - - /// Evaluate this time series without requiring it was previously loaded in - /// a data set. - /// - /// The evaluated time series point is the last one, analogous to calling - /// evaluateSlice or query with - /// [detectionTime][google.cloud.timeseriesinsights.v1.EvaluateSliceRequest.detection_time] set to - /// `timeseries.point(timeseries.point_size() - 1).time`. - /// - /// The length of the time series must be at least 10. - /// - /// All points must have the same time offset relative to the granularity. For - /// example, if the [granularity][google.cloud.timeseriesinsights.v1.EvaluateTimeseriesRequest.granularity] is "5s", then the following - /// point.time sequences are valid: - /// - /// - "100s", "105s", "120s", "125s" (no offset) - /// - "102s", "107s", "122s", "127s" (offset is "2s") - /// However, the following sequence is invalid as it has inconsistent offsets: - /// - "100s", "105s", "122s", "127s" (offsets are either "0s" or "2s") - /// - /// [google.cloud.timeseriesinsights.v1.EvaluateSliceRequest.detection_time]: crate::model::EvaluateSliceRequest::detection_time - /// [google.cloud.timeseriesinsights.v1.EvaluateTimeseriesRequest.granularity]: crate::model::EvaluateTimeseriesRequest::granularity - pub timeseries: std::option::Option, - - /// The granularity of the time series (time distance between two consecutive - /// points). - pub granularity: std::option::Option, - - /// The forecast parameters. - pub forecast_params: std::option::Option, - - pub(crate) _unknown_fields: serde_json::Map, -} - -impl EvaluateTimeseriesRequest { - /// Creates a new default instance. - pub fn new() -> Self { - std::default::Default::default() - } - - /// Sets the value of [parent][crate::model::EvaluateTimeseriesRequest::parent]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateTimeseriesRequest; - /// let x = EvaluateTimeseriesRequest::new().set_parent("example"); - /// ``` - pub fn set_parent>(mut self, v: T) -> Self { - self.parent = v.into(); - self - } - - /// Sets the value of [timeseries][crate::model::EvaluateTimeseriesRequest::timeseries]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateTimeseriesRequest; - /// use google_cloud_timeseriesinsights_v1::model::Timeseries; - /// let x = EvaluateTimeseriesRequest::new().set_timeseries(Timeseries::default()/* use setters */); - /// ``` - pub fn set_timeseries(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.timeseries = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [timeseries][crate::model::EvaluateTimeseriesRequest::timeseries]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateTimeseriesRequest; - /// use google_cloud_timeseriesinsights_v1::model::Timeseries; - /// let x = EvaluateTimeseriesRequest::new().set_or_clear_timeseries(Some(Timeseries::default()/* use setters */)); - /// let x = EvaluateTimeseriesRequest::new().set_or_clear_timeseries(None::); - /// ``` - pub fn set_or_clear_timeseries(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.timeseries = v.map(|x| x.into()); - self - } - - /// Sets the value of [granularity][crate::model::EvaluateTimeseriesRequest::granularity]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateTimeseriesRequest; - /// use wkt::Duration; - /// let x = EvaluateTimeseriesRequest::new().set_granularity(Duration::default()/* use setters */); - /// ``` - pub fn set_granularity(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.granularity = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [granularity][crate::model::EvaluateTimeseriesRequest::granularity]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateTimeseriesRequest; - /// use wkt::Duration; - /// let x = EvaluateTimeseriesRequest::new().set_or_clear_granularity(Some(Duration::default()/* use setters */)); - /// let x = EvaluateTimeseriesRequest::new().set_or_clear_granularity(None::); - /// ``` - pub fn set_or_clear_granularity(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.granularity = v.map(|x| x.into()); - self - } - - /// Sets the value of [forecast_params][crate::model::EvaluateTimeseriesRequest::forecast_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateTimeseriesRequest; - /// use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// let x = EvaluateTimeseriesRequest::new().set_forecast_params(ForecastParams::default()/* use setters */); - /// ``` - pub fn set_forecast_params(mut self, v: T) -> Self - where - T: std::convert::Into, - { - self.forecast_params = std::option::Option::Some(v.into()); - self - } - - /// Sets or clears the value of [forecast_params][crate::model::EvaluateTimeseriesRequest::forecast_params]. - /// - /// # Example - /// ```ignore,no_run - /// # use google_cloud_timeseriesinsights_v1::model::EvaluateTimeseriesRequest; - /// use google_cloud_timeseriesinsights_v1::model::ForecastParams; - /// let x = EvaluateTimeseriesRequest::new().set_or_clear_forecast_params(Some(ForecastParams::default()/* use setters */)); - /// let x = EvaluateTimeseriesRequest::new().set_or_clear_forecast_params(None::); - /// ``` - pub fn set_or_clear_forecast_params(mut self, v: std::option::Option) -> Self - where - T: std::convert::Into, - { - self.forecast_params = v.map(|x| x.into()); - self - } -} - -impl wkt::message::Message for EvaluateTimeseriesRequest { - fn typename() -> &'static str { - "type.googleapis.com/google.cloud.timeseriesinsights.v1.EvaluateTimeseriesRequest" - } -} diff --git a/src/generated/cloud/timeseriesinsights/v1/src/model/debug.rs b/src/generated/cloud/timeseriesinsights/v1/src/model/debug.rs deleted file mode 100644 index a558d4e4cd..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/src/model/debug.rs +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2025 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 -// -// https://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. -// -// Code generated by sidekick. DO NOT EDIT. - -#[allow(unused_imports)] -use super::*; - -impl std::fmt::Debug for super::BigqueryMapping { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("BigqueryMapping"); - debug_struct.field("timestamp_column", &self.timestamp_column); - debug_struct.field("group_id_column", &self.group_id_column); - debug_struct.field("dimension_column", &self.dimension_column); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::DataSource { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("DataSource"); - debug_struct.field("uri", &self.uri); - debug_struct.field("bq_mapping", &self.bq_mapping); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::DataSet { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("DataSet"); - debug_struct.field("name", &self.name); - debug_struct.field("data_names", &self.data_names); - debug_struct.field("data_sources", &self.data_sources); - debug_struct.field("state", &self.state); - debug_struct.field("status", &self.status); - debug_struct.field("ttl", &self.ttl); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::EventDimension { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("EventDimension"); - debug_struct.field("name", &self.name); - debug_struct.field("value", &self.value); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::Event { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("Event"); - debug_struct.field("dimensions", &self.dimensions); - debug_struct.field("group_id", &self.group_id); - debug_struct.field("event_time", &self.event_time); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::AppendEventsRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("AppendEventsRequest"); - debug_struct.field("events", &self.events); - debug_struct.field("dataset", &self.dataset); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::AppendEventsResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("AppendEventsResponse"); - debug_struct.field("dropped_events", &self.dropped_events); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::CreateDataSetRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("CreateDataSetRequest"); - debug_struct.field("parent", &self.parent); - debug_struct.field("dataset", &self.dataset); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::DeleteDataSetRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("DeleteDataSetRequest"); - debug_struct.field("name", &self.name); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::ListDataSetsRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("ListDataSetsRequest"); - debug_struct.field("parent", &self.parent); - debug_struct.field("page_size", &self.page_size); - debug_struct.field("page_token", &self.page_token); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::ListDataSetsResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("ListDataSetsResponse"); - debug_struct.field("datasets", &self.datasets); - debug_struct.field("next_page_token", &self.next_page_token); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::PinnedDimension { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("PinnedDimension"); - debug_struct.field("name", &self.name); - debug_struct.field("value", &self.value); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::ForecastParams { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("ForecastParams"); - debug_struct.field("noise_threshold", &self.noise_threshold); - debug_struct.field("seasonality_hint", &self.seasonality_hint); - debug_struct.field("horizon_duration", &self.horizon_duration); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::TimeseriesPoint { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("TimeseriesPoint"); - debug_struct.field("time", &self.time); - debug_struct.field("value", &self.value); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::Timeseries { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("Timeseries"); - debug_struct.field("point", &self.point); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::EvaluatedSlice { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("EvaluatedSlice"); - debug_struct.field("dimensions", &self.dimensions); - debug_struct.field("detection_point_actual", &self.detection_point_actual); - debug_struct.field("detection_point_forecast", &self.detection_point_forecast); - debug_struct.field("expected_deviation", &self.expected_deviation); - debug_struct.field("anomaly_score", &self.anomaly_score); - debug_struct.field("history", &self.history); - debug_struct.field("forecast", &self.forecast); - debug_struct.field("status", &self.status); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::SlicingParams { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("SlicingParams"); - debug_struct.field("dimension_names", &self.dimension_names); - debug_struct.field("pinned_dimensions", &self.pinned_dimensions); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::TimeseriesParams { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("TimeseriesParams"); - debug_struct.field("forecast_history", &self.forecast_history); - debug_struct.field("granularity", &self.granularity); - debug_struct.field("metric", &self.metric); - debug_struct.field("metric_aggregation_method", &self.metric_aggregation_method); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::QueryDataSetRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("QueryDataSetRequest"); - debug_struct.field("name", &self.name); - debug_struct.field("detection_time", &self.detection_time); - debug_struct.field("num_returned_slices", &self.num_returned_slices); - debug_struct.field("slicing_params", &self.slicing_params); - debug_struct.field("timeseries_params", &self.timeseries_params); - debug_struct.field("forecast_params", &self.forecast_params); - debug_struct.field("return_timeseries", &self.return_timeseries); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::QueryDataSetResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("QueryDataSetResponse"); - debug_struct.field("name", &self.name); - debug_struct.field("slices", &self.slices); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::EvaluateSliceRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("EvaluateSliceRequest"); - debug_struct.field("dataset", &self.dataset); - debug_struct.field("pinned_dimensions", &self.pinned_dimensions); - debug_struct.field("detection_time", &self.detection_time); - debug_struct.field("timeseries_params", &self.timeseries_params); - debug_struct.field("forecast_params", &self.forecast_params); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} - -impl std::fmt::Debug for super::EvaluateTimeseriesRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_struct = f.debug_struct("EvaluateTimeseriesRequest"); - debug_struct.field("parent", &self.parent); - debug_struct.field("timeseries", &self.timeseries); - debug_struct.field("granularity", &self.granularity); - debug_struct.field("forecast_params", &self.forecast_params); - if !self._unknown_fields.is_empty() { - debug_struct.field("_unknown_fields", &self._unknown_fields); - } - debug_struct.finish() - } -} diff --git a/src/generated/cloud/timeseriesinsights/v1/src/model/deserialize.rs b/src/generated/cloud/timeseriesinsights/v1/src/model/deserialize.rs deleted file mode 100644 index 3411edacd4..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/src/model/deserialize.rs +++ /dev/null @@ -1,2543 +0,0 @@ -// Copyright 2025 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 -// -// https://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. -// -// Code generated by sidekick. DO NOT EDIT. - -#[allow(unused_imports)] -use super::*; - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::BigqueryMapping { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __timestamp_column, - __group_id_column, - __dimension_column, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for BigqueryMapping") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "timestampColumn" => Ok(__FieldTag::__timestamp_column), - "timestamp_column" => Ok(__FieldTag::__timestamp_column), - "groupIdColumn" => Ok(__FieldTag::__group_id_column), - "group_id_column" => Ok(__FieldTag::__group_id_column), - "dimensionColumn" => Ok(__FieldTag::__dimension_column), - "dimension_column" => Ok(__FieldTag::__dimension_column), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::BigqueryMapping; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct BigqueryMapping") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__timestamp_column => { - if !fields.insert(__FieldTag::__timestamp_column) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for timestamp_column", - )); - } - result.timestamp_column = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__group_id_column => { - if !fields.insert(__FieldTag::__group_id_column) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for group_id_column", - )); - } - result.group_id_column = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__dimension_column => { - if !fields.insert(__FieldTag::__dimension_column) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for dimension_column", - )); - } - result.dimension_column = map.next_value::>>()?.unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::DataSource { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __uri, - __bq_mapping, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for DataSource") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "uri" => Ok(__FieldTag::__uri), - "bqMapping" => Ok(__FieldTag::__bq_mapping), - "bq_mapping" => Ok(__FieldTag::__bq_mapping), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::DataSource; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct DataSource") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__uri => { - if !fields.insert(__FieldTag::__uri) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for uri", - )); - } - result.uri = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__bq_mapping => { - if !fields.insert(__FieldTag::__bq_mapping) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for bq_mapping", - )); - } - result.bq_mapping = map - .next_value::>( - )?; - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::DataSet { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __name, - __data_names, - __data_sources, - __state, - __status, - __ttl, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for DataSet") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "name" => Ok(__FieldTag::__name), - "dataNames" => Ok(__FieldTag::__data_names), - "data_names" => Ok(__FieldTag::__data_names), - "dataSources" => Ok(__FieldTag::__data_sources), - "data_sources" => Ok(__FieldTag::__data_sources), - "state" => Ok(__FieldTag::__state), - "status" => Ok(__FieldTag::__status), - "ttl" => Ok(__FieldTag::__ttl), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::DataSet; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct DataSet") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__name => { - if !fields.insert(__FieldTag::__name) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for name", - )); - } - result.name = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__data_names => { - if !fields.insert(__FieldTag::__data_names) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for data_names", - )); - } - result.data_names = map.next_value::>>()?.unwrap_or_default(); - } - __FieldTag::__data_sources => { - if !fields.insert(__FieldTag::__data_sources) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for data_sources", - )); - } - result.data_sources = map.next_value::>>()?.unwrap_or_default(); - } - __FieldTag::__state => { - if !fields.insert(__FieldTag::__state) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for state", - )); - } - result.state = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__status => { - if !fields.insert(__FieldTag::__status) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for status", - )); - } - result.status = map - .next_value::>( - )?; - } - __FieldTag::__ttl => { - if !fields.insert(__FieldTag::__ttl) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for ttl", - )); - } - result.ttl = map.next_value::>()?; - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::EventDimension { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __name, - __string_val, - __long_val, - __bool_val, - __double_val, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for EventDimension") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "name" => Ok(__FieldTag::__name), - "stringVal" => Ok(__FieldTag::__string_val), - "string_val" => Ok(__FieldTag::__string_val), - "longVal" => Ok(__FieldTag::__long_val), - "long_val" => Ok(__FieldTag::__long_val), - "boolVal" => Ok(__FieldTag::__bool_val), - "bool_val" => Ok(__FieldTag::__bool_val), - "doubleVal" => Ok(__FieldTag::__double_val), - "double_val" => Ok(__FieldTag::__double_val), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::EventDimension; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct EventDimension") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__name => { - if !fields.insert(__FieldTag::__name) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for name", - )); - } - result.name = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__string_val => { - if !fields.insert(__FieldTag::__string_val) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for string_val", - )); - } - if result.value.is_some() { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for `value`, a oneof with full ID .google.cloud.timeseriesinsights.v1.EventDimension.string_val, latest field was stringVal", - )); - } - result.value = std::option::Option::Some( - crate::model::event_dimension::Value::StringVal( - map.next_value::>()? - .unwrap_or_default(), - ), - ); - } - __FieldTag::__long_val => { - if !fields.insert(__FieldTag::__long_val) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for long_val", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - if result.value.is_some() { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for `value`, a oneof with full ID .google.cloud.timeseriesinsights.v1.EventDimension.long_val, latest field was longVal", - )); - } - result.value = std::option::Option::Some( - crate::model::event_dimension::Value::LongVal( - map.next_value::<__With>()?.0.unwrap_or_default(), - ), - ); - } - __FieldTag::__bool_val => { - if !fields.insert(__FieldTag::__bool_val) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for bool_val", - )); - } - if result.value.is_some() { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for `value`, a oneof with full ID .google.cloud.timeseriesinsights.v1.EventDimension.bool_val, latest field was boolVal", - )); - } - result.value = std::option::Option::Some( - crate::model::event_dimension::Value::BoolVal( - map.next_value::>()? - .unwrap_or_default(), - ), - ); - } - __FieldTag::__double_val => { - if !fields.insert(__FieldTag::__double_val) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for double_val", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - if result.value.is_some() { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for `value`, a oneof with full ID .google.cloud.timeseriesinsights.v1.EventDimension.double_val, latest field was doubleVal", - )); - } - result.value = std::option::Option::Some( - crate::model::event_dimension::Value::DoubleVal( - map.next_value::<__With>()?.0.unwrap_or_default(), - ), - ); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::Event { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __dimensions, - __group_id, - __event_time, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for Event") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "dimensions" => Ok(__FieldTag::__dimensions), - "groupId" => Ok(__FieldTag::__group_id), - "group_id" => Ok(__FieldTag::__group_id), - "eventTime" => Ok(__FieldTag::__event_time), - "event_time" => Ok(__FieldTag::__event_time), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::Event; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct Event") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__dimensions => { - if !fields.insert(__FieldTag::__dimensions) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for dimensions", - )); - } - result.dimensions = - map.next_value::, - >>()? - .unwrap_or_default(); - } - __FieldTag::__group_id => { - if !fields.insert(__FieldTag::__group_id) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for group_id", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - result.group_id = map.next_value::<__With>()?.0.unwrap_or_default(); - } - __FieldTag::__event_time => { - if !fields.insert(__FieldTag::__event_time) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for event_time", - )); - } - result.event_time = - map.next_value::>()?; - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::AppendEventsRequest { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __events, - __dataset, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for AppendEventsRequest") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "events" => Ok(__FieldTag::__events), - "dataset" => Ok(__FieldTag::__dataset), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::AppendEventsRequest; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct AppendEventsRequest") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__events => { - if !fields.insert(__FieldTag::__events) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for events", - )); - } - result.events = map.next_value::>>()?.unwrap_or_default(); - } - __FieldTag::__dataset => { - if !fields.insert(__FieldTag::__dataset) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for dataset", - )); - } - result.dataset = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::AppendEventsResponse { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __dropped_events, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for AppendEventsResponse") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "droppedEvents" => Ok(__FieldTag::__dropped_events), - "dropped_events" => Ok(__FieldTag::__dropped_events), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::AppendEventsResponse; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct AppendEventsResponse") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__dropped_events => { - if !fields.insert(__FieldTag::__dropped_events) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for dropped_events", - )); - } - result.dropped_events = map.next_value::>>()?.unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::CreateDataSetRequest { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __parent, - __dataset, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for CreateDataSetRequest") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "parent" => Ok(__FieldTag::__parent), - "dataset" => Ok(__FieldTag::__dataset), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::CreateDataSetRequest; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct CreateDataSetRequest") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__parent => { - if !fields.insert(__FieldTag::__parent) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for parent", - )); - } - result.parent = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__dataset => { - if !fields.insert(__FieldTag::__dataset) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for dataset", - )); - } - result.dataset = - map.next_value::>()?; - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::DeleteDataSetRequest { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __name, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for DeleteDataSetRequest") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "name" => Ok(__FieldTag::__name), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::DeleteDataSetRequest; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct DeleteDataSetRequest") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__name => { - if !fields.insert(__FieldTag::__name) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for name", - )); - } - result.name = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::ListDataSetsRequest { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __parent, - __page_size, - __page_token, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for ListDataSetsRequest") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "parent" => Ok(__FieldTag::__parent), - "pageSize" => Ok(__FieldTag::__page_size), - "page_size" => Ok(__FieldTag::__page_size), - "pageToken" => Ok(__FieldTag::__page_token), - "page_token" => Ok(__FieldTag::__page_token), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::ListDataSetsRequest; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct ListDataSetsRequest") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__parent => { - if !fields.insert(__FieldTag::__parent) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for parent", - )); - } - result.parent = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__page_size => { - if !fields.insert(__FieldTag::__page_size) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for page_size", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - result.page_size = map.next_value::<__With>()?.0.unwrap_or_default(); - } - __FieldTag::__page_token => { - if !fields.insert(__FieldTag::__page_token) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for page_token", - )); - } - result.page_token = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::ListDataSetsResponse { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __datasets, - __next_page_token, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for ListDataSetsResponse") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "datasets" => Ok(__FieldTag::__datasets), - "nextPageToken" => Ok(__FieldTag::__next_page_token), - "next_page_token" => Ok(__FieldTag::__next_page_token), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::ListDataSetsResponse; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct ListDataSetsResponse") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__datasets => { - if !fields.insert(__FieldTag::__datasets) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for datasets", - )); - } - result.datasets = map.next_value::>>()?.unwrap_or_default(); - } - __FieldTag::__next_page_token => { - if !fields.insert(__FieldTag::__next_page_token) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for next_page_token", - )); - } - result.next_page_token = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::PinnedDimension { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __name, - __string_val, - __bool_val, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for PinnedDimension") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "name" => Ok(__FieldTag::__name), - "stringVal" => Ok(__FieldTag::__string_val), - "string_val" => Ok(__FieldTag::__string_val), - "boolVal" => Ok(__FieldTag::__bool_val), - "bool_val" => Ok(__FieldTag::__bool_val), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::PinnedDimension; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct PinnedDimension") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__name => { - if !fields.insert(__FieldTag::__name) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for name", - )); - } - result.name = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__string_val => { - if !fields.insert(__FieldTag::__string_val) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for string_val", - )); - } - if result.value.is_some() { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for `value`, a oneof with full ID .google.cloud.timeseriesinsights.v1.PinnedDimension.string_val, latest field was stringVal", - )); - } - result.value = std::option::Option::Some( - crate::model::pinned_dimension::Value::StringVal( - map.next_value::>()? - .unwrap_or_default(), - ), - ); - } - __FieldTag::__bool_val => { - if !fields.insert(__FieldTag::__bool_val) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for bool_val", - )); - } - if result.value.is_some() { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for `value`, a oneof with full ID .google.cloud.timeseriesinsights.v1.PinnedDimension.bool_val, latest field was boolVal", - )); - } - result.value = std::option::Option::Some( - crate::model::pinned_dimension::Value::BoolVal( - map.next_value::>()? - .unwrap_or_default(), - ), - ); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::ForecastParams { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __noise_threshold, - __seasonality_hint, - __horizon_duration, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for ForecastParams") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "noiseThreshold" => Ok(__FieldTag::__noise_threshold), - "noise_threshold" => Ok(__FieldTag::__noise_threshold), - "seasonalityHint" => Ok(__FieldTag::__seasonality_hint), - "seasonality_hint" => Ok(__FieldTag::__seasonality_hint), - "horizonDuration" => Ok(__FieldTag::__horizon_duration), - "horizon_duration" => Ok(__FieldTag::__horizon_duration), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::ForecastParams; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct ForecastParams") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__noise_threshold => { - if !fields.insert(__FieldTag::__noise_threshold) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for noise_threshold", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - result.noise_threshold = map.next_value::<__With>()?.0; - } - __FieldTag::__seasonality_hint => { - if !fields.insert(__FieldTag::__seasonality_hint) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for seasonality_hint", - )); - } - result.seasonality_hint = map.next_value::>()?.unwrap_or_default(); - } - __FieldTag::__horizon_duration => { - if !fields.insert(__FieldTag::__horizon_duration) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for horizon_duration", - )); - } - result.horizon_duration = - map.next_value::>()?; - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::TimeseriesPoint { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __time, - __value, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for TimeseriesPoint") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "time" => Ok(__FieldTag::__time), - "value" => Ok(__FieldTag::__value), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::TimeseriesPoint; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct TimeseriesPoint") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__time => { - if !fields.insert(__FieldTag::__time) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for time", - )); - } - result.time = - map.next_value::>()?; - } - __FieldTag::__value => { - if !fields.insert(__FieldTag::__value) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for value", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - result.value = map.next_value::<__With>()?.0; - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::Timeseries { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __point, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for Timeseries") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "point" => Ok(__FieldTag::__point), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::Timeseries; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct Timeseries") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__point => { - if !fields.insert(__FieldTag::__point) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for point", - )); - } - result.point = - map.next_value::, - >>()? - .unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::EvaluatedSlice { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __dimensions, - __detection_point_actual, - __detection_point_forecast, - __expected_deviation, - __anomaly_score, - __history, - __forecast, - __status, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for EvaluatedSlice") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "dimensions" => Ok(__FieldTag::__dimensions), - "detectionPointActual" => Ok(__FieldTag::__detection_point_actual), - "detection_point_actual" => Ok(__FieldTag::__detection_point_actual), - "detectionPointForecast" => Ok(__FieldTag::__detection_point_forecast), - "detection_point_forecast" => { - Ok(__FieldTag::__detection_point_forecast) - } - "expectedDeviation" => Ok(__FieldTag::__expected_deviation), - "expected_deviation" => Ok(__FieldTag::__expected_deviation), - "anomalyScore" => Ok(__FieldTag::__anomaly_score), - "anomaly_score" => Ok(__FieldTag::__anomaly_score), - "history" => Ok(__FieldTag::__history), - "forecast" => Ok(__FieldTag::__forecast), - "status" => Ok(__FieldTag::__status), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::EvaluatedSlice; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct EvaluatedSlice") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__dimensions => { - if !fields.insert(__FieldTag::__dimensions) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for dimensions", - )); - } - result.dimensions = - map.next_value::, - >>()? - .unwrap_or_default(); - } - __FieldTag::__detection_point_actual => { - if !fields.insert(__FieldTag::__detection_point_actual) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for detection_point_actual", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - result.detection_point_actual = map.next_value::<__With>()?.0; - } - __FieldTag::__detection_point_forecast => { - if !fields.insert(__FieldTag::__detection_point_forecast) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for detection_point_forecast", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - result.detection_point_forecast = map.next_value::<__With>()?.0; - } - __FieldTag::__expected_deviation => { - if !fields.insert(__FieldTag::__expected_deviation) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for expected_deviation", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - result.expected_deviation = map.next_value::<__With>()?.0; - } - __FieldTag::__anomaly_score => { - if !fields.insert(__FieldTag::__anomaly_score) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for anomaly_score", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - result.anomaly_score = map.next_value::<__With>()?.0; - } - __FieldTag::__history => { - if !fields.insert(__FieldTag::__history) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for history", - )); - } - result.history = - map.next_value::>()?; - } - __FieldTag::__forecast => { - if !fields.insert(__FieldTag::__forecast) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for forecast", - )); - } - result.forecast = - map.next_value::>()?; - } - __FieldTag::__status => { - if !fields.insert(__FieldTag::__status) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for status", - )); - } - result.status = map - .next_value::>( - )?; - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::SlicingParams { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __dimension_names, - __pinned_dimensions, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for SlicingParams") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "dimensionNames" => Ok(__FieldTag::__dimension_names), - "dimension_names" => Ok(__FieldTag::__dimension_names), - "pinnedDimensions" => Ok(__FieldTag::__pinned_dimensions), - "pinned_dimensions" => Ok(__FieldTag::__pinned_dimensions), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::SlicingParams; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct SlicingParams") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__dimension_names => { - if !fields.insert(__FieldTag::__dimension_names) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for dimension_names", - )); - } - result.dimension_names = map.next_value::>>()?.unwrap_or_default(); - } - __FieldTag::__pinned_dimensions => { - if !fields.insert(__FieldTag::__pinned_dimensions) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for pinned_dimensions", - )); - } - result.pinned_dimensions = - map.next_value::, - >>()? - .unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::TimeseriesParams { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __forecast_history, - __granularity, - __metric, - __metric_aggregation_method, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for TimeseriesParams") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "forecastHistory" => Ok(__FieldTag::__forecast_history), - "forecast_history" => Ok(__FieldTag::__forecast_history), - "granularity" => Ok(__FieldTag::__granularity), - "metric" => Ok(__FieldTag::__metric), - "metricAggregationMethod" => { - Ok(__FieldTag::__metric_aggregation_method) - } - "metric_aggregation_method" => { - Ok(__FieldTag::__metric_aggregation_method) - } - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::TimeseriesParams; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct TimeseriesParams") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__forecast_history => { - if !fields.insert(__FieldTag::__forecast_history) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for forecast_history", - )); - } - result.forecast_history = - map.next_value::>()?; - } - __FieldTag::__granularity => { - if !fields.insert(__FieldTag::__granularity) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for granularity", - )); - } - result.granularity = - map.next_value::>()?; - } - __FieldTag::__metric => { - if !fields.insert(__FieldTag::__metric) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for metric", - )); - } - result.metric = - map.next_value::>()?; - } - __FieldTag::__metric_aggregation_method => { - if !fields.insert(__FieldTag::__metric_aggregation_method) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for metric_aggregation_method", - )); - } - result.metric_aggregation_method = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::QueryDataSetRequest { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __name, - __detection_time, - __num_returned_slices, - __slicing_params, - __timeseries_params, - __forecast_params, - __return_timeseries, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for QueryDataSetRequest") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "name" => Ok(__FieldTag::__name), - "detectionTime" => Ok(__FieldTag::__detection_time), - "detection_time" => Ok(__FieldTag::__detection_time), - "numReturnedSlices" => Ok(__FieldTag::__num_returned_slices), - "num_returned_slices" => Ok(__FieldTag::__num_returned_slices), - "slicingParams" => Ok(__FieldTag::__slicing_params), - "slicing_params" => Ok(__FieldTag::__slicing_params), - "timeseriesParams" => Ok(__FieldTag::__timeseries_params), - "timeseries_params" => Ok(__FieldTag::__timeseries_params), - "forecastParams" => Ok(__FieldTag::__forecast_params), - "forecast_params" => Ok(__FieldTag::__forecast_params), - "returnTimeseries" => Ok(__FieldTag::__return_timeseries), - "return_timeseries" => Ok(__FieldTag::__return_timeseries), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::QueryDataSetRequest; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct QueryDataSetRequest") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__name => { - if !fields.insert(__FieldTag::__name) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for name", - )); - } - result.name = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__detection_time => { - if !fields.insert(__FieldTag::__detection_time) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for detection_time", - )); - } - result.detection_time = - map.next_value::>()?; - } - __FieldTag::__num_returned_slices => { - if !fields.insert(__FieldTag::__num_returned_slices) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for num_returned_slices", - )); - } - struct __With(std::option::Option); - impl<'de> serde::de::Deserialize<'de> for __With { - fn deserialize( - deserializer: D, - ) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - serde_with::As::< std::option::Option >::deserialize(deserializer).map(__With) - } - } - result.num_returned_slices = map.next_value::<__With>()?.0; - } - __FieldTag::__slicing_params => { - if !fields.insert(__FieldTag::__slicing_params) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for slicing_params", - )); - } - result.slicing_params = map - .next_value::>()?; - } - __FieldTag::__timeseries_params => { - if !fields.insert(__FieldTag::__timeseries_params) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for timeseries_params", - )); - } - result.timeseries_params = map - .next_value::>( - )?; - } - __FieldTag::__forecast_params => { - if !fields.insert(__FieldTag::__forecast_params) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for forecast_params", - )); - } - result.forecast_params = map - .next_value::>( - )?; - } - __FieldTag::__return_timeseries => { - if !fields.insert(__FieldTag::__return_timeseries) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for return_timeseries", - )); - } - result.return_timeseries = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::QueryDataSetResponse { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __name, - __slices, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for QueryDataSetResponse") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "name" => Ok(__FieldTag::__name), - "slices" => Ok(__FieldTag::__slices), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::QueryDataSetResponse; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct QueryDataSetResponse") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__name => { - if !fields.insert(__FieldTag::__name) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for name", - )); - } - result.name = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__slices => { - if !fields.insert(__FieldTag::__slices) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for slices", - )); - } - result.slices = - map.next_value::, - >>()? - .unwrap_or_default(); - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::EvaluateSliceRequest { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __dataset, - __pinned_dimensions, - __detection_time, - __timeseries_params, - __forecast_params, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for EvaluateSliceRequest") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "dataset" => Ok(__FieldTag::__dataset), - "pinnedDimensions" => Ok(__FieldTag::__pinned_dimensions), - "pinned_dimensions" => Ok(__FieldTag::__pinned_dimensions), - "detectionTime" => Ok(__FieldTag::__detection_time), - "detection_time" => Ok(__FieldTag::__detection_time), - "timeseriesParams" => Ok(__FieldTag::__timeseries_params), - "timeseries_params" => Ok(__FieldTag::__timeseries_params), - "forecastParams" => Ok(__FieldTag::__forecast_params), - "forecast_params" => Ok(__FieldTag::__forecast_params), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::EvaluateSliceRequest; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct EvaluateSliceRequest") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__dataset => { - if !fields.insert(__FieldTag::__dataset) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for dataset", - )); - } - result.dataset = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__pinned_dimensions => { - if !fields.insert(__FieldTag::__pinned_dimensions) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for pinned_dimensions", - )); - } - result.pinned_dimensions = - map.next_value::, - >>()? - .unwrap_or_default(); - } - __FieldTag::__detection_time => { - if !fields.insert(__FieldTag::__detection_time) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for detection_time", - )); - } - result.detection_time = - map.next_value::>()?; - } - __FieldTag::__timeseries_params => { - if !fields.insert(__FieldTag::__timeseries_params) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for timeseries_params", - )); - } - result.timeseries_params = map - .next_value::>( - )?; - } - __FieldTag::__forecast_params => { - if !fields.insert(__FieldTag::__forecast_params) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for forecast_params", - )); - } - result.forecast_params = map - .next_value::>( - )?; - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} - -#[doc(hidden)] -impl<'de> serde::de::Deserialize<'de> for super::EvaluateTimeseriesRequest { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[derive(PartialEq, Eq, Hash)] - enum __FieldTag { - __parent, - __timeseries, - __granularity, - __forecast_params, - Unknown(std::string::String), - } - impl<'de> serde::de::Deserialize<'de> for __FieldTag { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = __FieldTag; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a field name for EvaluateTimeseriesRequest") - } - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - use std::result::Result::Ok; - use std::string::ToString; - match value { - "parent" => Ok(__FieldTag::__parent), - "timeseries" => Ok(__FieldTag::__timeseries), - "granularity" => Ok(__FieldTag::__granularity), - "forecastParams" => Ok(__FieldTag::__forecast_params), - "forecast_params" => Ok(__FieldTag::__forecast_params), - _ => Ok(__FieldTag::Unknown(value.to_string())), - } - } - } - deserializer.deserialize_identifier(Visitor) - } - } - struct Visitor; - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = super::EvaluateTimeseriesRequest; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("struct EvaluateTimeseriesRequest") - } - fn visit_map(self, mut map: A) -> std::result::Result - where - A: serde::de::MapAccess<'de>, - { - #[allow(unused_imports)] - use serde::de::Error; - use std::option::Option::Some; - let mut fields = std::collections::HashSet::new(); - let mut result = Self::Value::new(); - while let Some(tag) = map.next_key::<__FieldTag>()? { - #[allow(clippy::match_single_binding)] - match tag { - __FieldTag::__parent => { - if !fields.insert(__FieldTag::__parent) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for parent", - )); - } - result.parent = map - .next_value::>()? - .unwrap_or_default(); - } - __FieldTag::__timeseries => { - if !fields.insert(__FieldTag::__timeseries) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for timeseries", - )); - } - result.timeseries = - map.next_value::>()?; - } - __FieldTag::__granularity => { - if !fields.insert(__FieldTag::__granularity) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for granularity", - )); - } - result.granularity = - map.next_value::>()?; - } - __FieldTag::__forecast_params => { - if !fields.insert(__FieldTag::__forecast_params) { - return std::result::Result::Err(A::Error::duplicate_field( - "multiple values for forecast_params", - )); - } - result.forecast_params = map - .next_value::>( - )?; - } - __FieldTag::Unknown(key) => { - let value = map.next_value::()?; - result._unknown_fields.insert(key, value); - } - } - } - std::result::Result::Ok(result) - } - } - deserializer.deserialize_any(Visitor) - } -} diff --git a/src/generated/cloud/timeseriesinsights/v1/src/model/serialize.rs b/src/generated/cloud/timeseriesinsights/v1/src/model/serialize.rs deleted file mode 100644 index b72d988831..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/src/model/serialize.rs +++ /dev/null @@ -1,768 +0,0 @@ -// Copyright 2025 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 -// -// https://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. -// -// Code generated by sidekick. DO NOT EDIT. - -#[allow(unused_imports)] -use super::*; - -#[doc(hidden)] -impl serde::ser::Serialize for super::BigqueryMapping { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.timestamp_column.is_empty() { - state.serialize_entry("timestampColumn", &self.timestamp_column)?; - } - if !self.group_id_column.is_empty() { - state.serialize_entry("groupIdColumn", &self.group_id_column)?; - } - if !self.dimension_column.is_empty() { - state.serialize_entry("dimensionColumn", &self.dimension_column)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::DataSource { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.uri.is_empty() { - state.serialize_entry("uri", &self.uri)?; - } - if self.bq_mapping.is_some() { - state.serialize_entry("bqMapping", &self.bq_mapping)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::DataSet { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.name.is_empty() { - state.serialize_entry("name", &self.name)?; - } - if !self.data_names.is_empty() { - state.serialize_entry("dataNames", &self.data_names)?; - } - if !self.data_sources.is_empty() { - state.serialize_entry("dataSources", &self.data_sources)?; - } - if !wkt::internal::is_default(&self.state) { - state.serialize_entry("state", &self.state)?; - } - if self.status.is_some() { - state.serialize_entry("status", &self.status)?; - } - if self.ttl.is_some() { - state.serialize_entry("ttl", &self.ttl)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::EventDimension { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.name.is_empty() { - state.serialize_entry("name", &self.name)?; - } - if let Some(value) = self.string_val() { - state.serialize_entry("stringVal", value)?; - } - if let Some(value) = self.long_val() { - struct __With<'a>(&'a i64); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::::serialize(self.0, serializer) - } - } - state.serialize_entry("longVal", &__With(value))?; - } - if let Some(value) = self.bool_val() { - state.serialize_entry("boolVal", value)?; - } - if let Some(value) = self.double_val() { - struct __With<'a>(&'a f64); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::::serialize(self.0, serializer) - } - } - state.serialize_entry("doubleVal", &__With(value))?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::Event { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.dimensions.is_empty() { - state.serialize_entry("dimensions", &self.dimensions)?; - } - if !wkt::internal::is_default(&self.group_id) { - struct __With<'a>(&'a i64); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::::serialize(self.0, serializer) - } - } - state.serialize_entry("groupId", &__With(&self.group_id))?; - } - if self.event_time.is_some() { - state.serialize_entry("eventTime", &self.event_time)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::AppendEventsRequest { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.events.is_empty() { - state.serialize_entry("events", &self.events)?; - } - if !self.dataset.is_empty() { - state.serialize_entry("dataset", &self.dataset)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::AppendEventsResponse { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.dropped_events.is_empty() { - state.serialize_entry("droppedEvents", &self.dropped_events)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::CreateDataSetRequest { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.parent.is_empty() { - state.serialize_entry("parent", &self.parent)?; - } - if self.dataset.is_some() { - state.serialize_entry("dataset", &self.dataset)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::DeleteDataSetRequest { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.name.is_empty() { - state.serialize_entry("name", &self.name)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::ListDataSetsRequest { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.parent.is_empty() { - state.serialize_entry("parent", &self.parent)?; - } - if !wkt::internal::is_default(&self.page_size) { - struct __With<'a>(&'a i32); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::::serialize(self.0, serializer) - } - } - state.serialize_entry("pageSize", &__With(&self.page_size))?; - } - if !self.page_token.is_empty() { - state.serialize_entry("pageToken", &self.page_token)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::ListDataSetsResponse { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.datasets.is_empty() { - state.serialize_entry("datasets", &self.datasets)?; - } - if !self.next_page_token.is_empty() { - state.serialize_entry("nextPageToken", &self.next_page_token)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::PinnedDimension { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.name.is_empty() { - state.serialize_entry("name", &self.name)?; - } - if let Some(value) = self.string_val() { - state.serialize_entry("stringVal", value)?; - } - if let Some(value) = self.bool_val() { - state.serialize_entry("boolVal", value)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::ForecastParams { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if self.noise_threshold.is_some() { - struct __With<'a>(&'a std::option::Option); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::>::serialize( - self.0, serializer, - ) - } - } - state.serialize_entry("noiseThreshold", &__With(&self.noise_threshold))?; - } - if !wkt::internal::is_default(&self.seasonality_hint) { - state.serialize_entry("seasonalityHint", &self.seasonality_hint)?; - } - if self.horizon_duration.is_some() { - state.serialize_entry("horizonDuration", &self.horizon_duration)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::TimeseriesPoint { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if self.time.is_some() { - state.serialize_entry("time", &self.time)?; - } - if self.value.is_some() { - struct __With<'a>(&'a std::option::Option); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::>::serialize( - self.0, serializer, - ) - } - } - state.serialize_entry("value", &__With(&self.value))?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::Timeseries { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.point.is_empty() { - state.serialize_entry("point", &self.point)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::EvaluatedSlice { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.dimensions.is_empty() { - state.serialize_entry("dimensions", &self.dimensions)?; - } - if self.detection_point_actual.is_some() { - struct __With<'a>(&'a std::option::Option); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::>::serialize( - self.0, serializer, - ) - } - } - state.serialize_entry( - "detectionPointActual", - &__With(&self.detection_point_actual), - )?; - } - if self.detection_point_forecast.is_some() { - struct __With<'a>(&'a std::option::Option); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::>::serialize( - self.0, serializer, - ) - } - } - state.serialize_entry( - "detectionPointForecast", - &__With(&self.detection_point_forecast), - )?; - } - if self.expected_deviation.is_some() { - struct __With<'a>(&'a std::option::Option); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::>::serialize( - self.0, serializer, - ) - } - } - state.serialize_entry("expectedDeviation", &__With(&self.expected_deviation))?; - } - if self.anomaly_score.is_some() { - struct __With<'a>(&'a std::option::Option); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::>::serialize( - self.0, serializer, - ) - } - } - state.serialize_entry("anomalyScore", &__With(&self.anomaly_score))?; - } - if self.history.is_some() { - state.serialize_entry("history", &self.history)?; - } - if self.forecast.is_some() { - state.serialize_entry("forecast", &self.forecast)?; - } - if self.status.is_some() { - state.serialize_entry("status", &self.status)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::SlicingParams { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.dimension_names.is_empty() { - state.serialize_entry("dimensionNames", &self.dimension_names)?; - } - if !self.pinned_dimensions.is_empty() { - state.serialize_entry("pinnedDimensions", &self.pinned_dimensions)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::TimeseriesParams { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if self.forecast_history.is_some() { - state.serialize_entry("forecastHistory", &self.forecast_history)?; - } - if self.granularity.is_some() { - state.serialize_entry("granularity", &self.granularity)?; - } - if self.metric.is_some() { - state.serialize_entry("metric", &self.metric)?; - } - if !wkt::internal::is_default(&self.metric_aggregation_method) { - state.serialize_entry("metricAggregationMethod", &self.metric_aggregation_method)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::QueryDataSetRequest { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.name.is_empty() { - state.serialize_entry("name", &self.name)?; - } - if self.detection_time.is_some() { - state.serialize_entry("detectionTime", &self.detection_time)?; - } - if self.num_returned_slices.is_some() { - struct __With<'a>(&'a std::option::Option); - impl<'a> serde::ser::Serialize for __With<'a> { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - serde_with::As::>::serialize( - self.0, serializer, - ) - } - } - state.serialize_entry("numReturnedSlices", &__With(&self.num_returned_slices))?; - } - if self.slicing_params.is_some() { - state.serialize_entry("slicingParams", &self.slicing_params)?; - } - if self.timeseries_params.is_some() { - state.serialize_entry("timeseriesParams", &self.timeseries_params)?; - } - if self.forecast_params.is_some() { - state.serialize_entry("forecastParams", &self.forecast_params)?; - } - if !wkt::internal::is_default(&self.return_timeseries) { - state.serialize_entry("returnTimeseries", &self.return_timeseries)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::QueryDataSetResponse { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.name.is_empty() { - state.serialize_entry("name", &self.name)?; - } - if !self.slices.is_empty() { - state.serialize_entry("slices", &self.slices)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::EvaluateSliceRequest { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.dataset.is_empty() { - state.serialize_entry("dataset", &self.dataset)?; - } - if !self.pinned_dimensions.is_empty() { - state.serialize_entry("pinnedDimensions", &self.pinned_dimensions)?; - } - if self.detection_time.is_some() { - state.serialize_entry("detectionTime", &self.detection_time)?; - } - if self.timeseries_params.is_some() { - state.serialize_entry("timeseriesParams", &self.timeseries_params)?; - } - if self.forecast_params.is_some() { - state.serialize_entry("forecastParams", &self.forecast_params)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} - -#[doc(hidden)] -impl serde::ser::Serialize for super::EvaluateTimeseriesRequest { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::ser::Serializer, - { - use serde::ser::SerializeMap; - #[allow(unused_imports)] - use std::option::Option::Some; - let mut state = serializer.serialize_map(std::option::Option::None)?; - if !self.parent.is_empty() { - state.serialize_entry("parent", &self.parent)?; - } - if self.timeseries.is_some() { - state.serialize_entry("timeseries", &self.timeseries)?; - } - if self.granularity.is_some() { - state.serialize_entry("granularity", &self.granularity)?; - } - if self.forecast_params.is_some() { - state.serialize_entry("forecastParams", &self.forecast_params)?; - } - if !self._unknown_fields.is_empty() { - for (key, value) in self._unknown_fields.iter() { - state.serialize_entry(key, &value)?; - } - } - state.end() - } -} diff --git a/src/generated/cloud/timeseriesinsights/v1/src/stub.rs b/src/generated/cloud/timeseriesinsights/v1/src/stub.rs deleted file mode 100644 index 3abf997ca0..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/src/stub.rs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2025 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 -// -// https://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. -// -// Code generated by sidekick. DO NOT EDIT. - -//! Traits to mock the clients in this library. -//! -//! Application developers may need to mock the clients in this library to test -//! how their application works with different (and sometimes hard to trigger) -//! client and service behavior. Such test can define mocks implementing the -//! trait(s) defined in this module, initialize the client with an instance of -//! this mock in their tests, and verify their application responds as expected. - -#![allow(rustdoc::broken_intra_doc_links)] - -pub(crate) mod dynamic; - -/// Defines the trait used to implement [super::client::TimeseriesInsightsController]. -/// -/// Application developers may need to implement this trait to mock -/// `client::TimeseriesInsightsController`. In other use-cases, application developers only -/// use `client::TimeseriesInsightsController` and need not be concerned with this trait or -/// its implementations. -/// -/// Services gain new RPCs routinely. Consequently, this trait gains new methods -/// too. To avoid breaking applications the trait provides a default -/// implementation of each method. Most of these implementations just return an -/// error. -pub trait TimeseriesInsightsController: std::fmt::Debug + Send + Sync { - /// Implements [super::client::TimeseriesInsightsController::list_data_sets]. - fn list_data_sets( - &self, - _req: crate::model::ListDataSetsRequest, - _options: crate::RequestOptions, - ) -> impl std::future::Future< - Output = crate::Result>, - > + Send { - gaxi::unimplemented::unimplemented_stub() - } - - /// Implements [super::client::TimeseriesInsightsController::create_data_set]. - fn create_data_set( - &self, - _req: crate::model::CreateDataSetRequest, - _options: crate::RequestOptions, - ) -> impl std::future::Future>> + Send - { - gaxi::unimplemented::unimplemented_stub() - } - - /// Implements [super::client::TimeseriesInsightsController::delete_data_set]. - fn delete_data_set( - &self, - _req: crate::model::DeleteDataSetRequest, - _options: crate::RequestOptions, - ) -> impl std::future::Future>> + Send { - gaxi::unimplemented::unimplemented_stub() - } - - /// Implements [super::client::TimeseriesInsightsController::append_events]. - fn append_events( - &self, - _req: crate::model::AppendEventsRequest, - _options: crate::RequestOptions, - ) -> impl std::future::Future< - Output = crate::Result>, - > + Send { - gaxi::unimplemented::unimplemented_stub() - } - - /// Implements [super::client::TimeseriesInsightsController::query_data_set]. - fn query_data_set( - &self, - _req: crate::model::QueryDataSetRequest, - _options: crate::RequestOptions, - ) -> impl std::future::Future< - Output = crate::Result>, - > + Send { - gaxi::unimplemented::unimplemented_stub() - } - - /// Implements [super::client::TimeseriesInsightsController::evaluate_slice]. - fn evaluate_slice( - &self, - _req: crate::model::EvaluateSliceRequest, - _options: crate::RequestOptions, - ) -> impl std::future::Future< - Output = crate::Result>, - > + Send { - gaxi::unimplemented::unimplemented_stub() - } - - /// Implements [super::client::TimeseriesInsightsController::evaluate_timeseries]. - fn evaluate_timeseries( - &self, - _req: crate::model::EvaluateTimeseriesRequest, - _options: crate::RequestOptions, - ) -> impl std::future::Future< - Output = crate::Result>, - > + Send { - gaxi::unimplemented::unimplemented_stub() - } -} diff --git a/src/generated/cloud/timeseriesinsights/v1/src/stub/dynamic.rs b/src/generated/cloud/timeseriesinsights/v1/src/stub/dynamic.rs deleted file mode 100644 index acc7ca97d2..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/src/stub/dynamic.rs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2025 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 -// -// https://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. -// -// Code generated by sidekick. DO NOT EDIT. - -/// A dyn-compatible, crate-private version of [super::TimeseriesInsightsController]. -#[async_trait::async_trait] -pub trait TimeseriesInsightsController: std::fmt::Debug + Send + Sync { - async fn list_data_sets( - &self, - req: crate::model::ListDataSetsRequest, - options: crate::RequestOptions, - ) -> crate::Result>; - - async fn create_data_set( - &self, - req: crate::model::CreateDataSetRequest, - options: crate::RequestOptions, - ) -> crate::Result>; - - async fn delete_data_set( - &self, - req: crate::model::DeleteDataSetRequest, - options: crate::RequestOptions, - ) -> crate::Result>; - - async fn append_events( - &self, - req: crate::model::AppendEventsRequest, - options: crate::RequestOptions, - ) -> crate::Result>; - - async fn query_data_set( - &self, - req: crate::model::QueryDataSetRequest, - options: crate::RequestOptions, - ) -> crate::Result>; - - async fn evaluate_slice( - &self, - req: crate::model::EvaluateSliceRequest, - options: crate::RequestOptions, - ) -> crate::Result>; - - async fn evaluate_timeseries( - &self, - req: crate::model::EvaluateTimeseriesRequest, - options: crate::RequestOptions, - ) -> crate::Result>; -} - -/// All implementations of [super::TimeseriesInsightsController] also implement [TimeseriesInsightsController]. -#[async_trait::async_trait] -impl TimeseriesInsightsController for T { - /// Forwards the call to the implementation provided by `T`. - async fn list_data_sets( - &self, - req: crate::model::ListDataSetsRequest, - options: crate::RequestOptions, - ) -> crate::Result> { - T::list_data_sets(self, req, options).await - } - - /// Forwards the call to the implementation provided by `T`. - async fn create_data_set( - &self, - req: crate::model::CreateDataSetRequest, - options: crate::RequestOptions, - ) -> crate::Result> { - T::create_data_set(self, req, options).await - } - - /// Forwards the call to the implementation provided by `T`. - async fn delete_data_set( - &self, - req: crate::model::DeleteDataSetRequest, - options: crate::RequestOptions, - ) -> crate::Result> { - T::delete_data_set(self, req, options).await - } - - /// Forwards the call to the implementation provided by `T`. - async fn append_events( - &self, - req: crate::model::AppendEventsRequest, - options: crate::RequestOptions, - ) -> crate::Result> { - T::append_events(self, req, options).await - } - - /// Forwards the call to the implementation provided by `T`. - async fn query_data_set( - &self, - req: crate::model::QueryDataSetRequest, - options: crate::RequestOptions, - ) -> crate::Result> { - T::query_data_set(self, req, options).await - } - - /// Forwards the call to the implementation provided by `T`. - async fn evaluate_slice( - &self, - req: crate::model::EvaluateSliceRequest, - options: crate::RequestOptions, - ) -> crate::Result> { - T::evaluate_slice(self, req, options).await - } - - /// Forwards the call to the implementation provided by `T`. - async fn evaluate_timeseries( - &self, - req: crate::model::EvaluateTimeseriesRequest, - options: crate::RequestOptions, - ) -> crate::Result> { - T::evaluate_timeseries(self, req, options).await - } -} diff --git a/src/generated/cloud/timeseriesinsights/v1/src/tracing.rs b/src/generated/cloud/timeseriesinsights/v1/src/tracing.rs deleted file mode 100644 index ea842d676c..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/src/tracing.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2025 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 -// -// https://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. -// -// Code generated by sidekick. DO NOT EDIT. -use crate::Result; - -/// Implements a [TimeseriesInsightsController](super::stub::TimeseriesInsightsController) decorator for logging and tracing. -#[derive(Clone, Debug)] -pub struct TimeseriesInsightsController -where - T: super::stub::TimeseriesInsightsController + std::fmt::Debug + Send + Sync, -{ - inner: T, - duration: gaxi::observability::DurationMetric, -} - -impl TimeseriesInsightsController -where - T: super::stub::TimeseriesInsightsController + std::fmt::Debug + Send + Sync, -{ - pub fn new(inner: T) -> Self { - Self { - inner, - duration: gaxi::observability::DurationMetric::new(&info::INSTRUMENTATION_CLIENT_INFO), - } - } -} - -impl super::stub::TimeseriesInsightsController for TimeseriesInsightsController -where - T: super::stub::TimeseriesInsightsController + std::fmt::Debug + Send + Sync, -{ - #[tracing::instrument(level = tracing::Level::DEBUG, ret)] - async fn list_data_sets( - &self, - req: crate::model::ListDataSetsRequest, - options: crate::RequestOptions, - ) -> Result> { - let (_span, pending) = gaxi::client_request_signals!( - metric: self.duration.clone(), - info: *info::INSTRUMENTATION_CLIENT_INFO, - method: "client::TimeseriesInsightsController::list_data_sets", - self.inner.list_data_sets(req, options)); - pending.await - } - - #[tracing::instrument(level = tracing::Level::DEBUG, ret)] - async fn create_data_set( - &self, - req: crate::model::CreateDataSetRequest, - options: crate::RequestOptions, - ) -> Result> { - let (_span, pending) = gaxi::client_request_signals!( - metric: self.duration.clone(), - info: *info::INSTRUMENTATION_CLIENT_INFO, - method: "client::TimeseriesInsightsController::create_data_set", - self.inner.create_data_set(req, options)); - pending.await - } - - #[tracing::instrument(level = tracing::Level::DEBUG, ret)] - async fn delete_data_set( - &self, - req: crate::model::DeleteDataSetRequest, - options: crate::RequestOptions, - ) -> Result> { - let (_span, pending) = gaxi::client_request_signals!( - metric: self.duration.clone(), - info: *info::INSTRUMENTATION_CLIENT_INFO, - method: "client::TimeseriesInsightsController::delete_data_set", - self.inner.delete_data_set(req, options)); - pending.await - } - - #[tracing::instrument(level = tracing::Level::DEBUG, ret)] - async fn append_events( - &self, - req: crate::model::AppendEventsRequest, - options: crate::RequestOptions, - ) -> Result> { - let (_span, pending) = gaxi::client_request_signals!( - metric: self.duration.clone(), - info: *info::INSTRUMENTATION_CLIENT_INFO, - method: "client::TimeseriesInsightsController::append_events", - self.inner.append_events(req, options)); - pending.await - } - - #[tracing::instrument(level = tracing::Level::DEBUG, ret)] - async fn query_data_set( - &self, - req: crate::model::QueryDataSetRequest, - options: crate::RequestOptions, - ) -> Result> { - let (_span, pending) = gaxi::client_request_signals!( - metric: self.duration.clone(), - info: *info::INSTRUMENTATION_CLIENT_INFO, - method: "client::TimeseriesInsightsController::query_data_set", - self.inner.query_data_set(req, options)); - pending.await - } - - #[tracing::instrument(level = tracing::Level::DEBUG, ret)] - async fn evaluate_slice( - &self, - req: crate::model::EvaluateSliceRequest, - options: crate::RequestOptions, - ) -> Result> { - let (_span, pending) = gaxi::client_request_signals!( - metric: self.duration.clone(), - info: *info::INSTRUMENTATION_CLIENT_INFO, - method: "client::TimeseriesInsightsController::evaluate_slice", - self.inner.evaluate_slice(req, options)); - pending.await - } - - #[tracing::instrument(level = tracing::Level::DEBUG, ret)] - async fn evaluate_timeseries( - &self, - req: crate::model::EvaluateTimeseriesRequest, - options: crate::RequestOptions, - ) -> Result> { - let (_span, pending) = gaxi::client_request_signals!( - metric: self.duration.clone(), - info: *info::INSTRUMENTATION_CLIENT_INFO, - method: "client::TimeseriesInsightsController::evaluate_timeseries", - self.inner.evaluate_timeseries(req, options)); - pending.await - } -} - -pub(crate) mod info { - const NAME: &str = env!("CARGO_PKG_NAME"); - const VERSION: &str = env!("CARGO_PKG_VERSION"); - pub(crate) static INSTRUMENTATION_CLIENT_INFO: std::sync::LazyLock< - gaxi::options::InstrumentationClientInfo, - > = std::sync::LazyLock::new(|| { - let mut info = gaxi::options::InstrumentationClientInfo::default(); - info.service_name = "timeseriesinsights"; - info.client_version = VERSION; - info.client_artifact = NAME; - info.default_host = "timeseriesinsights"; - info - }); -} diff --git a/src/generated/cloud/timeseriesinsights/v1/src/transport.rs b/src/generated/cloud/timeseriesinsights/v1/src/transport.rs deleted file mode 100644 index 1e6937d0e3..0000000000 --- a/src/generated/cloud/timeseriesinsights/v1/src/transport.rs +++ /dev/null @@ -1,763 +0,0 @@ -// Copyright 2025 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 -// -// https://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. -// -// Code generated by sidekick. DO NOT EDIT. - -#[allow(unused_imports)] -use crate::Error; -use crate::Result; - -/// Implements [TimeseriesInsightsController](super::stub::TimeseriesInsightsController) using a [gaxi::http::ReqwestClient]. -#[derive(Clone)] -pub struct TimeseriesInsightsController { - inner: gaxi::http::ReqwestClient, -} - -impl std::fmt::Debug for TimeseriesInsightsController { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("TimeseriesInsightsController") - .field("inner", &self.inner) - .finish() - } -} - -impl TimeseriesInsightsController { - pub async fn new(config: gaxi::options::ClientConfig) -> crate::ClientBuilderResult { - let tracing_is_enabled = gaxi::options::tracing_enabled(&config); - let inner = gaxi::http::ReqwestClient::new(config, crate::DEFAULT_HOST).await?; - let inner = if tracing_is_enabled { - inner.with_instrumentation(&super::tracing::info::INSTRUMENTATION_CLIENT_INFO) - } else { - inner - }; - Ok(Self { inner }) - } -} - -impl super::stub::TimeseriesInsightsController for TimeseriesInsightsController { - async fn list_data_sets( - &self, - req: crate::model::ListDataSetsRequest, - options: crate::RequestOptions, - ) -> Result> { - use gaxi::http::reqwest::{HeaderValue, Method}; - use gaxi::path_parameter::PathMismatchBuilder; - use gaxi::path_parameter::try_match; - use gaxi::routing_parameter::Segment; - use google_cloud_gax::error::binding::BindingError; - let (builder, method, _path_template, _resource_name) = None - .or_else(|| { - let var_parent = try_match( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}/datasets", var_parent,); - let path_template = "/v1/{parent}/datasets"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_parent,); - let builder = self.inner.builder(Method::GET, path); - let builder = builder.query(&[("pageSize", &req.page_size)]); - let builder = builder.query(&[("pageToken", &req.page_token)]); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::GET, path_template, resource_name))) - }) - .or_else(|| { - let var_parent = try_match( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[Segment::Literal("projects/"), Segment::SingleWildcard], - )?; - let path = format!("/v1/{}/datasets", var_parent,); - let path_template = "/v1/{parent}/datasets"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_parent,); - let builder = self.inner.builder(Method::GET, path); - let builder = builder.query(&[("pageSize", &req.page_size)]); - let builder = builder.query(&[("pageToken", &req.page_token)]); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::GET, path_template, resource_name))) - }) - .ok_or_else(|| { - let mut paths = Vec::new(); - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - ], - "parent", - "projects/*/locations/*", - ); - paths.push(builder.build()); - } - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[Segment::Literal("projects/"), Segment::SingleWildcard], - "parent", - "projects/*", - ); - paths.push(builder.build()); - } - google_cloud_gax::error::Error::binding(BindingError { paths }) - })??; - if let Some(recorder) = gaxi::observability::RequestRecorder::current() { - recorder.on_client_request( - gaxi::observability::ClientRequestAttributes::default() - .set_rpc_method("google.cloud.timeseriesinsights.v1.TimeseriesInsightsController/ListDataSets") - .set_url_template(_path_template) - .set_resource_name(_resource_name) - ); - } - let options = google_cloud_gax::options::internal::set_default_idempotency( - options, - gaxi::http::default_idempotency(&method), - ); - let builder = builder.query(&[("$alt", "json;enum-encoding=int")]).header( - "x-goog-api-client", - HeaderValue::from_static(&crate::info::X_GOOG_API_CLIENT_HEADER), - ); - let body = gaxi::http::handle_empty(None::, &method); - self.inner.execute(builder, body, options).await - } - - async fn create_data_set( - &self, - req: crate::model::CreateDataSetRequest, - options: crate::RequestOptions, - ) -> Result> { - use gaxi::http::reqwest::{HeaderValue, Method}; - use gaxi::path_parameter::PathMismatchBuilder; - use gaxi::path_parameter::try_match; - use gaxi::routing_parameter::Segment; - use google_cloud_gax::error::binding::BindingError; - let (builder, method, _path_template, _resource_name) = None - .or_else(|| { - let var_parent = try_match( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}/datasets", var_parent,); - let path_template = "/v1/{parent}/datasets"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_parent,); - let builder = self.inner.builder(Method::POST, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::POST, path_template, resource_name))) - }) - .or_else(|| { - let var_parent = try_match( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[Segment::Literal("projects/"), Segment::SingleWildcard], - )?; - let path = format!("/v1/{}/datasets", var_parent,); - let path_template = "/v1/{parent}/datasets"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_parent,); - let builder = self.inner.builder(Method::POST, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::POST, path_template, resource_name))) - }) - .ok_or_else(|| { - let mut paths = Vec::new(); - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - ], - "parent", - "projects/*/locations/*", - ); - paths.push(builder.build()); - } - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[Segment::Literal("projects/"), Segment::SingleWildcard], - "parent", - "projects/*", - ); - paths.push(builder.build()); - } - google_cloud_gax::error::Error::binding(BindingError { paths }) - })??; - if let Some(recorder) = gaxi::observability::RequestRecorder::current() { - recorder.on_client_request( - gaxi::observability::ClientRequestAttributes::default() - .set_rpc_method("google.cloud.timeseriesinsights.v1.TimeseriesInsightsController/CreateDataSet") - .set_url_template(_path_template) - .set_resource_name(_resource_name) - ); - } - let options = google_cloud_gax::options::internal::set_default_idempotency( - options, - gaxi::http::default_idempotency(&method), - ); - let builder = builder.query(&[("$alt", "json;enum-encoding=int")]).header( - "x-goog-api-client", - HeaderValue::from_static(&crate::info::X_GOOG_API_CLIENT_HEADER), - ); - let body = gaxi::http::handle_empty(req.dataset, &method); - self.inner.execute(builder, body, options).await - } - - async fn delete_data_set( - &self, - req: crate::model::DeleteDataSetRequest, - options: crate::RequestOptions, - ) -> Result> { - use gaxi::http::reqwest::{HeaderValue, Method}; - use gaxi::path_parameter::PathMismatchBuilder; - use gaxi::path_parameter::try_match; - use gaxi::routing_parameter::Segment; - use google_cloud_gax::error::binding::BindingError; - let (builder, method, _path_template, _resource_name) = None - .or_else(|| { - let var_name = try_match( - Some(&req).map(|m| &m.name).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}", var_name,); - let path_template = "/v1/{name}"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_name,); - let builder = self.inner.builder(Method::DELETE, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::DELETE, path_template, resource_name))) - }) - .or_else(|| { - let var_name = try_match( - Some(&req).map(|m| &m.name).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}", var_name,); - let path_template = "/v1/{name}"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_name,); - let builder = self.inner.builder(Method::DELETE, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::DELETE, path_template, resource_name))) - }) - .ok_or_else(|| { - let mut paths = Vec::new(); - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.name).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - "name", - "projects/*/locations/*/datasets/*", - ); - paths.push(builder.build()); - } - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.name).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - "name", - "projects/*/datasets/*", - ); - paths.push(builder.build()); - } - google_cloud_gax::error::Error::binding(BindingError { paths }) - })??; - if let Some(recorder) = gaxi::observability::RequestRecorder::current() { - recorder.on_client_request( - gaxi::observability::ClientRequestAttributes::default() - .set_rpc_method("google.cloud.timeseriesinsights.v1.TimeseriesInsightsController/DeleteDataSet") - .set_url_template(_path_template) - .set_resource_name(_resource_name) - ); - } - let options = google_cloud_gax::options::internal::set_default_idempotency( - options, - gaxi::http::default_idempotency(&method), - ); - let builder = builder.query(&[("$alt", "json;enum-encoding=int")]).header( - "x-goog-api-client", - HeaderValue::from_static(&crate::info::X_GOOG_API_CLIENT_HEADER), - ); - let body = gaxi::http::handle_empty(None::, &method); - self.inner - .execute(builder, body, options) - .await - .map(|r: crate::Response| { - let (parts, _) = r.into_parts(); - crate::Response::from_parts(parts, ()) - }) - } - - async fn append_events( - &self, - req: crate::model::AppendEventsRequest, - options: crate::RequestOptions, - ) -> Result> { - use gaxi::http::reqwest::{HeaderValue, Method}; - use gaxi::path_parameter::PathMismatchBuilder; - use gaxi::path_parameter::try_match; - use gaxi::routing_parameter::Segment; - use google_cloud_gax::error::binding::BindingError; - let mut req = req; - let (builder, method, _path_template, _resource_name) = None - .or_else(|| { - let var_dataset = try_match( - Some(&req).map(|m| &m.dataset).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}:appendEvents", var_dataset,); - let path_template = "/v1/{dataset}:appendEvents"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_dataset,); - let _ = Some(&mut req).map(|m| std::mem::take(&mut m.dataset)); - let builder = self.inner.builder(Method::POST, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::POST, path_template, resource_name))) - }) - .or_else(|| { - let var_dataset = try_match( - Some(&req).map(|m| &m.dataset).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}:appendEvents", var_dataset,); - let path_template = "/v1/{dataset}:appendEvents"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_dataset,); - let _ = Some(&mut req).map(|m| std::mem::take(&mut m.dataset)); - let builder = self.inner.builder(Method::POST, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::POST, path_template, resource_name))) - }) - .ok_or_else(|| { - let mut paths = Vec::new(); - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.dataset).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - "dataset", - "projects/*/locations/*/datasets/*", - ); - paths.push(builder.build()); - } - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.dataset).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - "dataset", - "projects/*/datasets/*", - ); - paths.push(builder.build()); - } - google_cloud_gax::error::Error::binding(BindingError { paths }) - })??; - if let Some(recorder) = gaxi::observability::RequestRecorder::current() { - recorder.on_client_request( - gaxi::observability::ClientRequestAttributes::default() - .set_rpc_method("google.cloud.timeseriesinsights.v1.TimeseriesInsightsController/AppendEvents") - .set_url_template(_path_template) - .set_resource_name(_resource_name) - ); - } - let options = google_cloud_gax::options::internal::set_default_idempotency( - options, - gaxi::http::default_idempotency(&method), - ); - let builder = builder.query(&[("$alt", "json;enum-encoding=int")]).header( - "x-goog-api-client", - HeaderValue::from_static(&crate::info::X_GOOG_API_CLIENT_HEADER), - ); - let body = gaxi::http::handle_empty(Some(req), &method); - self.inner.execute(builder, body, options).await - } - - async fn query_data_set( - &self, - req: crate::model::QueryDataSetRequest, - options: crate::RequestOptions, - ) -> Result> { - use gaxi::http::reqwest::{HeaderValue, Method}; - use gaxi::path_parameter::PathMismatchBuilder; - use gaxi::path_parameter::try_match; - use gaxi::routing_parameter::Segment; - use google_cloud_gax::error::binding::BindingError; - let mut req = req; - let (builder, method, _path_template, _resource_name) = None - .or_else(|| { - let var_name = try_match( - Some(&req).map(|m| &m.name).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}:query", var_name,); - let path_template = "/v1/{name}:query"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_name,); - let _ = Some(&mut req).map(|m| std::mem::take(&mut m.name)); - let builder = self.inner.builder(Method::POST, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::POST, path_template, resource_name))) - }) - .or_else(|| { - let var_name = try_match( - Some(&req).map(|m| &m.name).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}:query", var_name,); - let path_template = "/v1/{name}:query"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_name,); - let _ = Some(&mut req).map(|m| std::mem::take(&mut m.name)); - let builder = self.inner.builder(Method::POST, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::POST, path_template, resource_name))) - }) - .ok_or_else(|| { - let mut paths = Vec::new(); - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.name).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - "name", - "projects/*/locations/*/datasets/*", - ); - paths.push(builder.build()); - } - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.name).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - "name", - "projects/*/datasets/*", - ); - paths.push(builder.build()); - } - google_cloud_gax::error::Error::binding(BindingError { paths }) - })??; - if let Some(recorder) = gaxi::observability::RequestRecorder::current() { - recorder.on_client_request( - gaxi::observability::ClientRequestAttributes::default() - .set_rpc_method("google.cloud.timeseriesinsights.v1.TimeseriesInsightsController/QueryDataSet") - .set_url_template(_path_template) - .set_resource_name(_resource_name) - ); - } - let options = google_cloud_gax::options::internal::set_default_idempotency( - options, - gaxi::http::default_idempotency(&method), - ); - let builder = builder.query(&[("$alt", "json;enum-encoding=int")]).header( - "x-goog-api-client", - HeaderValue::from_static(&crate::info::X_GOOG_API_CLIENT_HEADER), - ); - let body = gaxi::http::handle_empty(Some(req), &method); - self.inner.execute(builder, body, options).await - } - - async fn evaluate_slice( - &self, - req: crate::model::EvaluateSliceRequest, - options: crate::RequestOptions, - ) -> Result> { - use gaxi::http::reqwest::{HeaderValue, Method}; - use gaxi::path_parameter::PathMismatchBuilder; - use gaxi::path_parameter::try_match; - use gaxi::routing_parameter::Segment; - use google_cloud_gax::error::binding::BindingError; - let mut req = req; - let (builder, method, _path_template, _resource_name) = None - .or_else(|| { - let var_dataset = try_match( - Some(&req).map(|m| &m.dataset).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}:evaluateSlice", var_dataset,); - let path_template = "/v1/{dataset}:evaluateSlice"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_dataset,); - let _ = Some(&mut req).map(|m| std::mem::take(&mut m.dataset)); - let builder = self.inner.builder(Method::POST, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::POST, path_template, resource_name))) - }) - .or_else(|| { - let var_dataset = try_match( - Some(&req).map(|m| &m.dataset).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}:evaluateSlice", var_dataset,); - let path_template = "/v1/{dataset}:evaluateSlice"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_dataset,); - let _ = Some(&mut req).map(|m| std::mem::take(&mut m.dataset)); - let builder = self.inner.builder(Method::POST, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::POST, path_template, resource_name))) - }) - .ok_or_else(|| { - let mut paths = Vec::new(); - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.dataset).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - "dataset", - "projects/*/locations/*/datasets/*", - ); - paths.push(builder.build()); - } - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.dataset).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/datasets/"), - Segment::SingleWildcard, - ], - "dataset", - "projects/*/datasets/*", - ); - paths.push(builder.build()); - } - google_cloud_gax::error::Error::binding(BindingError { paths }) - })??; - if let Some(recorder) = gaxi::observability::RequestRecorder::current() { - recorder.on_client_request( - gaxi::observability::ClientRequestAttributes::default() - .set_rpc_method("google.cloud.timeseriesinsights.v1.TimeseriesInsightsController/EvaluateSlice") - .set_url_template(_path_template) - .set_resource_name(_resource_name) - ); - } - let options = google_cloud_gax::options::internal::set_default_idempotency( - options, - gaxi::http::default_idempotency(&method), - ); - let builder = builder.query(&[("$alt", "json;enum-encoding=int")]).header( - "x-goog-api-client", - HeaderValue::from_static(&crate::info::X_GOOG_API_CLIENT_HEADER), - ); - let body = gaxi::http::handle_empty(Some(req), &method); - self.inner.execute(builder, body, options).await - } - - async fn evaluate_timeseries( - &self, - req: crate::model::EvaluateTimeseriesRequest, - options: crate::RequestOptions, - ) -> Result> { - use gaxi::http::reqwest::{HeaderValue, Method}; - use gaxi::path_parameter::PathMismatchBuilder; - use gaxi::path_parameter::try_match; - use gaxi::routing_parameter::Segment; - use google_cloud_gax::error::binding::BindingError; - let mut req = req; - let (builder, method, _path_template, _resource_name) = None - .or_else(|| { - let var_parent = try_match( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - ], - )?; - let path = format!("/v1/{}/datasets:evaluateTimeseries", var_parent,); - let path_template = "/v1/{parent}/datasets:evaluateTimeseries"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_parent,); - let _ = Some(&mut req).map(|m| std::mem::take(&mut m.parent)); - let builder = self.inner.builder(Method::POST, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::POST, path_template, resource_name))) - }) - .or_else(|| { - let var_parent = try_match( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[Segment::Literal("projects/"), Segment::SingleWildcard], - )?; - let path = format!("/v1/{}/datasets:evaluateTimeseries", var_parent,); - let path_template = "/v1/{parent}/datasets:evaluateTimeseries"; - - let resource_name = format!("//timeseriesinsights.googleapis.com/{}", var_parent,); - let _ = Some(&mut req).map(|m| std::mem::take(&mut m.parent)); - let builder = self.inner.builder(Method::POST, path); - let builder = Ok(builder); - Some(builder.map(|b| (b, Method::POST, path_template, resource_name))) - }) - .ok_or_else(|| { - let mut paths = Vec::new(); - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[ - Segment::Literal("projects/"), - Segment::SingleWildcard, - Segment::Literal("/locations/"), - Segment::SingleWildcard, - ], - "parent", - "projects/*/locations/*", - ); - paths.push(builder.build()); - } - { - let builder = PathMismatchBuilder::default(); - let builder = builder.maybe_add( - Some(&req).map(|m| &m.parent).map(|s| s.as_str()), - &[Segment::Literal("projects/"), Segment::SingleWildcard], - "parent", - "projects/*", - ); - paths.push(builder.build()); - } - google_cloud_gax::error::Error::binding(BindingError { paths }) - })??; - if let Some(recorder) = gaxi::observability::RequestRecorder::current() { - recorder.on_client_request( - gaxi::observability::ClientRequestAttributes::default() - .set_rpc_method("google.cloud.timeseriesinsights.v1.TimeseriesInsightsController/EvaluateTimeseries") - .set_url_template(_path_template) - .set_resource_name(_resource_name) - ); - } - let options = google_cloud_gax::options::internal::set_default_idempotency( - options, - gaxi::http::default_idempotency(&method), - ); - let builder = builder.query(&[("$alt", "json;enum-encoding=int")]).header( - "x-goog-api-client", - HeaderValue::from_static(&crate::info::X_GOOG_API_CLIENT_HEADER), - ); - let body = gaxi::http::handle_empty(Some(req), &method); - self.inner.execute(builder, body, options).await - } -} diff --git a/src/spanner/Cargo.toml b/src/spanner/Cargo.toml index 4147c23e50..1d830a4eae 100644 --- a/src/spanner/Cargo.toml +++ b/src/spanner/Cargo.toml @@ -26,7 +26,7 @@ categories.workspace = true rust-version.workspace = true [package.metadata.docs.rs] -features = ["unstable-time"] +all-features = true [features] default = ["builtin-metrics", "default-rustls-provider"] @@ -37,32 +37,41 @@ default = ["builtin-metrics", "default-rustls-provider"] default-rustls-provider = ["gaxi/_default-rustls-provider"] unstable-stream = ["dep:futures"] +# Feature flag for unstable OpenTelemetry integration. +# +# Enables injecting a custom caller-supplied OpenTelemetry [MeterProvider] +# via [with_meter_provider] and re-exports [opentelemetry]. +# +# Note: Because the `opentelemetry` crate has not yet reached 1.0, this feature +# is considered unstable and is subject to SemVer-incompatible changes across +# OpenTelemetry minor releases. +unstable-metrics = ["_internal-metrics"] + # Enables support for types from the `time` crate (`time::OffsetDateTime` and `time::Date`). unstable-time = ["wkt/time"] -# Enables collecting and recording metrics on a caller-owned OpenTelemetry MeterProvider. -# Pulls in only the lightweight OpenTelemetry API trait definitions and UUID generator. -# When enabled without `builtin-metrics`, applications can record both Client metrics and -# Spanner built-in request/attempt latency metrics to a custom MeterProvider (e.g. exporting -# to Datadog, Prometheus, or an OpenTelemetry Collector) without compiling Google Cloud -# Monitoring dependencies. -metrics = ["dep:opentelemetry", "dep:uuid"] - # Enabled by default. Includes the background Google Cloud Monitoring (GCM) export pipeline # that automatically publishes Spanner built-in request and latency metrics to Cloud Monitoring. # +# OpenTelemetry is used strictly as an internal implementation detail and is NOT exposed in the +# public API. +# # Applications that run outside GCP (such as Spanner Omni), or that send metrics to third-party -# monitoring systems instead of Cloud Monitoring, can disable this feature while keeping the -# `metrics` feature enabled to record built-in metrics directly to their own OpenTelemetry +# monitoring systems instead of Cloud Monitoring, can disable this feature and enable +# `unstable-metrics` to record built-in metrics directly to their own OpenTelemetry # MeterProvider. builtin-metrics = [ + "_internal-metrics", "dep:google-cloud-api", "dep:google-cloud-monitoring-v3", "dep:opentelemetry_sdk", "gaxi/_internal-http-client", - "metrics", ] +# Internal feature enabling the metric collection machinery (SpannerMetrics, attempt interceptor). +# Used internally by `builtin-metrics` and `unstable-metrics`. +_internal-metrics = ["dep:opentelemetry", "dep:uuid"] + [dependencies] async-trait.workspace = true base64.workspace = true diff --git a/src/spanner/src/batch_read_only_transaction.rs b/src/spanner/src/batch_read_only_transaction.rs index 6976e8fc78..a5f1fdb7bf 100644 --- a/src/spanner/src/batch_read_only_transaction.rs +++ b/src/spanner/src/batch_read_only_transaction.rs @@ -46,6 +46,7 @@ use std::time::{Duration, Instant}; /// # Ok(()) /// # } /// ``` +#[derive(Debug)] pub struct BatchReadOnlyTransactionBuilder { inner: MultiUseReadOnlyTransactionBuilder, } @@ -500,9 +501,9 @@ pub(crate) mod tests { #[test] fn auto_traits() { - assert_impl_all!(BatchReadOnlyTransactionBuilder: Send, Sync); - assert_impl_all!(BatchReadOnlyTransaction: Send, Sync, Debug); - assert_impl_all!(Partition: Send, Sync, Debug); + assert_impl_all!(BatchReadOnlyTransactionBuilder: Debug, Send, Sync); + assert_impl_all!(BatchReadOnlyTransaction: Debug, Send, Sync); + assert_impl_all!(Partition: Debug, Send, Sync); } #[test] diff --git a/src/spanner/src/batch_write_transaction.rs b/src/spanner/src/batch_write_transaction.rs index 37575d95de..4f7832c30e 100644 --- a/src/spanner/src/batch_write_transaction.rs +++ b/src/spanner/src/batch_write_transaction.rs @@ -37,6 +37,7 @@ use google_cloud_gax::retry_policy::RetryPolicyExt; use google_cloud_gax::retry_result::RetryResult; use google_cloud_gax::retry_state::RetryState; use http::HeaderMap; +use std::fmt::{Debug, Formatter, Result as FmtResult}; use std::sync::Arc; use std::time::Duration; use std::time::Instant; @@ -51,6 +52,7 @@ use futures::Stream; /// per-request tags apply only to queries and reads, and are ignored by the /// `BatchWrite` RPC. Use [set_transaction_tag][BatchWriteTransactionBuilder::set_transaction_tag] /// to tag the transactions of a batch write. +#[derive(Debug)] pub struct BatchWriteTransactionBuilder { client: DatabaseClient, transaction_tag: Option, @@ -225,6 +227,7 @@ impl BatchWriteTransactionBuilder { /// /// Batch writes are not guaranteed to be atomic across mutation groups. /// All mutations within a group are applied atomically. +#[derive(Debug)] pub struct BatchWriteTransaction { session_name: String, client: DatabaseClient, @@ -339,6 +342,18 @@ pub struct BatchWriteResponseStream { operation_recorded: bool, } +impl Debug for BatchWriteResponseStream { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_struct("BatchWriteResponseStream") + .field("session_name", &self.session_name) + .field("transaction_tag", &self.transaction_tag) + .field("priority", &self.priority) + .field("total_count", &self.total_count) + .field("completed_count", &self.completed_count) + .finish_non_exhaustive() + } +} + impl BatchWriteResponseStream { /// Fetches the next [BatchWriteResponse] from the stream. /// @@ -649,12 +664,13 @@ mod tests { use spanner_grpc_mock::MockSpanner; use spanner_grpc_mock::google::rpc::Status as ProtoStatus; use spanner_grpc_mock::google::spanner::v1 as mock_v1; + use std::fmt::Debug; #[test] fn traits() { - static_assertions::assert_impl_all!(BatchWriteTransactionBuilder: Send, Sync); - static_assertions::assert_impl_all!(BatchWriteTransaction: Send, Sync); - static_assertions::assert_impl_all!(BatchWriteResponseStream: Send, Sync); + static_assertions::assert_impl_all!(BatchWriteTransactionBuilder: Debug, Send, Sync); + static_assertions::assert_impl_all!(BatchWriteTransaction: Debug, Send, Sync); + static_assertions::assert_impl_all!(BatchWriteResponseStream: Debug, Send, Sync); } pub(crate) async fn setup_db_client( diff --git a/src/spanner/src/client.rs b/src/spanner/src/client.rs index 5ff8cadedd..c95b5d9681 100644 --- a/src/spanner/src/client.rs +++ b/src/spanner/src/client.rs @@ -27,7 +27,7 @@ use crate::model::{ ReadRequest, RollbackRequest, Session, Transaction, }; use crate::observability::Observability; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] use crate::observability::metrics::SpannerMetricsInterceptor; use crate::omni::{InstanceType, TlsConfig, TlsError, is_plaintext_endpoint}; use crate::request_id::RequestIdCreator; @@ -53,13 +53,13 @@ use std::sync::Arc; use tokio::task::JoinSet; pub use crate::database_client::DatabaseClient; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] use crate::observability::SharedMeterProvider; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] use google_cloud_gax::client_builder::Extensions; pub use google_cloud_spanner_admin_database_v1::client::DatabaseAdmin; pub use google_cloud_spanner_admin_instance_v1::client::InstanceAdmin; -#[cfg(feature = "metrics")] +#[cfg(feature = "unstable-metrics")] use opentelemetry::metrics::MeterProvider; /// A client for the [Spanner] API. @@ -76,9 +76,9 @@ pub struct Spanner { pub(crate) request_id_creator: Arc, #[cfg(feature = "builtin-metrics")] pub(crate) export_builtin_metrics_to_cloud_monitoring: Option, - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] pub(crate) export_builtin_metrics_to_custom_provider: Option, - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] pub(crate) meter_provider: Option, } @@ -126,7 +126,7 @@ impl google_cloud_gax::client_builder::internal::ClientFactory for Factory { .get::() .map(|config| config.0); - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] let (export_builtin_metrics_to_custom_provider, meter_provider) = extract_metrics_config(&config.extensions); @@ -138,9 +138,9 @@ impl google_cloud_gax::client_builder::internal::ClientFactory for Factory { request_id_creator: Arc::new(RequestIdCreator::new()), #[cfg(feature = "builtin-metrics")] export_builtin_metrics_to_cloud_monitoring, - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] export_builtin_metrics_to_custom_provider, - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] meter_provider, }) } @@ -241,7 +241,8 @@ pub trait SpannerBuilderExt { /// /// - **Spanner Omni**: Because Cloud Monitoring is not active for Omni, built-in metrics /// are exported to this provider by default. - #[cfg(feature = "metrics")] + #[cfg(feature = "unstable-metrics")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-metrics")))] fn with_meter_provider(self, provider: Arc) -> Self; /// Configures whether built-in request and attempt latency metrics should be @@ -289,7 +290,8 @@ pub trait SpannerBuilderExt { /// /// - **Emulator**: When connecting to the Spanner emulator, all built-in metrics collection /// and export are disabled. - #[cfg(feature = "metrics")] + #[cfg(feature = "unstable-metrics")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-metrics")))] fn with_export_builtin_metrics_to_custom_provider(self, export: bool) -> Self; /// Configures whether built-in request and attempt latency metrics should be @@ -318,10 +320,11 @@ pub trait SpannerBuilderExt { /// # Independence from Custom Provider Export /// /// Disabling Cloud Monitoring export does **not** affect built-in metrics exported to a - /// custom [`MeterProvider`] configured via - /// [`with_meter_provider`](Self::with_meter_provider) and - /// [`with_export_builtin_metrics_to_custom_provider`](Self::with_export_builtin_metrics_to_custom_provider). + /// custom `MeterProvider` configured via `with_meter_provider` and + /// `with_export_builtin_metrics_to_custom_provider` (available when the `unstable-metrics` + /// feature is enabled). #[cfg(feature = "builtin-metrics")] + #[cfg_attr(docsrs, doc(cfg(feature = "builtin-metrics")))] fn with_export_builtin_metrics_to_cloud_monitoring(self, export: bool) -> Self; } @@ -329,7 +332,7 @@ pub trait SpannerBuilderExt { #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct ExportBuiltinMetricsToCloudMonitoring(bool); -#[cfg(feature = "metrics")] +#[cfg(feature = "unstable-metrics")] #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct ExportBuiltinMetricsToCustomProvider(bool); @@ -347,12 +350,12 @@ impl SpannerBuilderExt for ClientBuilder { .with_extension(tls_config) } - #[cfg(feature = "metrics")] + #[cfg(feature = "unstable-metrics")] fn with_meter_provider(self, provider: Arc) -> Self { self.with_extension(SharedMeterProvider::from(provider)) } - #[cfg(feature = "metrics")] + #[cfg(feature = "unstable-metrics")] fn with_export_builtin_metrics_to_custom_provider(self, export: bool) -> Self { self.with_extension(ExportBuiltinMetricsToCustomProvider(export)) } @@ -528,7 +531,7 @@ async fn create_channel_pool( Ok(pool) } -#[cfg(feature = "metrics")] +#[cfg(feature = "unstable-metrics")] fn extract_metrics_config(extensions: &Extensions) -> (Option, Option) { let export_builtin_metrics_to_custom_provider = extensions .get::() @@ -544,6 +547,11 @@ fn extract_metrics_config(extensions: &Extensions) -> (Option, Option (Option, Option) { + (None, None) +} + macro_rules! define_idempotent_rpc { ($method:ident, $request_type:ty, $response_type:ty, $canonical_name:expr) => { pub(crate) async fn $method( @@ -554,7 +562,7 @@ macro_rules! define_idempotent_rpc { o11y: &Arc, ) -> Result<$response_type> { let options = self.attach_request_id(options, channel.channel_id); - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] let options = options.insert_extension(Arc::clone(o11y)); o11y.trace_operation( $canonical_name, @@ -737,9 +745,9 @@ impl Spanner { request_id_creator: Arc::new(RequestIdCreator::new()), #[cfg(feature = "builtin-metrics")] export_builtin_metrics_to_cloud_monitoring: None, - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] export_builtin_metrics_to_custom_provider: None, - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] meter_provider: None, } } @@ -749,17 +757,17 @@ impl Spanner { self.export_builtin_metrics_to_cloud_monitoring } - #[cfg(all(feature = "metrics", not(feature = "builtin-metrics")))] + #[cfg(all(feature = "_internal-metrics", not(feature = "builtin-metrics")))] pub(crate) fn export_builtin_metrics_to_cloud_monitoring(&self) -> Option { None } - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] pub(crate) fn export_builtin_metrics_to_custom_provider(&self) -> Option { self.export_builtin_metrics_to_custom_provider } - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] pub(crate) fn meter_provider(&self) -> Option { self.meter_provider.clone() } @@ -962,13 +970,13 @@ impl Channel { let request_id_interceptor: Arc = Arc::new(SpannerRequestIdInterceptor); - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] let interceptor: Arc = Arc::new(vec![ request_id_interceptor, Arc::new(SpannerMetricsInterceptor), ]); - #[cfg(not(feature = "metrics"))] + #[cfg(not(feature = "_internal-metrics"))] let interceptor: Arc = request_id_interceptor; transport.inner.set_attempt_interceptor(interceptor); @@ -2464,7 +2472,7 @@ mod tests { ); } - #[cfg(feature = "builtin-metrics")] + #[cfg(all(feature = "builtin-metrics", feature = "unstable-metrics"))] #[tokio_test_no_panics] async fn spanner_builder_with_meter_provider_cloud_spanner_default_does_not_export_builtin_metrics() { @@ -2535,7 +2543,7 @@ mod tests { ); } - #[cfg(feature = "builtin-metrics")] + #[cfg(all(feature = "builtin-metrics", feature = "unstable-metrics"))] #[tokio_test_no_panics] async fn spanner_builder_with_export_builtin_metrics_to_custom_provider() { use opentelemetry::metrics::MeterProvider; @@ -2598,7 +2606,7 @@ mod tests { ); } - #[cfg(feature = "builtin-metrics")] + #[cfg(all(feature = "builtin-metrics", feature = "unstable-metrics"))] #[tokio_test_no_panics] async fn spanner_builder_omni_with_meter_provider_defaults_to_export_builtin_metrics() { use opentelemetry::metrics::MeterProvider; @@ -2692,7 +2700,7 @@ mod tests { ); } - #[cfg(feature = "builtin-metrics")] + #[cfg(all(feature = "builtin-metrics", feature = "unstable-metrics"))] #[tokio_test_no_panics] async fn spanner_builder_with_raw_meter_provider_extension() { use opentelemetry::metrics::MeterProvider; diff --git a/src/spanner/src/database_client.rs b/src/spanner/src/database_client.rs index 9f7a667055..0379d3165c 100644 --- a/src/spanner/src/database_client.rs +++ b/src/spanner/src/database_client.rs @@ -46,17 +46,18 @@ use crate::routing::key_range_cache::KeyRangeCache; use crate::routing::key_recipe_cache::KeyRecipeCache; use crate::routing::latency_registry::LatencyRegistry; use crate::routing::location_router::{LocationRouter, RoutingContext}; -use crate::routing::server_connection::ServerConnection; +use crate::routing::server_connection::{ActiveRequestGuard, ServerConnection}; use crate::server_streaming::builder::{BatchWrite, ExecuteStreamingSql, StreamingRead}; -use crate::server_streaming::stream::TransactionIdCallback; +use crate::server_streaming::stream::{StreamGuard, StreamLifetimeGuard, TransactionIdCallback}; use crate::session_maintainer::ManagedSessionMaintainer; use crate::transaction_runner::TransactionRunnerBuilder; use crate::write_only_transaction::WriteOnlyTransactionBuilder; -use crate::{RequestOptions, Result}; +use crate::{Error, RequestOptions, Result}; use bytes::Bytes; use google_cloud_gax::error::rpc::Code; use std::env; use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; /// A client for interacting with a specific Spanner database. @@ -189,9 +190,12 @@ macro_rules! define_db_streaming_rpc { let callback = self.streaming_transaction_id_callback(is_read_write_begin, connection.as_ref()); if let Some(connection) = connection { + let group_uid = request.routing_group_uid(); + let guard = self.create_streaming_route_guard(&connection, group_uid); return self .spanner .$method(request, options, connection.channel()) + .with_lifetime_guard(guard) .with_transaction_id_callback(callback); } let lease = self.spanner.pick_channel_for_target(&channel_target); @@ -590,10 +594,10 @@ impl DatabaseClient { latency: Duration, result: &Result, ) { - let Some(routing) = &self.location_routing else { + let Some(connection) = connection else { return; }; - let Some(connection) = connection else { + let Some(routing) = &self.location_routing else { return; }; if connection.is_default() { @@ -604,9 +608,7 @@ impl DatabaseClient { Ok(_) => { routing.location_router.record_success(address); if group_uid > 0 { - routing - .location_router - .record_latency(group_uid, address, latency); + self.record_latency(group_uid, address, latency); } } Err(error) => { @@ -621,7 +623,7 @@ impl DatabaseClient { server_retry_delay, ); if group_uid > 0 { - routing.location_router.record_error(group_uid, address); + self.record_routing_error(group_uid, address); } } } @@ -629,7 +631,6 @@ impl DatabaseClient { } /// Records an observed round-trip latency sample for an endpoint address within a paxos group. - #[allow(dead_code)] // TODO: Used for streaming RPC latency recording in subsequent PRs pub(crate) fn record_latency(&self, group_uid: u64, server_address: &str, latency: Duration) { let Some(routing) = &self.location_routing else { return; @@ -640,7 +641,6 @@ impl DatabaseClient { } /// Records an RPC error penalty for an endpoint address within a paxos group. - #[allow(dead_code)] // TODO: Used for streaming RPC error recording in subsequent PRs pub(crate) fn record_routing_error(&self, group_uid: u64, server_address: &str) { let Some(routing) = &self.location_routing else { return; @@ -650,6 +650,28 @@ impl DatabaseClient { .record_error(group_uid, server_address); } + /// Creates an RAII streaming route guard that holds an [`ActiveRequestGuard`] for the direct + /// connection and reports routing latency, error cooldowns, and success feedback. + fn create_streaming_route_guard( + &self, + connection: &ServerConnection, + group_uid: u64, + ) -> StreamLifetimeGuard { + let routing = self + .location_routing + .as_ref() + .expect("location routing must be present when creating a routed streaming guard"); + let request_guard = connection.acquire_request_guard(); + Arc::new(RoutedStreamGuard { + _request_guard: request_guard, + routing: Arc::clone(routing), + server_address: connection.address().to_string(), + group_uid, + first_response_recorded: AtomicBool::new(false), + error_recorded: AtomicBool::new(false), + }) + } + /// Returns the database ID assigned by the server for location-aware routing, if known. #[allow(dead_code)] // TODO(#6236): Used by request routing in subsequent PRs pub(crate) fn database_id(&self) -> Option { @@ -1191,6 +1213,7 @@ fn is_read_write_begin(selector: Option<&TransactionSelector>) -> bool { } /// A builder for [DatabaseClient]. +#[derive(Debug)] pub struct DatabaseClientBuilder { spanner: Spanner, database_name: String, @@ -1305,7 +1328,7 @@ impl DatabaseClientBuilder { self.database_name }; - #[cfg(feature = "metrics")] + #[cfg(feature = "_internal-metrics")] let o11y = Arc::new( Observability::init( &self.spanner.config, @@ -1318,7 +1341,7 @@ impl DatabaseClientBuilder { ) .await, ); - #[cfg(not(feature = "metrics"))] + #[cfg(not(feature = "_internal-metrics"))] let o11y = Arc::new( Observability::init( &self.spanner.config, @@ -1491,9 +1514,67 @@ impl ObserveResponse for PartitionResponse { fn observe(&self, _client: &DatabaseClient) {} } +/// Stream lifetime drop guard that records active requests, cooldown errors, and latency metrics +/// for direct tablet connections in location-aware routing. +#[derive(Debug)] +struct RoutedStreamGuard { + _request_guard: ActiveRequestGuard, + routing: Arc, + server_address: String, + group_uid: u64, + first_response_recorded: AtomicBool, + error_recorded: AtomicBool, +} + +impl StreamGuard for RoutedStreamGuard { + fn record_first_response(&self, latency: Duration) { + if self.first_response_recorded.swap(true, Ordering::AcqRel) { + return; + } + self.routing + .location_router + .record_success(&self.server_address); + if self.group_uid > 0 { + self.routing.location_router.record_latency( + self.group_uid, + &self.server_address, + latency, + ); + } + } + + fn record_error(&self, error: &Error) { + if self.error_recorded.swap(true, Ordering::AcqRel) { + return; + } + let Some(code) = extract_status_code_from_error(error) else { + return; + }; + if matches!(code, Code::ResourceExhausted | Code::Unavailable) { + let server_retry_delay = extract_retry_delay_from_error(error); + self.routing + .location_router + .record_cooldown_error_with_delay(&self.server_address, code, server_retry_delay); + if self.group_uid > 0 { + self.routing + .location_router + .record_error(self.group_uid, &self.server_address); + } + } + } + + fn record_success(&self) { + if !self.first_response_recorded.swap(true, Ordering::AcqRel) { + self.routing + .location_router + .record_success(&self.server_address); + } + } +} + /// Extracts the group UID from an RPC request to associate routing feedback with the covering group. /// -/// Protobuf requests that define a `routing_hint` field ([`ExecuteSqlRequest`], [`BeginTransactionRequest`], +/// Protobuf requests that define a `routing_hint` field ([`ExecuteSqlRequest`], [`ReadRequest`], [`BeginTransactionRequest`], /// and [`CommitRequest`]) inspect their hint and return `group_uid`. Requests without a `routing_hint` /// field in their protobuf definitions ([`ExecuteBatchDmlRequest`], [`RollbackRequest`], [`PartitionQueryRequest`], /// and [`PartitionReadRequest`]) return 0. @@ -1509,6 +1590,12 @@ impl RequestRoutingGroupUid for ExecuteSqlRequest { } } +impl RequestRoutingGroupUid for ReadRequest { + fn routing_group_uid(&self) -> u64 { + self.routing_hint.as_ref().map_or(0, |hint| hint.group_uid) + } +} + impl RequestRoutingGroupUid for BeginTransactionRequest { fn routing_group_uid(&self) -> u64 { self.routing_hint.as_ref().map_or(0, |hint| hint.group_uid) @@ -1573,7 +1660,7 @@ mod tests { use mockall::Sequence; use spanner_grpc_mock::google::spanner::v1 as mock_v1; use spanner_grpc_mock::{MockSpanner, start}; - use std::fmt; + use std::fmt::Debug; use std::sync::Mutex; use tokio::sync::mpsc; @@ -1605,7 +1692,7 @@ mod tests { "BeginTransactionRequest without hint must return 0" ); - let commit_with_hint = CommitRequest::default().set_routing_hint(hint); + let commit_with_hint = CommitRequest::default().set_routing_hint(hint.clone()); assert_eq!( commit_with_hint.routing_group_uid(), 9001, @@ -1617,6 +1704,18 @@ mod tests { "CommitRequest without hint must return 0" ); + let read_with_hint = ReadRequest::default().set_routing_hint(hint); + assert_eq!( + read_with_hint.routing_group_uid(), + 9001, + "ReadRequest with hint must return hint group_uid" + ); + assert_eq!( + ReadRequest::default().routing_group_uid(), + 0, + "ReadRequest without hint must return 0" + ); + assert_eq!( ExecuteBatchDmlRequest::default().routing_group_uid(), 0, @@ -1660,7 +1759,8 @@ mod tests { #[test] fn auto_traits() { use static_assertions::assert_impl_all; - assert_impl_all!(DatabaseClient: Send, Sync, Clone, fmt::Debug); + assert_impl_all!(DatabaseClient: Send, Sync, Clone, Debug); + assert_impl_all!(DatabaseClientBuilder: Debug, Send, Sync); } #[tokio_test_no_panics] diff --git a/src/spanner/src/error.rs b/src/spanner/src/error.rs index 7b77f3794d..978442c1b6 100644 --- a/src/spanner/src/error.rs +++ b/src/spanner/src/error.rs @@ -16,6 +16,8 @@ use google_cloud_gax::error::rpc::{Code, Status, StatusDetails}; use std::error::Error; pub use crate::from_value::ConvertError; +pub use crate::omni::TlsError; +pub use crate::row::RowError; pub use wkt::{DurationError, TimestampError}; /// An unexpected error that occurs when the client receives data from Spanner @@ -101,10 +103,12 @@ mod tests { use super::*; use google_cloud_gax::error::rpc::Code; use static_assertions::assert_impl_all; + use std::fmt::Debug; #[test] fn auto_traits() { - assert_impl_all!(BatchUpdateError: Send, Sync, std::fmt::Debug); + assert_impl_all!(BatchUpdateError: Send, Sync, Debug); + assert_impl_all!(SpannerInternalError: Send, Sync, Debug, Clone, PartialEq, Eq); } #[test] diff --git a/src/spanner/src/from_value.rs b/src/spanner/src/from_value.rs index b34c6f3f15..e902ca9b88 100644 --- a/src/spanner/src/from_value.rs +++ b/src/spanner/src/from_value.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::Error; pub use crate::types::{Type, TypeCode}; use crate::value::Kind; use crate::value::SPANNER_DATE_FORMAT; @@ -21,6 +22,7 @@ use base64::prelude::BASE64_STANDARD; use google_cloud_type::model::Date; use rust_decimal::Decimal; use serde_json::Value as JsonValue; +use std::error::Error as _; use std::time::SystemTime; use time::Date as TimeDate; #[cfg(feature = "unstable-time")] @@ -29,6 +31,22 @@ use time::OffsetDateTime; use time::format_description::well_known::Rfc3339; /// Represent failures in converting a Spanner Value to a Rust type. +/// +/// # Example +/// ``` +/// # use google_cloud_spanner::error::ConvertError; +/// # use google_cloud_spanner::types; +/// # use google_cloud_spanner::value::{FromValue, Value}; +/// let value = Value::from("not-a-bool"); +/// let string_type = types::string(); +/// let error = bool::from_value(&value, &string_type).expect_err("string is not a bool"); +/// match error { +/// ConvertError::KindMismatch { want, got } => { +/// println!("Kind mismatch: expected {want:?}, got {got:?}"); +/// } +/// _ => {} +/// } +/// ``` #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum ConvertError { @@ -50,6 +68,42 @@ pub enum ConvertError { Convert(#[source] BoxedError), } +impl ConvertError { + /// Extracts a `ConvertError` from a [`google_cloud_spanner::Error`][Error], if present. + /// + /// # Example + /// ``` + /// # use google_cloud_spanner::client::Spanner; + /// # use google_cloud_spanner::error::ConvertError; + /// # use google_cloud_spanner::statement::Statement; + /// # async fn example(client: Spanner) -> Result<(), google_cloud_spanner::Error> { + /// let db_client = client.database_client("projects/p/instances/i/databases/d").build().await?; + /// let transaction = db_client.single_use().build(); + /// let mut result_set = transaction + /// .execute_query(Statement::builder("SELECT 'not-a-number' AS text").build()) + /// .await?; + /// + /// if let Some(row) = result_set.next().await { + /// let result: Result = row?.try_get("text"); + /// if let Err(error) = result { + /// if let Some(convert_error) = ConvertError::extract(&error) { + /// println!("Conversion failed: {convert_error}"); + /// } + /// } + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// Row deserialization returns [`Error`] with a source of [`ConvertError`] when + /// value conversion fails due to type mismatch or parsing error. This method + /// downcasts the immediate error source. + pub fn extract(err: &Error) -> Option<&Self> { + err.source() + .and_then(|source| source.downcast_ref::()) + } +} + type BoxedError = Box; /// Converts a Spanner [Value] into a Rust type. @@ -1941,4 +1995,27 @@ mod tests { let j = JsonValue::from_value(&v, &spanner_type).unwrap(); assert_eq!(j, serde_json::json!({"a": "hello"})); } + + #[test] + fn convert_error_auto_traits() { + use std::fmt::Debug; + static_assertions::assert_impl_all!(ConvertError: Debug, Send, Sync); + } + + #[test] + fn convert_error_extract() { + let error = crate::Error::deser(ConvertError::NotNull); + let extracted = ConvertError::extract(&error).expect("should extract ConvertError"); + assert!( + matches!(extracted, ConvertError::NotNull), + "expected NotNull variant" + ); + + let other_error = + crate::Error::deser(crate::row::RowError::ColumnNotFound("col".to_string())); + assert!( + ConvertError::extract(&other_error).is_none(), + "should return None for non-ConvertError" + ); + } } diff --git a/src/spanner/src/lib.rs b/src/spanner/src/lib.rs index d8681d7d3d..20e0274065 100644 --- a/src/spanner/src/lib.rs +++ b/src/spanner/src/lib.rs @@ -19,6 +19,22 @@ //! some parts are hard to use. //! //! We welcome feedback about the APIs, documentation, missing features, bugs, etc. +//! +//! # Features +//! +//! - `builtin-metrics`: enabled by default. Automatically exports Spanner built-in request +//! and attempt latency metrics to Google Cloud Monitoring in the background. +//! - `default-rustls-provider`: enabled by default. Use the default rustls crypto provider +//! ([aws-lc-rs]) for TLS and authentication. +//! - `unstable-metrics`: enable integration with a custom caller-supplied OpenTelemetry +//! `MeterProvider`. Because the `opentelemetry` crate is pre-1.0, this feature is +//! subject to SemVer-incompatible changes across OpenTelemetry minor releases. +//! - `unstable-stream`: enable the (unstable) features to convert several types to +//! a `futures::Stream`. +//! - `unstable-time`: enable support for types from the `time` crate (`time::OffsetDateTime` +//! and `time::Date`). +//! +//! [aws-lc-rs]: https://crates.io/crates/aws-lc-rs #![cfg_attr(docsrs, feature(doc_cfg))] @@ -47,7 +63,12 @@ pub mod value; pub use google_cloud_gax::Result; pub use google_cloud_gax::error::Error; -#[cfg(feature = "metrics")] +/// Re-export of the `opentelemetry` crate. +/// +/// **Warning:** This re-export is subject to SemVer-incompatible changes across +/// OpenTelemetry minor releases until OpenTelemetry reaches 1.0. +#[cfg(feature = "unstable-metrics")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-metrics")))] pub use opentelemetry; pub use rust_decimal::Decimal; diff --git a/src/spanner/src/mutation.rs b/src/spanner/src/mutation.rs index f0d9feaba2..29f4d1fc1c 100644 --- a/src/spanner/src/mutation.rs +++ b/src/spanner/src/mutation.rs @@ -244,6 +244,7 @@ impl Delete { } /// A builder for constructing `Write` mutations fluently. +#[derive(Clone, Debug, PartialEq)] pub struct WriteBuilder { table: String, mutation_type: MutationType, @@ -251,6 +252,7 @@ pub struct WriteBuilder { values: Vec, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] enum MutationType { Insert, Update, @@ -302,6 +304,7 @@ impl WriteBuilder { } /// A binder that associates a column name with a value within a `WriteBuilder`. +#[derive(Clone, Debug, PartialEq)] pub struct ValueBinder { builder: WriteBuilder, column: String, @@ -533,8 +536,8 @@ mod tests { static_assertions::assert_impl_all!(Mutation: Send, Sync, Clone, std::fmt::Debug); static_assertions::assert_impl_all!(Write: Send, Sync, Clone, std::fmt::Debug); static_assertions::assert_impl_all!(Delete: Send, Sync, Clone, std::fmt::Debug); - static_assertions::assert_impl_all!(WriteBuilder: Send, Sync); - static_assertions::assert_impl_all!(ValueBinder: Send, Sync); + static_assertions::assert_impl_all!(WriteBuilder: Send, Sync, Clone, std::fmt::Debug); + static_assertions::assert_impl_all!(ValueBinder: Send, Sync, Clone, std::fmt::Debug); static_assertions::assert_impl_all!(MutationGroup: Send, Sync, Clone, std::fmt::Debug); } @@ -619,10 +622,10 @@ mod tests { match mutation.inner { InternalMutation::Insert(write) => { - assert_eq!(write.values[0].as_string(), "user-123"); - assert_eq!(write.values[1].as_string(), "42"); - assert!(write.values[2].as_bool()); - assert_eq!(write.values[3].as_string(), "admin"); + assert_eq!(write.values[0].as_str(), Some("user-123")); + assert_eq!(write.values[1].as_str(), Some("42")); + assert_eq!(write.values[2].as_bool(), Some(true)); + assert_eq!(write.values[3].as_str(), Some("admin")); } _ => panic!("Expected Insert mutation"), } @@ -673,8 +676,8 @@ mod tests { assert_eq!(write.table, "Users"); assert_eq!(write.columns, vec!["UserId", "UserName"]); assert_eq!(write.values.len(), 2); - assert_eq!(write.values[0].as_string(), "1"); - assert_eq!(write.values[1].as_string(), "Alice"); + assert_eq!(write.values[0].as_str(), Some("1")); + assert_eq!(write.values[1].as_str(), Some("Alice")); } _ => panic!("Expected Insert mutation"), } @@ -692,7 +695,7 @@ mod tests { assert_eq!(write.table, "Users"); assert_eq!(write.columns, vec!["UserId"]); assert_eq!(write.values.len(), 1); - assert_eq!(write.values[0].as_string(), "1"); + assert_eq!(write.values[0].as_str(), Some("1")); } _ => panic!("Expected Update mutation"), } @@ -710,7 +713,7 @@ mod tests { assert_eq!(write.table, "Users"); assert_eq!(write.columns, vec!["UserId"]); assert_eq!(write.values.len(), 1); - assert_eq!(write.values[0].as_string(), "1"); + assert_eq!(write.values[0].as_str(), Some("1")); } _ => panic!("Expected InsertOrUpdate mutation"), } @@ -728,7 +731,7 @@ mod tests { assert_eq!(write.table, "Users"); assert_eq!(write.columns, vec!["UserId"]); assert_eq!(write.values.len(), 1); - assert_eq!(write.values[0].as_string(), "1"); + assert_eq!(write.values[0].as_str(), Some("1")); } _ => panic!("Expected Replace mutation"), } diff --git a/src/spanner/src/observability/metrics.rs b/src/spanner/src/observability/metrics.rs index cd03fd46d9..58cef37ae1 100644 --- a/src/spanner/src/observability/metrics.rs +++ b/src/spanner/src/observability/metrics.rs @@ -19,25 +19,25 @@ use gaxi::attempt_interceptor::AttemptInterceptor; use gaxi::options::ClientConfig; use google_cloud_gax::error::Error; use http::HeaderMap; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] use std::fmt; use std::fmt::Debug; use std::future::Future; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] use std::ops::Deref; use std::sync::Arc; use std::time::Duration; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) const INSTRUMENTATION_SCOPE: &str = "cloud.google.com/rust"; -#[cfg(any(test, feature = "builtin-metrics"))] +#[cfg(feature = "_internal-metrics")] pub(crate) const NATIVE_METRICS_PREFIX: &str = "spanner.googleapis.com/internal/client/"; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) const CLIENT_METRICS_PREFIX: &str = "spanner/client/"; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] use { google_cloud_gax::options::RequestOptions, http::header::{HeaderName, HeaderValue}, @@ -67,7 +67,7 @@ use { #[cfg(feature = "builtin-metrics")] pub(crate) const DEFAULT_EXPORT_INTERVAL: Duration = Duration::from_secs(60); -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) const BUCKET_BOUNDARIES: [f64; 50] = [ 0.0, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 25.0, 30.0, 40.0, 50.0, 65.0, 80.0, 100.0, 130.0, 160.0, 200.0, @@ -88,7 +88,7 @@ const DEFAULT_METADATA_ROOT: &str = "http://metadata.google.internal"; #[cfg(feature = "builtin-metrics")] const INSTANCE_ZONE_METADATA_PATH: &str = "/computeMetadata/v1/instance/zone"; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] #[derive(Clone, Debug)] pub(crate) struct SpannerMetrics { operation_latencies: Histogram, @@ -102,7 +102,7 @@ pub(crate) struct SpannerMetrics { afe_connectivity_error_count: Counter, } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] impl SpannerMetrics { #[cfg(any(test, feature = "builtin-metrics"))] pub(crate) fn new(meter: &Meter) -> Self { @@ -172,7 +172,7 @@ impl SpannerMetrics { /// Parses `projects/{project}/instances/{instance}/databases/{database}` into its /// `(project_id, instance_id, database_id)` components. -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) fn parse_database_name(database_name: &str) -> Option<(&str, &str, &str)> { let mut parts = database_name.split('/'); if parts.next() != Some("projects") { @@ -195,7 +195,7 @@ pub(crate) fn parse_database_name(database_name: &str) -> Option<(&str, &str, &s /// Generates a unique identifier for the `client_uid` metric attribute in the format /// `UUID@PID@hostname`. -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) fn generate_client_uid() -> String { let uuid = Uuid::new_v4().to_string(); let pid = process::id(); @@ -328,12 +328,12 @@ async fn fetch_location_from_mds() -> Option { } /// Returns the library client identification string (`"spanner-rust/"`). -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) fn client_name() -> &'static str { concat!("spanner-rust/", env!("CARGO_PKG_VERSION")) } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] #[derive(Debug)] pub(crate) struct Observability { pub(crate) metrics: Vec, @@ -344,7 +344,7 @@ pub(crate) struct Observability { pub(crate) caller_meter_provider: Option, } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] impl Observability { pub(crate) fn disabled() -> Self { Self::disabled_with_caller_provider(None) @@ -661,7 +661,7 @@ impl Observability { } } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] impl Drop for Observability { fn drop(&mut self) { self.shutdown(); @@ -679,17 +679,17 @@ impl Drop for Observability { /// - Implements [`Deref`](std::ops::Deref) so callers /// can invoke meter methods directly without manual unwrapping. /// - Implements [`Clone`] and [`From>`]. -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) struct SharedMeterProvider(pub(crate) Arc); -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] impl Clone for SharedMeterProvider { fn clone(&self) -> Self { Self(Arc::clone(&self.0)) } } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] impl fmt::Debug for SharedMeterProvider { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter @@ -698,14 +698,14 @@ impl fmt::Debug for SharedMeterProvider { } } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] impl From> for SharedMeterProvider { fn from(provider: Arc) -> Self { Self(provider) } } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] impl Deref for SharedMeterProvider { type Target = dyn MeterProvider; @@ -714,23 +714,23 @@ impl Deref for SharedMeterProvider { } } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) const AFE_SERVER_TIMING_HEADER: &str = "x-goog-spanner-enable-afe-server-timing"; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] static AFE_SERVER_TIMING_ENABLED: LazyLock = LazyLock::new(|| { !env::var("SPANNER_DISABLE_AFE_SERVER_TIMING") .map(|val| val.eq_ignore_ascii_case("true") || val == "1") .unwrap_or(false) }); -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] #[inline] fn is_afe_server_timing_enabled() -> bool { *AFE_SERVER_TIMING_ENABLED } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) fn normalize_method_name(method: &str) -> Cow<'static, str> { let trimmed = method.trim_start_matches('/'); let clean = if let Some(suffix) = trimmed.strip_prefix("google.spanner.v1.") { @@ -771,14 +771,14 @@ pub(crate) fn normalize_method_name(method: &str) -> Cow<'static, str> { } } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] fn error_to_status_str(error: Option<&Error>) -> &'static str { error.map_or("OK", |e| { e.status().map_or("UNKNOWN", |status| status.code.name()) }) } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) fn parse_server_timing_from_headers(headers: &HeaderMap) -> ServerTimings { let mut timings = ServerTimings::default(); for header_value in headers.get_all("server-timing") { @@ -793,10 +793,10 @@ pub(crate) fn parse_server_timing_from_headers(headers: &HeaderMap) -> ServerTim } #[derive(Debug, Default, Clone)] -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) struct SpannerMetricsInterceptor; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] impl AttemptInterceptor for SpannerMetricsInterceptor { fn intercept(&self, headers: &mut HeaderMap, _attempt: u32) { if is_afe_server_timing_enabled() { @@ -824,14 +824,14 @@ impl AttemptInterceptor for SpannerMetricsInterceptor { } } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] #[derive(Debug, Default, PartialEq)] pub(crate) struct ServerTimings { pub(crate) gfe_latency: Option, pub(crate) afe_latency: Option, } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) fn parse_server_timing(header_val: &str) -> ServerTimings { let mut timings = ServerTimings::default(); for part in header_val.split(',') { @@ -856,7 +856,7 @@ pub(crate) fn parse_server_timing(header_val: &str) -> ServerTimings { timings } -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] fn parse_duration_param(param: &str) -> Option { let (key, value) = param.split_once('=')?; if !key.trim().eq_ignore_ascii_case("dur") { @@ -870,11 +870,11 @@ fn parse_duration_param(param: &str) -> Option { .filter(|duration| *duration >= 0.0 && duration.is_finite()) } -#[cfg(not(feature = "metrics"))] +#[cfg(not(feature = "_internal-metrics"))] #[derive(Debug, Default)] pub(crate) struct Observability; -#[cfg(not(feature = "metrics"))] +#[cfg(not(feature = "_internal-metrics"))] impl Observability { pub(crate) fn disabled() -> Self { Self @@ -909,7 +909,7 @@ impl Observability { /// No-op stub implementation when the `metrics` feature is disabled. /// /// This allows interceptors to call `record_attempt` unconditionally without sprinkling - /// `#[cfg(feature = "metrics")]` across call sites. + /// `#[cfg(feature = "_internal-metrics")]` across call sites. #[inline(always)] pub(crate) fn record_attempt( &self, @@ -923,7 +923,7 @@ impl Observability { /// No-op stub implementation when the `metrics` feature is disabled. /// /// This allows client operations to call `record_operation` unconditionally without sprinkling - /// `#[cfg(feature = "metrics")]` across call sites. + /// `#[cfg(feature = "_internal-metrics")]` across call sites. #[inline(always)] pub(crate) fn record_operation( &self, @@ -938,15 +938,15 @@ impl Observability { pub(crate) fn shutdown(&self) {} } -#[cfg(not(feature = "metrics"))] +#[cfg(not(feature = "_internal-metrics"))] #[allow(dead_code)] #[derive(Debug, Default, Clone)] pub(crate) struct SpannerMetricsInterceptor; -#[cfg(not(feature = "metrics"))] +#[cfg(not(feature = "_internal-metrics"))] impl AttemptInterceptor for SpannerMetricsInterceptor {} -#[cfg(all(test, not(feature = "metrics")))] +#[cfg(all(test, not(feature = "_internal-metrics")))] mod disabled_tests { use super::*; @@ -972,7 +972,7 @@ mod disabled_tests { } } -#[cfg(all(test, feature = "metrics"))] +#[cfg(all(test, feature = "_internal-metrics"))] mod tests { use super::*; use google_cloud_gax::error::rpc::{Code, Status}; diff --git a/src/spanner/src/observability/mod.rs b/src/spanner/src/observability/mod.rs index 14589bad2c..d39616ff25 100644 --- a/src/spanner/src/observability/mod.rs +++ b/src/spanner/src/observability/mod.rs @@ -18,5 +18,5 @@ pub(crate) mod metrics; #[cfg(all(test, feature = "builtin-metrics"))] mod mock_tests; pub(crate) use metrics::Observability; -#[cfg(feature = "metrics")] +#[cfg(feature = "_internal-metrics")] pub(crate) use metrics::SharedMeterProvider; diff --git a/src/spanner/src/partitioned_dml_transaction.rs b/src/spanner/src/partitioned_dml_transaction.rs index ddf4d119f1..7ad2fef26d 100644 --- a/src/spanner/src/partitioned_dml_transaction.rs +++ b/src/spanner/src/partitioned_dml_transaction.rs @@ -42,6 +42,7 @@ use google_cloud_gax::options::RequestOptions as GaxRequestOptions; /// # Ok(()) /// # } /// ``` +#[derive(Debug)] pub struct PartitionedDmlTransactionBuilder { client: DatabaseClient, retry_policy: Box, @@ -135,6 +136,7 @@ impl PartitionedDmlTransactionBuilder { /// A Partitioned DML transaction cannot be committed or rolled back. /// /// See also: +#[derive(Debug)] pub struct PartitionedDmlTransaction { client: DatabaseClient, retry_policy: Box, @@ -264,11 +266,12 @@ mod tests { use gaxi::grpc::tonic; use google_cloud_test_macros::tokio_test_no_panics; use spanner_grpc_mock::google::spanner::v1; + use std::fmt::Debug; #[test] fn auto_traits() { - static_assertions::assert_impl_all!(PartitionedDmlTransactionBuilder: Send, Sync); - static_assertions::assert_impl_all!(PartitionedDmlTransaction: Send, Sync); + static_assertions::assert_impl_all!(PartitionedDmlTransactionBuilder: Debug, Send, Sync); + static_assertions::assert_impl_all!(PartitionedDmlTransaction: Debug, Send, Sync); } #[tokio_test_no_panics] diff --git a/src/spanner/src/read_only_transaction.rs b/src/spanner/src/read_only_transaction.rs index f55471cbe9..5def126fa6 100644 --- a/src/spanner/src/read_only_transaction.rs +++ b/src/spanner/src/read_only_transaction.rs @@ -52,6 +52,7 @@ use tokio::sync::futures::OwnedNotified; /// # Ok(()) /// # } /// ``` +#[derive(Debug)] pub struct SingleUseReadOnlyTransactionBuilder { client: DatabaseClient, timestamp_bound: Option, @@ -240,6 +241,7 @@ pub enum BeginTransactionOption { /// # Ok(()) /// # } /// ``` +#[derive(Debug)] pub struct MultiUseReadOnlyTransactionBuilder { client: DatabaseClient, timestamp_bound: Option, @@ -1466,11 +1468,12 @@ pub(crate) mod tests { #[test] fn auto_traits() { - static_assertions::assert_impl_all!(SingleUseReadOnlyTransactionBuilder: Send, Sync); - static_assertions::assert_impl_all!(SingleUseReadOnlyTransaction: Send, Sync, std::fmt::Debug); - static_assertions::assert_impl_all!(MultiUseReadOnlyTransactionBuilder: Send, Sync); - static_assertions::assert_impl_all!(MultiUseReadOnlyTransaction: Send, Sync, std::fmt::Debug); - static_assertions::assert_impl_all!(ReadContext: Send, Sync, std::fmt::Debug); + use std::fmt::Debug; + static_assertions::assert_impl_all!(SingleUseReadOnlyTransactionBuilder: Debug, Send, Sync); + static_assertions::assert_impl_all!(SingleUseReadOnlyTransaction: Debug, Send, Sync); + static_assertions::assert_impl_all!(MultiUseReadOnlyTransactionBuilder: Debug, Send, Sync); + static_assertions::assert_impl_all!(MultiUseReadOnlyTransaction: Debug, Send, Sync); + static_assertions::assert_impl_all!(ReadContext: Debug, Send, Sync); } pub(crate) fn create_session_mock() -> MockSpanner { diff --git a/src/spanner/src/result.rs b/src/spanner/src/result.rs index 93914c1df6..8f01ce5cda 100644 --- a/src/spanner/src/result.rs +++ b/src/spanner/src/result.rs @@ -16,4 +16,5 @@ pub use crate::result_set::ResultSet; pub use crate::result_set_metadata::ResultSetMetadata; -pub use crate::row::Row; +pub use crate::row::{ColumnIndex, Row, RowError}; +pub use crate::transaction_runner::TransactionResult; diff --git a/src/spanner/src/result_set.rs b/src/spanner/src/result_set.rs index 11781f6420..d158439883 100644 --- a/src/spanner/src/result_set.rs +++ b/src/spanner/src/result_set.rs @@ -1837,13 +1837,13 @@ pub(crate) mod tests { ]) .await; - let row = rs.next().await.unwrap().unwrap(); + let row = rs + .next() + .await + .expect("row should exist") + .expect("row should be ok"); assert_eq!(row.raw_values().len(), 1); - if let Some(prost_types::value::Kind::StringValue(ref s)) = row.raw_values()[0].0.kind { - assert_eq!(s, "hello world"); - } else { - panic!("Expected StringValue"); - } + assert_eq!(row.raw_values()[0].as_str(), Some("hello world")); assert!(rs.next().await.is_none()); } @@ -1873,18 +1873,15 @@ pub(crate) mod tests { ]) .await; - let row = rs.next().await.unwrap().unwrap(); + let row = rs + .next() + .await + .expect("row should exist") + .expect("row should be ok"); assert_eq!(row.raw_values().len(), 1); - if let Some(prost_types::value::Kind::ListValue(ref l)) = row.raw_values()[0].0.kind { - assert_eq!(l.values.len(), 1); - if let Some(prost_types::value::Kind::StringValue(ref s)) = l.values[0].kind { - assert_eq!(s, "AB"); - } else { - panic!("Expected StringValue"); - } - } else { - panic!("Expected ListValue"); - } + let list = row.raw_values()[0].as_list().expect("Expected ListValue"); + assert_eq!(list.len(), 1); + assert_eq!(list.get(0).expect("element 0").as_str(), Some("AB")); assert!(rs.next().await.is_none()); } diff --git a/src/spanner/src/retry_policy.rs b/src/spanner/src/retry_policy.rs index 4f4a7ef647..ca4808422a 100644 --- a/src/spanner/src/retry_policy.rs +++ b/src/spanner/src/retry_policy.rs @@ -16,11 +16,13 @@ use google_cloud_gax::error::Error; use google_cloud_gax::retry_policy::{Aip194Strict, RetryPolicy}; -use google_cloud_gax::retry_result::RetryResult; use google_cloud_gax::retry_state::RetryState; use google_cloud_gax::throttle_result::ThrottleResult; use std::time::Duration; +pub use crate::transaction_retry_policy::{BasicTransactionRetryPolicy, TransactionRetryPolicy}; +pub use google_cloud_gax::retry_result::RetryResult; + /// The retry policy the Spanner client applies to RPCs that do not configure /// their own. It decorates/extends [google_cloud_gax::retry_policy::Aip194Strict]. /// diff --git a/src/spanner/src/routing/key_recipe.rs b/src/spanner/src/routing/key_recipe.rs index 898b790ce4..33ac62d7eb 100644 --- a/src/spanner/src/routing/key_recipe.rs +++ b/src/spanner/src/routing/key_recipe.rs @@ -599,7 +599,7 @@ fn resolve_struct_field<'a>( "Invalid negative struct index {struct_index} in key recipe part" ))); } - let list = current.try_as_list().ok_or_else(|| { + let list = current.as_list().ok_or_else(|| { internal_error("Expected Struct ListValue for struct parameter traversal") })?; current = list.get(struct_index as usize).ok_or_else(|| { @@ -736,14 +736,14 @@ fn encode_not_null_marker( /// Evaluates a boolean column value (`BOOL`). fn encode_bool_part(buffer: &mut Vec, value: &Value, decreasing: bool) -> Result<()> { let boolean_value = value - .try_as_bool() + .as_bool() .ok_or_else(|| internal_error("Type mismatch: expected Bool value for BOOL column"))?; append_bool_ordered(buffer, boolean_value, decreasing) } /// Evaluates an integer or enum column value (`INT64` or `ENUM`). fn encode_int64_part(buffer: &mut Vec, value: &Value, decreasing: bool) -> Result<()> { - let string_value = value.try_as_string().ok_or_else(|| { + let string_value = value.as_str().ok_or_else(|| { internal_error("Type mismatch: expected String value for INT64 or ENUM column") })?; let integer_value = string_value.parse::().map_err(|error| { @@ -756,7 +756,7 @@ fn encode_int64_part(buffer: &mut Vec, value: &Value, decreasing: bool) -> R /// Evaluates a floating-point column value (`FLOAT64`). fn encode_float64_part(buffer: &mut Vec, value: &Value, decreasing: bool) -> Result<()> { - if let Some(string_value) = value.try_as_string() { + if let Some(string_value) = value.as_str() { let number = match string_value { "NaN" => f64::NAN, "Infinity" => f64::INFINITY, @@ -769,7 +769,7 @@ fn encode_float64_part(buffer: &mut Vec, value: &Value, decreasing: bool) -> }; return append_double_ordered(buffer, number, decreasing); } - let number = value.try_as_f64().ok_or_else(|| { + let number = value.as_f64().ok_or_else(|| { internal_error("Type mismatch: expected Number or special String value for FLOAT64 column") })?; append_double_ordered(buffer, number, decreasing) @@ -778,14 +778,14 @@ fn encode_float64_part(buffer: &mut Vec, value: &Value, decreasing: bool) -> /// Evaluates a string column value (`STRING`). fn encode_string_part(buffer: &mut Vec, value: &Value, decreasing: bool) -> Result<()> { let string_value = value - .try_as_string() + .as_str() .ok_or_else(|| internal_error("Type mismatch: expected String value for STRING column"))?; append_string_ordered(buffer, string_value, decreasing) } /// Evaluates a byte array column value (`BYTES`). fn encode_bytes_part(buffer: &mut Vec, value: &Value, decreasing: bool) -> Result<()> { - let string_value = value.try_as_string().ok_or_else(|| { + let string_value = value.as_str().ok_or_else(|| { internal_error("Type mismatch: expected base64 String value for BYTES column") })?; let mut stack_buffer = [0u8; 512]; diff --git a/src/spanner/src/routing/mock_tests.rs b/src/spanner/src/routing/mock_tests.rs index 9c0120f88e..10b7453394 100644 --- a/src/spanner/src/routing/mock_tests.rs +++ b/src/spanner/src/routing/mock_tests.rs @@ -49,7 +49,7 @@ use crate::mutation::Mutation; use crate::omni::{InstanceType, TlsConfig}; use crate::read::ReadRequest; use crate::read_write_transaction::ReadWriteTransaction; -use crate::retry_delay::ProtoRetryInfo; +use crate::retry_delay::{ProtoRetryInfo, extract_status_code_from_error}; use crate::routing::directed_read::select_eligible_tablets_for_directed_read; use crate::routing::key_range_cache::RangeMode; use crate::routing::location_router::RoutingContext; @@ -59,6 +59,7 @@ use gaxi::grpc::tonic::transport::server::TcpIncoming; use gaxi::grpc::tonic::transport::{Certificate, Identity, Server, ServerTlsConfig}; use gaxi::grpc::tonic::{Code as TonicCode, MetadataMap, Response, Status as TonicStatus}; use google_cloud_auth::credentials::anonymous::Builder as Anonymous; +use google_cloud_gax::error::rpc::Code; use google_cloud_gax::retry_policy::NeverRetry; use google_cloud_test_macros::tokio_test_no_panics; use mockall::Sequence; @@ -7016,3 +7017,1175 @@ async fn replay_protected_write_only_transaction_preserves_affinity_across_preco Ok(()) } + +#[tokio_test_no_panics] +async fn streaming_rpc_feedback_success_records_latency_and_repairs_cooldown() -> anyhow::Result<()> +{ + let mut mock_gateway = create_base_mock(); + let mut mock_tablet = create_base_mock(); + + let tablet_called = Arc::new(AtomicUsize::new(0)); + let tablet_called_clone = Arc::clone(&tablet_called); + + mock_tablet + .expect_execute_streaming_sql() + .times(3) + .returning(move |_| { + tablet_called_clone.fetch_add(1, Ordering::SeqCst); + let (sender, receiver) = mpsc::channel(1); + let partial_result_set = sample_int64_partial_result_set("SingerId", "42", None); + sender + .try_send(Ok(partial_result_set)) + .expect("should send streaming sql partial result set"); + Ok(Response::from(receiver)) + }); + + let (tablet_address, _tablet_server) = start("127.0.0.1:0", mock_tablet).await?; + + let tablet_address_for_update = tablet_address.clone(); + mock_gateway + .expect_execute_sql() + .times(1) + .returning(move |request| { + let operation_uid = request + .get_ref() + .routing_hint + .as_ref() + .map(|hint| hint.operation_uid) + .unwrap_or(1); + + let cache_update = sample_query_mock_cache_update( + 5555, + operation_uid, + 9001, + &tablet_address_for_update, + ); + Ok(Response::new(mock_v1::ResultSet { + cache_update: Some(cache_update), + ..Default::default() + })) + }); + + let (database_client, _spanner, _gateway_server) = + setup_mock_database_client(mock_gateway).await?; + + let router = database_client + .location_router() + .expect("location router must be present"); + + // Cold-start query populates recipe and range cache via gateway + let statement = Statement::builder("SELECT * FROM Users WHERE account_id = @account_id") + .add_param("account_id", 42i64) + .build(); + let _ = database_client + .execute_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .await?; + + // Pre-warm tablet connection in cache + let client_config = database_client + .cache_updater() + .expect("cache updater must be present") + .client_config(); + let _ = router + .connection_cache() + .get(&tablet_address, client_config) + .await?; + + // Record an expired failure in cooldown tracker (30s in the past so cooldown is over, but failure tier is 1) + let past = Instant::now() - Duration::from_secs(30); + router + .cooldown_tracker() + .record_failure_at(&tablet_address, past); + assert!( + !router.cooldown_tracker().is_empty(), + "cooldown tracker should contain entry for tablet address before repair" + ); + + assert!( + !router + .latency_registry() + .has_score(Some(router.database_scope()), 9001, &tablet_address), + "latency registry should have no score for tablet address before direct streaming execution" + ); + + // Execute 3 successful direct streaming SQL RPCs to the tablet and consume streams to EOF + for _ in 0..3 { + let mut stream = database_client + .execute_streaming_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .send() + .await?; + while let Some(message) = stream.next_message().await { + let _ = message?; + } + } + + assert_eq!( + tablet_called.load(Ordering::SeqCst), + 3, + "mock tablet must have received 3 direct streaming execute_streaming_sql calls" + ); + + // Verify latency was recorded + assert!( + router + .latency_registry() + .has_score(Some(router.database_scope()), 9001, &tablet_address), + "latency registry must have recorded latency score for tablet address after successful streaming execution" + ); + let cost = router.latency_registry().get_selection_cost( + Some(router.database_scope()), + 9001, + 0, + &tablet_address, + ); + assert!( + cost > 0.0, + "selection cost for tablet address must be greater than zero after latency recording" + ); + + // Verify that 3 consecutive successes repaired the failure tier and pruned the entry + assert!( + router.cooldown_tracker().is_empty(), + "cooldown tracker must be empty after 3 successful streaming RPCs repaired failure tier of expired entry" + ); + + Ok(()) +} + +#[tokio_test_no_panics] +async fn streaming_rpc_feedback_resource_exhausted_places_endpoint_on_cooldown_and_penalizes_latency() +-> anyhow::Result<()> { + let mut mock_gateway = create_base_mock(); + let mut mock_tablet = create_base_mock(); + + let tablet_called = Arc::new(AtomicBool::new(false)); + let tablet_called_clone = Arc::clone(&tablet_called); + + mock_tablet + .expect_execute_streaming_sql() + .times(1) + .returning(move |_| { + tablet_called_clone.store(true, Ordering::SeqCst); + Err(TonicStatus::resource_exhausted("tablet overloaded")) + }); + + let (tablet_address, _tablet_server) = start("127.0.0.1:0", mock_tablet).await?; + + let fallback_called = Arc::new(AtomicBool::new(false)); + let fallback_called_clone = Arc::clone(&fallback_called); + + let tablet_address_for_update = tablet_address.clone(); + mock_gateway + .expect_execute_sql() + .times(1) + .returning(move |request| { + let operation_uid = request + .get_ref() + .routing_hint + .as_ref() + .map(|hint| hint.operation_uid) + .unwrap_or(1); + + let cache_update = sample_query_mock_cache_update( + 5555, + operation_uid, + 9001, + &tablet_address_for_update, + ); + Ok(Response::new(mock_v1::ResultSet { + cache_update: Some(cache_update), + ..Default::default() + })) + }); + + mock_gateway + .expect_execute_streaming_sql() + .times(1) + .returning(move |_| { + fallback_called_clone.store(true, Ordering::SeqCst); + let (sender, receiver) = mpsc::channel(1); + let partial_result_set = sample_int64_partial_result_set("SingerId", "42", None); + sender + .try_send(Ok(partial_result_set)) + .expect("should send streaming sql fallback result set"); + Ok(Response::from(receiver)) + }); + + let (database_client, _spanner, _gateway_server) = + setup_mock_database_client(mock_gateway).await?; + + let router = database_client + .location_router() + .expect("location router must be present"); + + // Cold-start query populates recipe and range cache via gateway + let statement = Statement::builder("SELECT * FROM Users WHERE account_id = @account_id") + .add_param("account_id", 42i64) + .build(); + let _ = database_client + .execute_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .await?; + + // Pre-warm tablet connection in cache + let client_config = database_client + .cache_updater() + .expect("cache updater must be present") + .client_config(); + let _ = router + .connection_cache() + .get(&tablet_address, client_config) + .await?; + + assert!( + !router.cooldown_tracker().is_cooling_down(&tablet_address), + "tablet address must not be on cooldown initially" + ); + + // Direct streaming execution to tablet fails with ResourceExhausted + let stream_result = database_client + .execute_streaming_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .send() + .await; + + assert!( + stream_result.is_err(), + "execute_streaming_sql to overloaded tablet must return an error" + ); + assert!( + tablet_called.load(Ordering::SeqCst), + "mock tablet must have received direct streaming execute_streaming_sql call" + ); + + // Verify tablet was placed on cooldown and latency penalty was recorded + assert!( + router.cooldown_tracker().is_cooling_down(&tablet_address), + "tablet address must be on active cooldown after ResourceExhausted error" + ); + assert!( + router + .latency_registry() + .has_score(Some(router.database_scope()), 9001, &tablet_address), + "latency registry must have recorded error penalty for tablet address" + ); + + // Subsequent streaming execution falls back to gateway because tablet is on cooldown + let mut fallback_stream = database_client + .execute_streaming_sql(statement.into_request(), RequestOptions::default(), None) + .send() + .await?; + while let Some(message) = fallback_stream.next_message().await { + let _ = message?; + } + + assert!( + fallback_called.load(Ordering::SeqCst), + "gateway fallback must have received the streaming execution while tablet is on cooldown" + ); + + Ok(()) +} + +#[tokio_test_no_panics] +async fn streaming_rpc_feedback_unavailable_places_endpoint_on_cooldown() -> anyhow::Result<()> { + let mut mock_gateway = create_base_mock(); + let mut mock_tablet = create_base_mock(); + + let tablet_called = Arc::new(AtomicBool::new(false)); + let tablet_called_clone = Arc::clone(&tablet_called); + + mock_tablet + .expect_execute_streaming_sql() + .times(1) + .returning(move |_| { + tablet_called_clone.store(true, Ordering::SeqCst); + Err(TonicStatus::unavailable("tablet node restarting")) + }); + + let (tablet_address, _tablet_server) = start("127.0.0.1:0", mock_tablet).await?; + + let tablet_address_for_update = tablet_address.clone(); + mock_gateway + .expect_execute_sql() + .times(1) + .returning(move |request| { + let operation_uid = request + .get_ref() + .routing_hint + .as_ref() + .map(|hint| hint.operation_uid) + .unwrap_or(1); + + let cache_update = sample_query_mock_cache_update( + 5555, + operation_uid, + 9001, + &tablet_address_for_update, + ); + Ok(Response::new(mock_v1::ResultSet { + cache_update: Some(cache_update), + ..Default::default() + })) + }); + + let (database_client, _spanner, _gateway_server) = + setup_mock_database_client(mock_gateway).await?; + + let router = database_client + .location_router() + .expect("location router must be present"); + + // Cold-start query populates recipe and range cache via gateway + let statement = Statement::builder("SELECT * FROM Users WHERE account_id = @account_id") + .add_param("account_id", 42i64) + .build(); + let _ = database_client + .execute_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .await?; + + // Pre-warm tablet connection in cache + let client_config = database_client + .cache_updater() + .expect("cache updater must be present") + .client_config(); + let _ = router + .connection_cache() + .get(&tablet_address, client_config) + .await?; + + assert!( + !router.cooldown_tracker().is_cooling_down(&tablet_address), + "tablet address must not be cooling down initially" + ); + + // Direct streaming execution to tablet fails with Unavailable + let stream_result = database_client + .execute_streaming_sql(statement.into_request(), RequestOptions::default(), None) + .send() + .await; + + assert!( + stream_result.is_err(), + "streaming SQL to unavailable tablet must return an error" + ); + assert!( + tablet_called.load(Ordering::SeqCst), + "mock tablet must have received direct streaming execute_streaming_sql call" + ); + + // Verify tablet was placed on cooldown and latency penalty was recorded + assert!( + router.cooldown_tracker().is_cooling_down(&tablet_address), + "tablet address must be on active cooldown after Unavailable error" + ); + assert!( + router + .latency_registry() + .has_score(Some(router.database_scope()), 9001, &tablet_address), + "latency registry must have recorded error penalty for tablet address" + ); + + Ok(()) +} + +#[tokio_test_no_panics] +async fn streaming_rpc_tracks_active_request_guard_during_stream_and_releases_on_drop() +-> anyhow::Result<()> { + let mut mock_gateway = create_base_mock(); + let mut mock_tablet = create_base_mock(); + + let (resume_sender, resume_receiver) = mpsc::channel::<()>(1); + let resume_receiver = Arc::new(tokio::sync::Mutex::new(resume_receiver)); + let tablet_called = Arc::new(AtomicBool::new(false)); + let tablet_called_clone = Arc::clone(&tablet_called); + + let resume_receiver_clone = Arc::clone(&resume_receiver); + mock_tablet + .expect_execute_streaming_sql() + .times(1) + .returning(move |_| { + tablet_called_clone.store(true, Ordering::SeqCst); + let (sender, receiver) = mpsc::channel(2); + let partial_result_set = sample_int64_partial_result_set("SingerId", "42", None); + sender + .try_send(Ok(partial_result_set)) + .expect("should send first chunk"); + let resume_receiver = Arc::clone(&resume_receiver_clone); + tokio::spawn(async move { + let mut guard = resume_receiver.lock().await; + let _ = guard.recv().await; + drop(sender); + }); + Ok(Response::from(receiver)) + }); + + let (tablet_address, _tablet_server) = start("127.0.0.1:0", mock_tablet).await?; + + let tablet_address_for_update = tablet_address.clone(); + mock_gateway + .expect_execute_sql() + .times(1) + .returning(move |request| { + let operation_uid = request + .get_ref() + .routing_hint + .as_ref() + .map(|hint| hint.operation_uid) + .unwrap_or(1); + + let cache_update = sample_query_mock_cache_update( + 5555, + operation_uid, + 9001, + &tablet_address_for_update, + ); + Ok(Response::new(mock_v1::ResultSet { + cache_update: Some(cache_update), + ..Default::default() + })) + }); + + let (database_client, _spanner, _gateway_server) = + setup_mock_database_client(mock_gateway).await?; + + let router = database_client + .location_router() + .expect("location router must be present"); + + // Cold-start query populates recipe and range cache via gateway + let statement = Statement::builder("SELECT * FROM Users WHERE account_id = @account_id") + .add_param("account_id", 42i64) + .build(); + let _ = database_client + .execute_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .await?; + + // Pre-warm tablet connection in cache + let client_config = database_client + .cache_updater() + .expect("cache updater must be present") + .client_config(); + let connection = router + .connection_cache() + .get(&tablet_address, client_config) + .await?; + + assert_eq!( + connection.active_request_count(), + 0, + "active requests on direct connection must be 0 prior to streaming RPC" + ); + + // Start streaming RPC + let mut stream = database_client + .execute_streaming_sql(statement.into_request(), RequestOptions::default(), None) + .send() + .await?; + + // Read first message + let first_message = stream.next_message().await; + assert!( + first_message.is_some(), + "first message from stream must be received" + ); + + // While stream is alive, active request count on connection must be 1 + assert_eq!( + connection.active_request_count(), + 1, + "active request count on direct connection must be 1 while stream is open" + ); + + // Drop the stream before completing + drop(stream); + let _ = resume_sender.send(()).await; + + // After dropping stream, active request count must immediately return to 0 + assert_eq!( + connection.active_request_count(), + 0, + "active request count on direct connection must return to 0 after stream is dropped" + ); + + Ok(()) +} + +#[tokio_test_no_panics] +async fn streaming_read_feedback_success_records_latency_and_repairs_cooldown() -> anyhow::Result<()> +{ + let mock_gateway = create_base_mock(); + let (gateway_address, _gateway_server) = start("127.0.0.1:0", mock_gateway).await?; + + let mut mock_tablet_leader = create_base_mock(); + let leader_called = Arc::new(AtomicUsize::new(0)); + let leader_called_clone = Arc::clone(&leader_called); + + mock_tablet_leader + .expect_streaming_read() + .times(3) + .returning(move |_| { + leader_called_clone.fetch_add(1, Ordering::SeqCst); + let (sender, receiver) = mpsc::channel(1); + let partial_result_set = sample_int64_partial_result_set("SingerId", "42", None); + sender + .try_send(Ok(partial_result_set)) + .expect("should send streaming read partial result set"); + Ok(Response::from(receiver)) + }); + let (tablet_leader_address, _tablet_leader_server) = + start("127.0.0.1:0", mock_tablet_leader).await?; + + let mock_tablet_follower = create_base_mock(); + let (tablet_follower_address, _tablet_follower_server) = + start("127.0.0.1:0", mock_tablet_follower).await?; + + let spanner = Spanner::builder() + .with_endpoint(gateway_address.clone()) + .with_instance_type(InstanceType::Omni) + .with_credentials(Anonymous::new().build()) + .build() + .await?; + + let database_client = spanner + .database_client("projects/test-project/instances/test-instance/databases/test-db") + .with_location_aware_routing(true) + .build() + .await?; + + let mut update = sample_model_cache_update( + 10103, + 8003, + &tablet_leader_address, + &tablet_follower_address, + ); + update.range = vec![ModelRange { + start_key: Bytes::from_static(b""), + limit_key: Bytes::from_static(b""), + group_uid: 8003, + split_id: 8003, + generation: Bytes::from_static(b"gen_1"), + _unknown_fields: Default::default(), + }]; + update.key_recipes = Some(RecipeList { + schema_generation: Bytes::from_static(b"schema_v1"), + recipe: vec![KeyRecipe { + target: Some(Target::TableName("Singers".to_string())), + part: vec![ + Part { + tag: 100, + order: Order::Unspecified, + null_order: NullOrder::Unspecified, + r#type: None, + struct_identifiers: vec![], + value_type: None, + _unknown_fields: Default::default(), + }, + Part { + tag: 0, + order: Order::Ascending, + null_order: NullOrder::NullsFirst, + r#type: Some(Type { + code: TypeCode::Int64, + ..Default::default() + }), + struct_identifiers: vec![], + value_type: Some(ValueType::Identifier("SingerId".to_string())), + _unknown_fields: Default::default(), + }, + ], + _unknown_fields: Default::default(), + }], + _unknown_fields: Default::default(), + }); + database_client.observe_cache_update(Some(update)); + + let router = database_client + .location_router() + .expect("location router must be present"); + let client_config = database_client + .cache_updater() + .expect("cache updater present") + .client_config(); + let _ = router + .connection_cache() + .get(&tablet_leader_address, client_config) + .await?; + + // Record an expired failure in cooldown tracker (30s in the past so cooldown is over, but failure tier is 1) + let past = Instant::now() - Duration::from_secs(30); + router + .cooldown_tracker() + .record_failure_at(&tablet_leader_address, past); + assert!( + !router.cooldown_tracker().is_empty(), + "cooldown tracker should contain entry for tablet leader address before repair" + ); + + let read_request = ReadRequest::builder("Singers", vec!["SingerId"]) + .with_keys(KeySet::from(key![42i64])) + .build(); + + // Execute 3 direct streaming reads to the tablet leader and consume streams to EOF + for _ in 0..3 { + let mut stream = database_client + .streaming_read( + read_request.clone().into_request(), + RequestOptions::default(), + None, + ) + .send() + .await?; + while let Some(message) = stream.next_message().await { + let _ = message?; + } + } + + assert_eq!( + leader_called.load(Ordering::SeqCst), + 3, + "mock tablet leader must have received 3 direct streaming_read calls" + ); + + // Verify latency was recorded for group 8003 + assert!( + router.latency_registry().has_score( + Some(router.database_scope()), + 8003, + &tablet_leader_address + ), + "latency registry must have recorded latency score for tablet leader address after successful streaming read execution" + ); + + // Verify that 3 consecutive successes repaired the failure tier and pruned the entry + assert!( + router.cooldown_tracker().is_empty(), + "cooldown tracker must be empty after 3 successful streaming read RPCs repaired failure tier of expired entry" + ); + + Ok(()) +} + +#[tokio_test_no_panics] +async fn streaming_rpc_midstream_error_places_endpoint_on_cooldown_and_penalizes_latency() +-> anyhow::Result<()> { + let mut mock_gateway = create_base_mock(); + let mut mock_tablet = create_base_mock(); + + let tablet_called = Arc::new(AtomicBool::new(false)); + let tablet_called_clone = Arc::clone(&tablet_called); + + mock_tablet + .expect_execute_streaming_sql() + .times(1) + .returning(move |_| { + tablet_called_clone.store(true, Ordering::SeqCst); + let (sender, receiver) = mpsc::channel(2); + let partial_result_set = sample_int64_partial_result_set("SingerId", "42", None); + sender + .try_send(Ok(partial_result_set)) + .expect("should send first chunk"); + sender + .try_send(Err(TonicStatus::unavailable( + "tablet replica crashed mid-stream", + ))) + .expect("should send mid-stream error"); + Ok(Response::from(receiver)) + }); + + let (tablet_address, _tablet_server) = start("127.0.0.1:0", mock_tablet).await?; + + let tablet_address_for_update = tablet_address.clone(); + mock_gateway + .expect_execute_sql() + .times(1) + .returning(move |request| { + let operation_uid = request + .get_ref() + .routing_hint + .as_ref() + .map(|hint| hint.operation_uid) + .unwrap_or(1); + + let cache_update = sample_query_mock_cache_update( + 5555, + operation_uid, + 9001, + &tablet_address_for_update, + ); + Ok(Response::new(mock_v1::ResultSet { + cache_update: Some(cache_update), + ..Default::default() + })) + }); + + let (database_client, _spanner, _gateway_server) = + setup_mock_database_client(mock_gateway).await?; + + let router = database_client + .location_router() + .expect("location router must be present"); + + let statement = Statement::builder("SELECT * FROM Users WHERE account_id = @account_id") + .add_param("account_id", 42i64) + .build(); + let _ = database_client + .execute_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .await?; + + let client_config = database_client + .cache_updater() + .expect("cache updater must be present") + .client_config(); + let connection = router + .connection_cache() + .get(&tablet_address, client_config) + .await?; + + assert!( + !router.cooldown_tracker().is_cooling_down(&tablet_address), + "tablet should not be on cooldown initially" + ); + + let mut stream = database_client + .execute_streaming_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .send() + .await?; + + // Initial message succeeds: first response latency is recorded + let first_message = stream.next_message().await; + assert!( + first_message.is_some(), + "first streaming chunk should be received" + ); + let _ = first_message.expect("first chunk must exist")?; + + assert!( + router + .latency_registry() + .has_score(Some(router.database_scope()), 9001, &tablet_address), + "latency registry must have recorded first-response latency after first chunk" + ); + assert_eq!( + connection.active_request_count(), + 1, + "active request count must be 1 while stream is active" + ); + + // Second message returns mid-stream UNAVAILABLE error + let second_message = stream.next_message().await; + assert!( + second_message.is_some(), + "second message should return mid-stream error" + ); + let error = second_message + .expect("second chunk must exist") + .expect_err("second chunk must be an error"); + assert_eq!( + extract_status_code_from_error(&error), + Some(Code::Unavailable), + "error must be UNAVAILABLE" + ); + + // Verify mid-stream error placed endpoint on cooldown and recorded routing feedback + assert!( + router.cooldown_tracker().is_cooling_down(&tablet_address), + "cooldown tracker must have placed tablet address on cooldown after mid-stream UNAVAILABLE error" + ); + assert!( + router + .latency_registry() + .has_score(Some(router.database_scope()), 9001, &tablet_address), + "latency registry must have recorded first-response score for tablet address" + ); + assert_eq!( + connection.active_request_count(), + 0, + "active request count must drop to 0 after error releases lifetime guard" + ); + + Ok(()) +} + +#[tokio_test_no_panics] +async fn streaming_rpc_early_client_drop_records_latency_and_repairs_cooldown() -> anyhow::Result<()> +{ + let mut mock_gateway = create_base_mock(); + let mut mock_tablet = create_base_mock(); + + let tablet_called = Arc::new(AtomicUsize::new(0)); + let tablet_called_clone = Arc::clone(&tablet_called); + + mock_tablet + .expect_execute_streaming_sql() + .times(3) + .returning(move |_| { + tablet_called_clone.fetch_add(1, Ordering::SeqCst); + let (sender, receiver) = mpsc::channel(3); + for id in 1..=3 { + let partial_result_set = + sample_int64_partial_result_set("SingerId", &id.to_string(), None); + sender + .try_send(Ok(partial_result_set)) + .expect("should send chunk"); + } + Ok(Response::from(receiver)) + }); + + let (tablet_address, _tablet_server) = start("127.0.0.1:0", mock_tablet).await?; + + let tablet_address_for_update = tablet_address.clone(); + mock_gateway + .expect_execute_sql() + .times(1) + .returning(move |request| { + let operation_uid = request + .get_ref() + .routing_hint + .as_ref() + .map(|hint| hint.operation_uid) + .unwrap_or(1); + + let cache_update = sample_query_mock_cache_update( + 5555, + operation_uid, + 9001, + &tablet_address_for_update, + ); + Ok(Response::new(mock_v1::ResultSet { + cache_update: Some(cache_update), + ..Default::default() + })) + }); + + let (database_client, _spanner, _gateway_server) = + setup_mock_database_client(mock_gateway).await?; + + let router = database_client + .location_router() + .expect("location router must be present"); + + let statement = Statement::builder("SELECT * FROM Users WHERE account_id = @account_id") + .add_param("account_id", 42i64) + .build(); + let _ = database_client + .execute_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .await?; + + let client_config = database_client + .cache_updater() + .expect("cache updater must be present") + .client_config(); + let connection = router + .connection_cache() + .get(&tablet_address, client_config) + .await?; + + // Record an expired failure (30s in the past) in cooldown tracker to simulate recovery state + let past = Instant::now() - Duration::from_secs(30); + router + .cooldown_tracker() + .record_failure_at(&tablet_address, past); + assert!( + !router.cooldown_tracker().is_empty(), + "cooldown tracker should contain entry for tablet address before early drop" + ); + + // Execute 3 streaming RPCs, consuming only the first chunk and dropping each stream early + for _ in 0..3 { + let mut stream = database_client + .execute_streaming_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .send() + .await?; + + // Consume ONLY the first chunk + let first_message = stream.next_message().await; + assert!(first_message.is_some(), "first chunk should be received"); + let _ = first_message.expect("first chunk must exist")?; + + assert_eq!( + connection.active_request_count(), + 1, + "active request count must be 1 while stream is retained" + ); + + // Client drops the stream early before reading to EOF + drop(stream); + + assert_eq!( + connection.active_request_count(), + 0, + "active request count must drop to 0 after dropping stream" + ); + } + + assert_eq!( + tablet_called.load(Ordering::SeqCst), + 3, + "mock tablet must have received 3 streaming calls" + ); + + // Verify first-response latency was recorded + assert!( + router + .latency_registry() + .has_score(Some(router.database_scope()), 9001, &tablet_address), + "latency registry must have recorded latency score for tablet address despite early drop" + ); + + // Verify that 3 successful first responses repaired failure tier in cooldown tracker + assert!( + router.cooldown_tracker().is_empty(), + "cooldown tracker must have pruned repaired entry on 3 successful first responses despite early drop" + ); + + Ok(()) +} + +#[tokio_test_no_panics] +async fn streaming_rpc_error_with_retry_delay_hint_applies_server_cooldown_floor() +-> anyhow::Result<()> { + let mut mock_gateway = create_base_mock(); + let mut mock_tablet = create_base_mock(); + + let tablet_called = Arc::new(AtomicBool::new(false)); + let tablet_called_clone = Arc::clone(&tablet_called); + + let retry_info_bytes = encode_test_retry_info(2, 0); + + mock_tablet + .expect_execute_streaming_sql() + .times(1) + .returning(move |_| { + tablet_called_clone.store(true, Ordering::SeqCst); + Err(TonicStatus::with_details_and_metadata( + TonicCode::ResourceExhausted, + "tablet overloaded with resource exhaustion", + retry_info_bytes.clone().into(), + MetadataMap::new(), + )) + }); + + let (tablet_address, _tablet_server) = start("127.0.0.1:0", mock_tablet).await?; + + let tablet_address_for_update = tablet_address.clone(); + mock_gateway + .expect_execute_sql() + .times(1) + .returning(move |request| { + let operation_uid = request + .get_ref() + .routing_hint + .as_ref() + .map(|hint| hint.operation_uid) + .unwrap_or(1); + + let cache_update = sample_query_mock_cache_update( + 5555, + operation_uid, + 9001, + &tablet_address_for_update, + ); + Ok(Response::new(mock_v1::ResultSet { + cache_update: Some(cache_update), + ..Default::default() + })) + }); + + let (database_client, _spanner, _gateway_server) = + setup_mock_database_client(mock_gateway).await?; + + let router = database_client + .location_router() + .expect("location router must be present"); + + let statement = Statement::builder("SELECT * FROM Users WHERE account_id = @account_id") + .add_param("account_id", 42i64) + .build(); + let _ = database_client + .execute_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .await?; + + let client_config = database_client + .cache_updater() + .expect("cache updater must be present") + .client_config(); + let _ = router + .connection_cache() + .get(&tablet_address, client_config) + .await?; + + let result = database_client + .execute_streaming_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .send() + .await; + + assert!(result.is_err(), "execute_streaming_sql must return error"); + + // The 2-second retry delay plus progressive floor/jitter means cooldown is at least 2 seconds + let now = Instant::now(); + assert!( + router + .cooldown_tracker() + .is_cooling_down_at(&tablet_address, now + Duration::from_millis(1500)), + "tablet address must be on active cooldown at +1.5s respecting the 2s server retry hint" + ); + assert!( + !router + .cooldown_tracker() + .is_cooling_down_at(&tablet_address, now + Duration::from_secs(4)), + "tablet address cooldown must expire after the server retry hint window has passed" + ); + + Ok(()) +} + +#[tokio_test_no_panics] +async fn result_set_streaming_records_routing_feedback_and_releases_guard() -> anyhow::Result<()> { + let mut mock_gateway = create_base_mock(); + let mut mock_tablet = create_base_mock(); + + let tablet_called = Arc::new(AtomicBool::new(false)); + let tablet_called_clone = Arc::clone(&tablet_called); + + mock_tablet + .expect_execute_streaming_sql() + .times(1) + .returning(move |_| { + tablet_called_clone.store(true, Ordering::SeqCst); + let (sender, receiver) = mpsc::channel(1); + let partial_result_set = sample_int64_partial_result_set("SingerId", "42", None); + sender + .try_send(Ok(partial_result_set)) + .expect("should send streaming sql partial result set"); + drop(sender); + Ok(Response::from(receiver)) + }); + + let (tablet_address, _tablet_server) = start("127.0.0.1:0", mock_tablet).await?; + + let tablet_address_for_update = tablet_address.clone(); + mock_gateway + .expect_execute_sql() + .times(1) + .returning(move |request| { + let operation_uid = request + .get_ref() + .routing_hint + .as_ref() + .map(|hint| hint.operation_uid) + .unwrap_or(1); + + let cache_update = sample_query_mock_cache_update( + 5555, + operation_uid, + 9001, + &tablet_address_for_update, + ); + Ok(Response::new(mock_v1::ResultSet { + cache_update: Some(cache_update), + ..Default::default() + })) + }); + + let (database_client, _spanner, _gateway_server) = + setup_mock_database_client(mock_gateway).await?; + + let router = database_client + .location_router() + .expect("location router must be present"); + + let statement = Statement::builder("SELECT * FROM Users WHERE account_id = @account_id") + .add_param("account_id", 42i64) + .build(); + let _ = database_client + .execute_sql( + statement.clone().into_request(), + RequestOptions::default(), + None, + ) + .await?; + + let client_config = database_client + .cache_updater() + .expect("cache updater must be present") + .client_config(); + let connection = router + .connection_cache() + .get(&tablet_address, client_config) + .await?; + + // Execute via SingleUseReadOnlyTransaction returning ResultSet + let transaction = database_client.single_use().build(); + let mut result_set = transaction.execute_query(statement).await?; + + let row = result_set.next().await; + assert!(row.is_some(), "result set should yield a row"); + let row = row.expect("row must exist")?; + assert_eq!(row.raw_values().len(), 1, "row must have 1 column"); + + assert!( + router + .latency_registry() + .has_score(Some(router.database_scope()), 9001, &tablet_address), + "latency registry must have recorded latency score for tablet address through ResultSet" + ); + + // Drop result set and allow background drain task to finish + drop(result_set); + tokio::task::yield_now().await; + + assert_eq!( + connection.active_request_count(), + 0, + "active request count must drop to 0 after dropping ResultSet" + ); + + Ok(()) +} diff --git a/src/spanner/src/routing/server_connection.rs b/src/spanner/src/routing/server_connection.rs index 0153ef9e27..574e291973 100644 --- a/src/spanner/src/routing/server_connection.rs +++ b/src/spanner/src/routing/server_connection.rs @@ -47,6 +47,7 @@ struct Inner { /// RAII guard that decrements the active request count of a [`ServerConnection`] when dropped. #[must_use = "if unused the request count will decrement immediately"] +#[derive(Debug)] pub(crate) struct ActiveRequestGuard { inner: Arc, } diff --git a/src/spanner/src/routing/temporal.rs b/src/spanner/src/routing/temporal.rs index 7b879fb645..880145ff11 100644 --- a/src/spanner/src/routing/temporal.rs +++ b/src/spanner/src/routing/temporal.rs @@ -79,7 +79,7 @@ pub(crate) fn encode_date_part( value: &Value, decreasing: bool, ) -> Result<()> { - let date_string = value.try_as_string().ok_or_else(|| { + let date_string = value.as_str().ok_or_else(|| { crate::error::internal_error( "Type mismatch: expected ISO 8601 String value for DATE column", ) @@ -95,7 +95,7 @@ pub(crate) fn encode_timestamp_part( value: &Value, decreasing: bool, ) -> Result<()> { - let timestamp_string = value.try_as_string().ok_or_else(|| { + let timestamp_string = value.as_str().ok_or_else(|| { crate::error::internal_error( "Type mismatch: expected RFC 3339 String value for TIMESTAMP column", ) diff --git a/src/spanner/src/routing/uuid.rs b/src/spanner/src/routing/uuid.rs index ed8f0fc6a4..8ea8509c96 100644 --- a/src/spanner/src/routing/uuid.rs +++ b/src/spanner/src/routing/uuid.rs @@ -93,7 +93,7 @@ pub(crate) fn encode_uuid_part( value: &Value, decreasing: bool, ) -> Result<()> { - let uuid_string = value.try_as_string().ok_or_else(|| { + let uuid_string = value.as_str().ok_or_else(|| { crate::error::internal_error("Type mismatch: expected String value for UUID column") })?; let uuid_bytes = parse_uuid_bytes(uuid_string)?; diff --git a/src/spanner/src/row.rs b/src/spanner/src/row.rs index 36a61b644a..6a3ddaf9d0 100644 --- a/src/spanner/src/row.rs +++ b/src/spanner/src/row.rs @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::error::Error as _; +use std::fmt::{Debug, Display}; + use crate::Error; use crate::Result; use crate::from_value::FromValue; use crate::result_set_metadata::ResultSetMetadata; -use crate::value::{Kind, Value}; -use std::fmt::{Debug, Display}; +use crate::value::Value; /// A row in a query result. #[derive(Clone, Debug, PartialEq)] @@ -37,6 +39,16 @@ pub(crate) mod private { /// A trait for types that can be used to index into a [`Row`]. /// +/// # Example +/// ``` +/// # use google_cloud_spanner::result::{ColumnIndex, Row}; +/// fn print_column_value(row: &Row, index: I) -> Result<(), google_cloud_spanner::Error> { +/// let value: String = row.try_get(index)?; +/// println!("Value: {value}"); +/// Ok(()) +/// } +/// ``` +/// /// This trait is sealed and cannot be implemented for types outside of this crate. pub trait ColumnIndex: private::Sealed + Debug + Display { /// Returns the index of the column in the given row, if it exists. @@ -71,7 +83,7 @@ impl ColumnIndex for &T { } /// Errors that can occur when getting a value from a [`Row`]. -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum RowError { /// The requested column name was not found in the row. @@ -79,7 +91,53 @@ pub enum RowError { ColumnNotFound(String), /// The requested column index was out of range. #[error("Column index out of range: {index} (expected < {len})")] - IndexOutOfRange { index: usize, len: usize }, + IndexOutOfRange { + /// The index that was requested. + index: usize, + /// The total number of columns in the row. + len: usize, + }, +} + +impl RowError { + /// Extracts a `RowError` from a [`google_cloud_spanner::Error`][Error], if present. + /// + /// # Example + /// ``` + /// # use google_cloud_spanner::client::Spanner; + /// # use google_cloud_spanner::error::RowError; + /// # use google_cloud_spanner::statement::Statement; + /// # async fn example(client: Spanner) -> Result<(), google_cloud_spanner::Error> { + /// let db_client = client.database_client("projects/p/instances/i/databases/d").build().await?; + /// let transaction = db_client.single_use().build(); + /// let mut result_set = transaction + /// .execute_query(Statement::builder("SELECT 42 AS id").build()) + /// .await?; + /// + /// if let Some(row) = result_set.next().await { + /// let result: Result = row?.try_get("nonexistent_column"); + /// if let Err(error) = result { + /// if let Some(row_error) = RowError::extract(&error) { + /// match row_error { + /// RowError::ColumnNotFound(column) => println!("Column not found: {column}"), + /// RowError::IndexOutOfRange { index, len } => { + /// println!("Index {index} out of range (length {len})"); + /// } + /// _ => {} + /// } + /// } + /// } + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// Row operations return [`Error`] with a source of [`RowError`] when a column lookup + /// fails or an index is out of bounds. This method downcasts the immediate error source. + pub fn extract(err: &Error) -> Option<&Self> { + err.source() + .and_then(|source| source.downcast_ref::()) + } } impl Row { @@ -118,7 +176,7 @@ impl Row { /// * `Err(Error)` if the column name or index is invalid. pub fn try_is_null(&self, index: I) -> Result { let (_, value) = self.get_value(index)?; - Ok(value.kind() == Kind::Null) + Ok(value.is_null()) } /// Returns true if the value at the specified column name or index is null, panicking on error. @@ -181,8 +239,8 @@ impl Row { /// /// * `Ok(T)` if the value was successfully retrieved and converted to type `T`. /// * `Err(Error)` if: - /// * The column name or index is invalid. - /// * The column value is incompatible with type `T`. + /// * The column name or index is invalid. The underlying [`RowError`] can be extracted using [`RowError::extract`]. + /// * The column value is incompatible with type `T`. The underlying [`ConvertError`][crate::error::ConvertError] can be extracted using [`ConvertError::extract`][crate::error::ConvertError::extract]. pub fn try_get(&self, index: I) -> Result { let (column_index, value) = self.get_value(index)?; let r#type = self @@ -267,6 +325,7 @@ mod tests { #[test] fn auto_traits() { static_assertions::assert_impl_all!(Row: Clone, Debug, PartialEq, Send, Sync); + static_assertions::assert_impl_all!(RowError: Clone, Debug, PartialEq, Eq, Send, Sync); static_assertions::assert_impl_all!(usize: ColumnIndex, Display); static_assertions::assert_impl_all!(&str: ColumnIndex, Display); static_assertions::assert_impl_all!(String: ColumnIndex, Display); @@ -339,44 +398,114 @@ mod tests { }; // Test getting by valid index - assert_eq!(row.get::(0), "hello"); - assert_eq!(row.get::(1), 42); - assert_eq!(row.get::(2), 42.5); + assert_eq!( + row.get::(0), + "hello", + "expected string at index 0" + ); + assert_eq!(row.get::(1), 42, "expected int64 at index 1"); + assert_eq!(row.get::(2), 42.5, "expected float64 at index 2"); assert!( row.get::(3), "expected bool value at index 3 to be true" ); - assert_eq!(row.get::, _>(4), vec![1_u8, 2, 3]); - assert_eq!(row.get::(5), decimal); - assert_eq!(row.get::(6), date); - assert_eq!(row.get::(7), timestamp); - assert_eq!(row.get::(8), 1.23_f32); - assert_eq!(row.get::(9), "{\"key\":\"value\"}"); + assert_eq!( + row.get::, _>(4), + vec![1_u8, 2, 3], + "expected bytes at index 4" + ); + assert_eq!( + row.get::(5), + decimal, + "expected numeric at index 5" + ); + assert_eq!(row.get::(6), date, "expected date at index 6"); + assert_eq!( + row.get::(7), + timestamp, + "expected timestamp at index 7" + ); + assert_eq!( + row.get::(8), + 1.23_f32, + "expected float32 at index 8" + ); + assert_eq!( + row.get::(9), + "{\"key\":\"value\"}", + "expected json at index 9" + ); assert_eq!( row.get::(10), - "123e4567-e89b-12d3-a456-426614174000" + "123e4567-e89b-12d3-a456-426614174000", + "expected uuid at index 10" + ); + assert_eq!( + row.get::(11), + "P1Y2M3D", + "expected interval at index 11" ); - assert_eq!(row.get::(11), "P1Y2M3D"); // Test getting by valid name - assert_eq!(row.get::("col_string"), "hello"); - assert_eq!(row.get::("col_int64"), 42); - assert_eq!(row.get::("col_float64"), 42.5); + assert_eq!( + row.get::("col_string"), + "hello", + "expected col_string by name" + ); + assert_eq!( + row.get::("col_int64"), + 42, + "expected col_int64 by name" + ); + assert_eq!( + row.get::("col_float64"), + 42.5, + "expected col_float64 by name" + ); assert!( row.get::("col_bool"), "expected col_bool to be true" ); - assert_eq!(row.get::, _>("col_bytes"), vec![1_u8, 2, 3]); - assert_eq!(row.get::("col_numeric"), decimal); - assert_eq!(row.get::("col_date"), date); - assert_eq!(row.get::("col_timestamp"), timestamp); - assert_eq!(row.get::("col_float32"), 1.23_f32); - assert_eq!(row.get::("col_json"), "{\"key\":\"value\"}"); + assert_eq!( + row.get::, _>("col_bytes"), + vec![1_u8, 2, 3], + "expected col_bytes by name" + ); + assert_eq!( + row.get::("col_numeric"), + decimal, + "expected col_numeric by name" + ); + assert_eq!( + row.get::("col_date"), + date, + "expected col_date by name" + ); + assert_eq!( + row.get::("col_timestamp"), + timestamp, + "expected col_timestamp by name" + ); + assert_eq!( + row.get::("col_float32"), + 1.23_f32, + "expected col_float32 by name" + ); + assert_eq!( + row.get::("col_json"), + "{\"key\":\"value\"}", + "expected col_json by name" + ); assert_eq!( row.get::("col_uuid"), - "123e4567-e89b-12d3-a456-426614174000" + "123e4567-e89b-12d3-a456-426614174000", + "expected col_uuid by name" + ); + assert_eq!( + row.get::("col_interval"), + "P1Y2M3D", + "expected col_interval by name" ); - assert_eq!(row.get::("col_interval"), "P1Y2M3D"); // Test getting by invalid index assert!( @@ -401,7 +530,11 @@ mod tests { ); // int64 is encoded as a string, so getting it as a string is also possible. - assert_eq!(row.get::(1), "42"); + assert_eq!( + row.get::(1), + "42", + "expected int64 converted to string" + ); // Test getting with reference index types (&usize, &&str, &String) and owned String assert_eq!(row.get::(&0), "hello"); @@ -468,6 +601,48 @@ mod tests { ); } + #[test] + fn row_error_extract_and_derives() { + let metadata = ResultSetMetadata { + column_names: Arc::new(vec!["col0".to_string()]), + column_types: Arc::new(vec![types::string()]), + undeclared_parameters: Arc::new(BTreeMap::new()), + }; + let row = Row { + values: vec!["val0".to_value()], + metadata, + }; + + let err_missing = row + .try_get::("nonexistent") + .expect_err("column not found"); + let extracted_missing = RowError::extract(&err_missing).expect("should extract RowError"); + assert_eq!( + *extracted_missing, + RowError::ColumnNotFound("nonexistent".to_string()), + "expected ColumnNotFound variant" + ); + assert_eq!( + extracted_missing.clone(), + *extracted_missing, + "expected cloned RowError to equal original" + ); + assert_eq!( + extracted_missing.to_string(), + "Could not find column: 'nonexistent'", + "expected 'Could not find column: \\'nonexistent\\'' display string" + ); + + let err_out_of_range = row.try_get::(5).expect_err("index out of range"); + let extracted_out_of_range = + RowError::extract(&err_out_of_range).expect("should extract RowError"); + assert_eq!( + *extracted_out_of_range, + RowError::IndexOutOfRange { index: 5, len: 1 }, + "expected IndexOutOfRange variant" + ); + } + #[test] #[should_panic( expected = "failed to retrieve column \"col_invalid\": cannot deserialize the response Could not find column: 'col_invalid'" @@ -645,6 +820,39 @@ mod tests { let _: String = row.get(1); } + #[test] + fn generic_column_index_helper() { + fn fetch_value(row: &Row, index: I) -> Result { + row.try_get(index) + } + + let metadata = ResultSetMetadata { + column_names: Arc::new(vec!["username".to_string()]), + column_types: Arc::new(vec![types::string()]), + undeclared_parameters: Arc::new(BTreeMap::new()), + }; + let row = Row { + values: vec!["alice".to_value()], + metadata, + }; + + let by_name = fetch_value(&row, "username").expect("fetch by string slice"); + assert_eq!(by_name, "alice", "expected value fetched by name"); + + let by_string = fetch_value(&row, "username".to_string()).expect("fetch by String"); + assert_eq!(by_string, "alice", "expected value fetched by owned String"); + + let owned_username = "username".to_string(); + let by_borrowed_string = fetch_value(&row, &owned_username).expect("fetch by &String"); + assert_eq!( + by_borrowed_string, "alice", + "expected value fetched by &String" + ); + + let by_index = fetch_value(&row, 0_usize).expect("fetch by usize"); + assert_eq!(by_index, "alice", "expected value fetched by index"); + } + #[test] #[should_panic( expected = "failed to check if column \"col_invalid\" is null: cannot deserialize the response Could not find column: 'col_invalid'" diff --git a/src/spanner/src/server_streaming/builder.rs b/src/spanner/src/server_streaming/builder.rs index 108edab647..e180c04ae2 100644 --- a/src/spanner/src/server_streaming/builder.rs +++ b/src/spanner/src/server_streaming/builder.rs @@ -31,6 +31,7 @@ use gaxi::grpc::tonic::GrpcMethod; use gaxi::prost::ToProto; use prost::Message; use std::sync::LazyLock; +use std::time::Instant; /// The request builder for [SpannerImpl::execute_streaming_sql][crate::client::SpannerImpl::execute_streaming_sql] calls. #[derive(Debug)] @@ -338,6 +339,7 @@ where }; let path = http::uri::PathAndQuery::from_static(path_str); + let start_time = Instant::now(); let response = match grpc_client .server_streaming( extensions, @@ -349,12 +351,15 @@ where ) .await { - Ok(response) => response, + Ok(response) => { + if let Some(guard) = &lifetime_guard { + guard.record_first_response(start_time.elapsed()); + } + response + } Err(err) => { - if let Some(guard) = lifetime_guard - && let Some(status) = err.status() - { - guard.record_error_code(status.code); + if let Some(guard) = lifetime_guard { + guard.record_error(&err); } return Err(err); } @@ -369,14 +374,19 @@ mod tests { use super::*; use crate::client::Spanner; use crate::server_streaming::stream::StreamGuard; + use gaxi::grpc::tonic::Response; use gaxi::grpc::tonic::Status; use google_cloud_auth::credentials::anonymous::Builder as Anonymous; use google_cloud_gax::error::rpc::Code; use google_cloud_gax::options::RequestOptions; use google_cloud_test_macros::tokio_test_no_panics; + use spanner_grpc_mock::MockSpanner; + use spanner_grpc_mock::google::spanner::v1 as mock_v1; use std::fmt::Debug; use std::sync::Arc; use std::sync::Mutex; + use std::time::Duration; + use tokio::sync::mpsc::channel; use tokio::task::JoinHandle; #[test] @@ -399,7 +409,7 @@ mod tests { } async fn create_test_grpc_client_with_mock( - mock: spanner_grpc_mock::MockSpanner, + mock: MockSpanner, ) -> (gaxi::grpc::Client, JoinHandle<()>) { let (address, server) = spanner_grpc_mock::start("0.0.0.0:0", mock) .await @@ -421,7 +431,7 @@ mod tests { } async fn create_test_grpc_client() -> (gaxi::grpc::Client, JoinHandle<()>) { - create_test_grpc_client_with_mock(spanner_grpc_mock::MockSpanner::new()).await + create_test_grpc_client_with_mock(MockSpanner::new()).await } #[tokio_test_no_panics] @@ -693,4 +703,52 @@ mod tests { "Initial handshake failure without lifetime guard must return Err" ); } + + #[derive(Debug, Default)] + struct TestLatencyGuard { + recorded_latency: Arc>>, + } + + impl StreamGuard for TestLatencyGuard { + fn record_first_response(&self, latency: Duration) { + *self.recorded_latency.lock().expect("lock poisoned") = Some(latency); + } + } + + #[tokio_test_no_panics] + async fn make_server_streaming_request_records_first_response_latency_on_success() { + let mut mock = MockSpanner::new(); + mock.expect_execute_streaming_sql().returning(|_| { + let (sender, receiver) = channel(1); + let _ = sender.try_send(Ok(mock_v1::PartialResultSet::default())); + Ok(Response::from(receiver)) + }); + let (grpc_client, _server) = create_test_grpc_client_with_mock(mock).await; + let recorded_latency = Arc::new(Mutex::new(None)); + let guard = Arc::new(TestLatencyGuard { + recorded_latency: Arc::clone(&recorded_latency), + }); + + let result = + make_server_streaming_request::( + &grpc_client, + mock_v1::ExecuteSqlRequest::default(), + RequestOptions::default(), + "ExecuteStreamingSql", + "/google.spanner.v1.Spanner/ExecuteStreamingSql", + "session=test", + Some(guard), + ) + .await; + + assert!( + result.is_ok(), + "make_server_streaming_request must succeed on 200 response" + ); + let latency = *recorded_latency.lock().expect("lock poisoned"); + assert!( + latency.is_some(), + "first response latency must be recorded on successful connection" + ); + } } diff --git a/src/spanner/src/server_streaming/stream.rs b/src/spanner/src/server_streaming/stream.rs index ddb6dd86a0..2372fc1e0a 100644 --- a/src/spanner/src/server_streaming/stream.rs +++ b/src/spanner/src/server_streaming/stream.rs @@ -12,20 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::Result; use crate::google::spanner::v1::BatchWriteResponse; use crate::google::spanner::v1::CacheUpdate as ProtoCacheUpdate; use crate::google::spanner::v1::PartialResultSet; +use crate::retry_delay::extract_status_code_from_error; +use crate::{Error, Result}; use gaxi::grpc::from_status::to_gax_error; use gaxi::grpc::tonic::Streaming; use google_cloud_gax::error::rpc::Code; use http::HeaderMap; use std::fmt::{Debug, Formatter, Result as FmtResult}; use std::sync::Arc; +use std::time::Duration; -/// Trait for stream lifetime drop guards capable of recording RPC error codes for dynamic channel pooling. +/// Trait for stream lifetime drop guards capable of recording RPC error codes for dynamic channel pooling +/// and routing feedback for location-aware routing. pub(crate) trait StreamGuard: Debug + Send + Sync + 'static { - fn record_error_code(&self, code: Code); + fn record_error_code(&self, _code: Code) {} + + fn record_error(&self, error: &Error) { + if let Some(code) = extract_status_code_from_error(error) { + self.record_error_code(code); + } + } + + fn record_first_response(&self, _latency: Duration) {} + + fn record_success(&self) {} } /// Type alias for stream lifetime drop guards. @@ -86,16 +99,16 @@ impl SpannerServerStream { let message = match self.inner.message().await.map_err(to_gax_error).transpose() { Some(Ok(message)) => message, Some(Err(err)) => { - if let Some(guard) = self.lifetime_guard.take() - && let Some(status) = err.status() - { - guard.record_error_code(status.code); + if let Some(guard) = self.lifetime_guard.take() { + guard.record_error(&err); } self.on_first_transaction_id = None; return Some(Err(err)); } None => { - self.lifetime_guard = None; + if let Some(guard) = self.lifetime_guard.take() { + guard.record_success(); + } self.on_first_transaction_id = None; return None; } @@ -219,12 +232,17 @@ mod tests { struct TestDropGuard { dropped: Arc, recorded_code: Arc>>, + recorded_success: Arc, } impl StreamGuard for TestDropGuard { fn record_error_code(&self, code: Code) { *self.recorded_code.lock().expect("lock poisoned") = Some(code); } + + fn record_success(&self) { + self.recorded_success.store(true, Ordering::Release); + } } impl Drop for TestDropGuard { @@ -237,9 +255,11 @@ mod tests { async fn stream_drop_releases_lifetime_guard() -> anyhow::Result<()> { let dropped = Arc::new(AtomicBool::new(false)); let recorded_code = Arc::new(Mutex::new(None)); + let recorded_success = Arc::new(AtomicBool::new(false)); let guard = Arc::new(TestDropGuard { dropped: Arc::clone(&dropped), recorded_code: Arc::clone(&recorded_code), + recorded_success: Arc::clone(&recorded_success), }); let mut mock = create_session_mock(); @@ -275,6 +295,10 @@ mod tests { None, "No error code should be recorded on normal stream drop" ); + assert!( + !recorded_success.load(Ordering::Acquire), + "Early stream drop without EOF must not invoke record_success" + ); Ok(()) } @@ -282,9 +306,11 @@ mod tests { async fn stream_eof_releases_lifetime_guard() -> anyhow::Result<()> { let dropped = Arc::new(AtomicBool::new(false)); let recorded_code = Arc::new(Mutex::new(None)); + let recorded_success = Arc::new(AtomicBool::new(false)); let guard = Arc::new(TestDropGuard { dropped: Arc::clone(&dropped), recorded_code: Arc::clone(&recorded_code), + recorded_success: Arc::clone(&recorded_success), }); let mut mock = create_session_mock(); @@ -322,6 +348,10 @@ mod tests { None, "No error code should be recorded on normal EOF" ); + assert!( + recorded_success.load(Ordering::Acquire), + "Stream EOF must invoke record_success on lifetime guard" + ); Ok(()) } @@ -329,9 +359,11 @@ mod tests { async fn stream_error_releases_lifetime_guard() -> anyhow::Result<()> { let dropped = Arc::new(AtomicBool::new(false)); let recorded_code = Arc::new(Mutex::new(None)); + let recorded_success = Arc::new(AtomicBool::new(false)); let guard = Arc::new(TestDropGuard { dropped: Arc::clone(&dropped), recorded_code: Arc::clone(&recorded_code), + recorded_success: Arc::clone(&recorded_success), }); let mut mock = create_session_mock(); @@ -375,6 +407,10 @@ mod tests { Some(Code::Unavailable), "Stream error must record Code::Unavailable on guard" ); + assert!( + !recorded_success.load(Ordering::Acquire), + "Stream error must not invoke record_success on lifetime guard" + ); Ok(()) } diff --git a/src/spanner/src/statement.rs b/src/spanner/src/statement.rs index 0633445c9b..bd97876672 100644 --- a/src/spanner/src/statement.rs +++ b/src/spanner/src/statement.rs @@ -613,8 +613,8 @@ mod tests { assert_eq!(stmt.params.len(), 1); assert_eq!(stmt.request_options, None); - let val = stmt.params.get("age").unwrap(); - assert_eq!(val.as_string(), "21"); + let val = stmt.params.get("age").expect("age parameter should exist"); + assert_eq!(val.as_str(), Some("21")); } #[test] @@ -633,10 +633,22 @@ mod tests { .add_param("active", active_bool) .build(); - assert_eq!(stmt.params.get("id").unwrap().as_string(), "user-123"); - assert_eq!(stmt.params.get("id2").unwrap().as_string(), "user-456"); - assert_eq!(stmt.params.get("age").unwrap().as_string(), "42"); - assert!(stmt.params.get("active").unwrap().as_bool()); + assert_eq!( + stmt.params.get("id").expect("id param").as_str(), + Some("user-123") + ); + assert_eq!( + stmt.params.get("id2").expect("id2 param").as_str(), + Some("user-456") + ); + assert_eq!( + stmt.params.get("age").expect("age param").as_str(), + Some("42") + ); + assert_eq!( + stmt.params.get("active").expect("active param").as_bool(), + Some(true) + ); } #[test] @@ -658,11 +670,26 @@ mod tests { .add_typed_param("role", &"admin", types::string()) .build(); - assert_eq!(stmt.params.get("id").unwrap().as_string(), "user-123"); - assert_eq!(stmt.params.get("id2").unwrap().as_string(), "user-456"); - assert_eq!(stmt.params.get("age").unwrap().as_string(), "42"); - assert!(stmt.params.get("active").unwrap().as_bool()); - assert_eq!(stmt.params.get("role").unwrap().as_string(), "admin"); + assert_eq!( + stmt.params.get("id").expect("id param").as_str(), + Some("user-123") + ); + assert_eq!( + stmt.params.get("id2").expect("id2 param").as_str(), + Some("user-456") + ); + assert_eq!( + stmt.params.get("age").expect("age param").as_str(), + Some("42") + ); + assert_eq!( + stmt.params.get("active").expect("active param").as_bool(), + Some(true) + ); + assert_eq!( + stmt.params.get("role").expect("role param").as_str(), + Some("admin") + ); } #[test] @@ -678,8 +705,8 @@ mod tests { stmt.params .get("age") .expect("parameter 'age' should be present") - .as_string(), - "21" + .as_str(), + Some("21") ); } @@ -703,8 +730,8 @@ mod tests { stmt.params .get("id") .expect("parameter 'id' should be present") - .as_string(), - "user-123" + .as_str(), + Some("user-123") ); } @@ -742,11 +769,14 @@ mod tests { .build(); assert_eq!(stmt.param_types.len(), 1); - assert_eq!(stmt.param_types.get("id").unwrap(), &types::string()); + assert_eq!( + stmt.param_types.get("id").expect("param type should exist"), + &types::string() + ); assert_eq!(stmt.params.len(), 1); - let val = stmt.params.get("id").unwrap(); - assert_eq!(val.as_string(), "user-123"); + let val = stmt.params.get("id").expect("id parameter should exist"); + assert_eq!(val.as_str(), Some("user-123")); } #[test] diff --git a/src/spanner/src/timestamp_bound.rs b/src/spanner/src/timestamp_bound.rs index 296079a528..d54f753cd9 100644 --- a/src/spanner/src/timestamp_bound.rs +++ b/src/spanner/src/timestamp_bound.rs @@ -13,7 +13,9 @@ // limitations under the License. use crate::model::transaction_options::read_only::TimestampBound as ReadOnlyTimestampBound; -use std::fmt::Debug; +use std::time::Duration as StdDuration; +use wkt::Duration; +use wkt::Timestamp; /// Use a timestamp bound to specify how to choose a timestamp at which a query should read data. /// @@ -21,9 +23,9 @@ use std::fmt::Debug; /// ``` /// # use google_cloud_spanner::client::Spanner; /// # use google_cloud_spanner::transaction::TimestampBound; -/// # async fn test_doc() -> Result<(), google_cloud_spanner::Error> { -/// let client = Spanner::builder().build().await.unwrap(); -/// let db = client.database_client("projects/p/instances/i/databases/d").build().await.unwrap(); +/// # async fn test_doc() -> Result<(), Box> { +/// let client = Spanner::builder().build().await?; +/// let db = client.database_client("projects/p/instances/i/databases/d").build().await?; /// /// let tx = db.single_use().set_timestamp_bound(TimestampBound::strong()).build(); /// # Ok(()) @@ -31,9 +33,15 @@ use std::fmt::Debug; /// ``` /// /// See for more information. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct TimestampBound(pub(crate) ReadOnlyTimestampBound); +impl Default for TimestampBound { + fn default() -> Self { + Self::strong() + } +} + impl TimestampBound { /// Returns a strong timestamp bound. Strong reads are guaranteed to see the /// effects of all transactions that have committed before the start of the read. @@ -47,44 +55,42 @@ impl TimestampBound { /// Returns a timestamp bound for an exact timestamp. The data will be read as it was at the given timestamp. /// - /// See [timestamp_bound_exact_staleness] for more information. + /// For fallible conversion from types such as RFC 3339 strings, [`std::time::SystemTime`], + /// or [`time::OffsetDateTime`], use [`try_read_timestamp`](Self::try_read_timestamp). /// - /// [timestamp_bound_exact_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#exact_staleness - pub fn read_timestamp(timestamp: T) -> Self - where - T: TryInto, - T::Error: Debug, - { - Self::try_read_timestamp(timestamp).expect("timestamp out of range") + /// See [timestamp_bound_read_timestamp] for more information. + /// + /// [timestamp_bound_read_timestamp]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#read_timestamp + pub fn read_timestamp(timestamp: Timestamp) -> Self { + Self(ReadOnlyTimestampBound::ReadTimestamp(Box::new(timestamp))) } /// Returns a timestamp bound for an exact timestamp, returning an error if the timestamp is out of range. /// - /// See [timestamp_bound_exact_staleness] for more information. + /// See [timestamp_bound_read_timestamp] for more information. /// - /// [timestamp_bound_exact_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#exact_staleness + /// [timestamp_bound_read_timestamp]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#read_timestamp pub fn try_read_timestamp(timestamp: T) -> Result where - T: TryInto, + T: TryInto, { let timestamp = timestamp.try_into()?; - Ok(Self(ReadOnlyTimestampBound::ReadTimestamp(Box::new( - timestamp, - )))) + Ok(Self::read_timestamp(timestamp)) } /// Returns a timestamp bound for a minimum read timestamp. The data will be read as it was at the /// given timestamp or later. /// + /// For fallible conversion from types such as RFC 3339 strings, [`std::time::SystemTime`], + /// or [`time::OffsetDateTime`], use [`try_min_read_timestamp`](Self::try_min_read_timestamp). + /// /// See [timestamp_bound_bounded_staleness] for more information. /// /// [timestamp_bound_bounded_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#bounded_staleness - pub fn min_read_timestamp(timestamp: T) -> Self - where - T: TryInto, - T::Error: Debug, - { - Self::try_min_read_timestamp(timestamp).expect("timestamp out of range") + pub fn min_read_timestamp(timestamp: Timestamp) -> Self { + Self(ReadOnlyTimestampBound::MinReadTimestamp(Box::new( + timestamp, + ))) } /// Returns a timestamp bound for a minimum read timestamp, returning an error if the timestamp is out of range. @@ -94,26 +100,25 @@ impl TimestampBound { /// [timestamp_bound_bounded_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#bounded_staleness pub fn try_min_read_timestamp(timestamp: T) -> Result where - T: TryInto, + T: TryInto, { let timestamp = timestamp.try_into()?; - Ok(Self(ReadOnlyTimestampBound::MinReadTimestamp(Box::new( - timestamp, - )))) + Ok(Self::min_read_timestamp(timestamp)) } /// Returns a timestamp bound for an exact staleness. The data will be read as it was at the given timestamp /// calculated by the current server time minus the given duration. /// + /// For fallible conversion from types such as [`wkt::Duration`], use + /// [`try_exact_staleness`](Self::try_exact_staleness). + /// /// See [timestamp_bound_exact_staleness] for more information. /// /// [timestamp_bound_exact_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#exact_staleness - pub fn exact_staleness(duration: T) -> Self - where - T: TryInto, - T::Error: Debug, - { - Self::try_exact_staleness(duration).expect("duration out of range") + pub fn exact_staleness(duration: StdDuration) -> Self { + Self(ReadOnlyTimestampBound::ExactStaleness(Box::new( + to_clamped_duration(duration), + ))) } /// Returns a timestamp bound for an exact staleness, returning an error if the duration is out of range. @@ -123,7 +128,7 @@ impl TimestampBound { /// [timestamp_bound_exact_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#exact_staleness pub fn try_exact_staleness(duration: T) -> Result where - T: TryInto, + T: TryInto, { let duration = duration.try_into()?; Ok(Self(ReadOnlyTimestampBound::ExactStaleness(Box::new( @@ -134,15 +139,16 @@ impl TimestampBound { /// Returns a timestamp bound for a maximum staleness. The data will be read as it was at the /// current server time minus the given duration or later. /// + /// For fallible conversion from types such as [`wkt::Duration`], use + /// [`try_max_staleness`](Self::try_max_staleness). + /// /// See [timestamp_bound_bounded_staleness] for more information. /// /// [timestamp_bound_bounded_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#bounded_staleness - pub fn max_staleness(duration: T) -> Self - where - T: TryInto, - T::Error: Debug, - { - Self::try_max_staleness(duration).expect("duration out of range") + pub fn max_staleness(duration: StdDuration) -> Self { + Self(ReadOnlyTimestampBound::MaxStaleness(Box::new( + to_clamped_duration(duration), + ))) } /// Returns a timestamp bound for a maximum staleness, returning an error if the duration is out of range. @@ -152,7 +158,7 @@ impl TimestampBound { /// [timestamp_bound_bounded_staleness]: https://docs.cloud.google.com/spanner/docs/timestamp-bounds#bounded_staleness pub fn try_max_staleness(duration: T) -> Result where - T: TryInto, + T: TryInto, { let duration = duration.try_into()?; Ok(Self(ReadOnlyTimestampBound::MaxStaleness(Box::new( @@ -161,23 +167,31 @@ impl TimestampBound { } } +fn to_clamped_duration(duration: StdDuration) -> Duration { + let seconds = i64::try_from(duration.as_secs()).unwrap_or(i64::MAX); + let nanos = duration.subsec_nanos() as i32; + Duration::clamp(seconds, nanos) +} + #[cfg(test)] mod tests { use super::*; - use std::time::Duration; + use std::fmt::Debug; + use std::time::Duration as StdDuration; use std::time::SystemTime; use time::macros::datetime; use wkt::Timestamp; #[test] fn test_auto_traits() { - static_assertions::assert_impl_all!(TimestampBound: Clone, Debug, Send, Sync); + static_assertions::assert_impl_all!(TimestampBound: Clone, Debug, Default, PartialEq, Send, Sync); } #[test] fn test_strong() { let bound = TimestampBound::strong(); assert!(matches!(bound.0, ReadOnlyTimestampBound::Strong(true))); + assert_eq!(bound, TimestampBound::default()); } #[test] @@ -187,12 +201,6 @@ mod tests { // 1. OffsetDateTime #[cfg(feature = "unstable-time")] { - let read = TimestampBound::read_timestamp(ts); - assert!(matches!( - read.0, - ReadOnlyTimestampBound::ReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32 - )); - let try_read = TimestampBound::try_read_timestamp(ts).expect("valid OffsetDateTime"); assert!(matches!( try_read.0, @@ -216,12 +224,6 @@ mod tests { // 3. SystemTime let system_time = SystemTime::from(ts); - let read = TimestampBound::read_timestamp(system_time); - assert!(matches!( - read.0, - ReadOnlyTimestampBound::ReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32 - )); - let try_read = TimestampBound::try_read_timestamp(system_time).expect("valid SystemTime"); assert!(matches!( try_read.0, @@ -236,12 +238,6 @@ mod tests { // 1. OffsetDateTime #[cfg(feature = "unstable-time")] { - let min_read = TimestampBound::min_read_timestamp(ts); - assert!(matches!( - min_read.0, - ReadOnlyTimestampBound::MinReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32 - )); - let try_min_read = TimestampBound::try_min_read_timestamp(ts).expect("valid OffsetDateTime"); assert!(matches!( @@ -267,12 +263,6 @@ mod tests { // 3. SystemTime let system_time = SystemTime::from(ts); - let min_read = TimestampBound::min_read_timestamp(system_time); - assert!(matches!( - min_read.0, - ReadOnlyTimestampBound::MinReadTimestamp(ref t) if t.seconds() == ts.unix_timestamp() && t.nanos() == ts.nanosecond() as i32 - )); - let try_min_read = TimestampBound::try_min_read_timestamp(system_time).expect("valid SystemTime"); assert!(matches!( @@ -283,7 +273,7 @@ mod tests { #[test] fn test_exact_staleness_methods() { - let d = Duration::from_secs(60); + let d = StdDuration::from_secs(60); // 1. std::time::Duration let exact = TimestampBound::exact_staleness(d); @@ -299,13 +289,7 @@ mod tests { )); // 2. wkt::Duration - let wkt_d = wkt::Duration::try_from(d).expect("valid wkt duration"); - let exact = TimestampBound::exact_staleness(wkt_d); - assert!(matches!( - exact.0, - ReadOnlyTimestampBound::ExactStaleness(ref t) if t.seconds() == 60 && t.nanos() == 0 - )); - + let wkt_d = Duration::clamp(60, 0); let try_exact = TimestampBound::try_exact_staleness(wkt_d).expect("valid wkt::Duration"); assert!(matches!( try_exact.0, @@ -315,7 +299,7 @@ mod tests { #[test] fn test_max_staleness_methods() { - let d = Duration::from_secs(120); + let d = StdDuration::from_secs(120); // 1. std::time::Duration let max = TimestampBound::max_staleness(d); @@ -331,17 +315,55 @@ mod tests { )); // 2. wkt::Duration - let wkt_d = wkt::Duration::try_from(d).expect("valid wkt duration"); - let max = TimestampBound::max_staleness(wkt_d); - assert!(matches!( - max.0, - ReadOnlyTimestampBound::MaxStaleness(ref t) if t.seconds() == 120 && t.nanos() == 0 - )); - + let wkt_d = Duration::clamp(120, 0); let try_max = TimestampBound::try_max_staleness(wkt_d).expect("valid wkt::Duration"); assert!(matches!( try_max.0, ReadOnlyTimestampBound::MaxStaleness(ref t) if t.seconds() == 120 && t.nanos() == 0 )); } + + #[test] + fn test_out_of_range() { + let out_of_range_time = SystemTime::UNIX_EPOCH + .checked_sub(StdDuration::from_secs(100_000_000_000)) + .expect("valid SystemTime"); + assert!( + TimestampBound::try_read_timestamp(out_of_range_time).is_err(), + "expected try_read_timestamp to fail for out-of-range timestamp" + ); + assert!( + TimestampBound::try_min_read_timestamp(out_of_range_time).is_err(), + "expected try_min_read_timestamp to fail for out-of-range timestamp" + ); + + let out_of_range_duration = StdDuration::from_secs(u64::MAX); + assert!( + TimestampBound::try_exact_staleness(out_of_range_duration).is_err(), + "expected try_exact_staleness to fail for out-of-range duration" + ); + assert!( + TimestampBound::try_max_staleness(out_of_range_duration).is_err(), + "expected try_max_staleness to fail for out-of-range duration" + ); + + let clamped_exact = TimestampBound::exact_staleness(out_of_range_duration); + assert!( + matches!( + clamped_exact.0, + ReadOnlyTimestampBound::ExactStaleness(ref d) + if d.seconds() == Duration::MAX_SECONDS && d.nanos() == 0 + ), + "expected exact_staleness to clamp to MAX_SECONDS" + ); + let clamped_max = TimestampBound::max_staleness(out_of_range_duration); + assert!( + matches!( + clamped_max.0, + ReadOnlyTimestampBound::MaxStaleness(ref d) + if d.seconds() == Duration::MAX_SECONDS && d.nanos() == 0 + ), + "expected max_staleness to clamp to MAX_SECONDS" + ); + } } diff --git a/src/spanner/src/to_value.rs b/src/spanner/src/to_value.rs index 0092d12108..c0ebfcd2f9 100644 --- a/src/spanner/src/to_value.rs +++ b/src/spanner/src/to_value.rs @@ -212,6 +212,9 @@ impl ToValue for wkt::Timestamp { } } +// wkt::Timestamp is strictly bounded to [0001-01-01, 9999-12-31], which is +// entirely within OffsetDateTime's range ([-999,999, +999,999]). This conversion +// is mathematically infallible. impl From for Value { fn from(timestamp: wkt::Timestamp) -> Self { let nanos = timestamp.seconds() as i128 * 1_000_000_000 + timestamp.nanos() as i128; @@ -395,396 +398,409 @@ mod tests { use time::format_description::well_known::Rfc3339; #[test] - fn test_null_value_conversions() { - let null_val = Value::null(); - assert_eq!(null_val.kind(), Kind::Null); + fn null_value_conversions() { + let null_value = Value::null(); + assert_eq!(null_value.kind(), Kind::Null); - let unit_val = ().to_value(); - assert_eq!(unit_val, null_val); + let unit_value = ().to_value(); + assert_eq!(unit_value, null_value); - let unit_ref_into: Value = (&()).into(); - assert_eq!(unit_ref_into, null_val); + let unit_ref_value: Value = (&()).into(); + assert_eq!(unit_ref_value, null_value); - let some_unit = Some(()).to_value(); - assert_eq!(some_unit, null_val); + let some_unit_value = Some(()).to_value(); + assert_eq!(some_unit_value, null_value); - let opt_unit: Value = None::<()>.into(); - assert_eq!(opt_unit, null_val); + let opt_unit_value: Value = None::<()>.into(); + assert_eq!(opt_unit_value, null_value); - let opt_val: Value = None::.into(); - assert_eq!(opt_val, null_val); + let opt_value: Value = None::.into(); + assert_eq!(opt_value, null_value); - let opt_i64: Value = None::.into(); - assert_eq!(opt_i64, null_val); + let opt_i64_value: Value = None::.into(); + assert_eq!(opt_i64_value, null_value); } #[test] - fn test_from_value_conversions() { - let v: Value = "hello".to_string().into(); - assert_eq!(v.as_string(), "hello"); + fn from_value_conversions() { + let value: Value = "hello".to_string().into(); + assert_eq!(value.as_str(), Some("hello")); - let v: Value = "world".into(); - assert_eq!(v.as_string(), "world"); + let value: Value = "world".into(); + assert_eq!(value.as_str(), Some("world")); - let v: Value = 42i64.into(); - assert_eq!(v.as_string(), "42"); + let value: Value = 42i64.into(); + assert_eq!(value.as_str(), Some("42")); - let v: Value = 42i32.into(); - assert_eq!(v.as_string(), "42"); + let value: Value = 42i32.into(); + assert_eq!(value.as_str(), Some("42")); - let v: Value = true.into(); - assert!(v.as_bool()); + let value: Value = true.into(); + assert_eq!(value.as_bool(), Some(true)); - let v: Value = 42.5f64.into(); - assert_eq!(v.as_f64(), 42.5); + let value: Value = 42.5f64.into(); + assert_eq!(value.as_f64(), Some(42.5)); - let v: Value = 42.5f32.into(); - assert_eq!(v.as_f64(), 42.5); + let value: Value = 42.5f32.into(); + assert_eq!(value.as_f64(), Some(42.5)); - let v: Value = vec![1u8, 2, 3].into(); - assert_eq!(v.as_string(), "AQID"); + let value: Value = vec![1u8, 2, 3].into(); + assert_eq!(value.as_str(), Some("AQID")); - let v: Value = (&[1u8, 2, 3][..]).into(); - assert_eq!(v.as_string(), "AQID"); + let value: Value = (&[1u8, 2, 3][..]).into(); + assert_eq!(value.as_str(), Some("AQID")); - let d = Decimal::from_str("123.456").expect("valid decimal"); - let v: Value = d.into(); - assert_eq!(v.as_string(), "123.456"); + let decimal = Decimal::from_str("123.456").expect("valid decimal"); + let value: Value = decimal.into(); + assert_eq!(value.as_str(), Some("123.456")); #[cfg(feature = "unstable-time")] { let date_time = OffsetDateTime::parse("2023-10-27T10:00:00Z", &Rfc3339) .expect("valid RFC 3339 format"); - let v: Value = date_time.into(); - assert_eq!(v.as_string(), "2023-10-27T10:00:00.000000000Z"); + let value: Value = date_time.into(); + assert_eq!(value.as_str(), Some("2023-10-27T10:00:00.000000000Z")); let time_date = TimeDate::from_calendar_date(2023, Month::October, 27) .expect("valid calendar date"); - let v: Value = time_date.into(); - assert_eq!(v.as_string(), "2023-10-27"); + let value: Value = time_date.into(); + assert_eq!(value.as_str(), Some("2023-10-27")); } let system_time = SystemTime::UNIX_EPOCH + Duration::from_secs(1698400800); - let v: Value = system_time.into(); - assert_eq!(v.as_string(), "2023-10-27T10:00:00.000000000Z"); + let value: Value = system_time.into(); + assert_eq!(value.as_str(), Some("2023-10-27T10:00:00.000000000Z")); let timestamp = wkt::Timestamp::clamp(1698400800, 0); - let v: Value = timestamp.into(); - assert_eq!(v.as_string(), "2023-10-27T10:00:00.000000000Z"); + let value: Value = timestamp.into(); + assert_eq!(value.as_str(), Some("2023-10-27T10:00:00.000000000Z")); let google_date = Date::new().set_year(2023).set_month(10).set_day(27); - let v: Value = (&google_date).into(); - assert_eq!(v.as_string(), "2023-10-27"); + let value: Value = (&google_date).into(); + assert_eq!(value.as_str(), Some("2023-10-27")); - let json_val = serde_json::json!({"key": "value"}); - let v: Value = json_val.into(); - assert_eq!(v.as_string(), "{\"key\":\"value\"}"); + let json_value = serde_json::json!({"key": "value"}); + let value: Value = json_value.into(); + assert_eq!(value.as_str(), Some("{\"key\":\"value\"}")); let list: Value = vec![1i64, 2i64].into(); assert_eq!(list.kind(), Kind::List); - assert_eq!(list.as_list().len(), 2); + assert_eq!(list.as_list().expect("list should exist").len(), 2); - let opt_some: Value = Some(42i64).into(); - assert_eq!(opt_some.as_string(), "42"); - let opt_none: Value = None::.into(); - assert_eq!(opt_none.kind(), Kind::Null); + let opt_some_value: Value = Some(42i64).into(); + assert_eq!(opt_some_value.as_str(), Some("42")); + let opt_none_value: Value = None::.into(); + assert_eq!(opt_none_value.kind(), Kind::Null); - let unit: Value = ().into(); - assert_eq!(unit.kind(), Kind::Null); + let unit_value: Value = ().into(); + assert_eq!(unit_value.kind(), Kind::Null); let borrowed_i64: Value = (&42i64).into(); - assert_eq!(borrowed_i64.as_string(), "42"); + assert_eq!(borrowed_i64.as_str(), Some("42")); let borrowed_bool: Value = (&true).into(); - assert!(borrowed_bool.as_bool()); + assert_eq!(borrowed_bool.as_bool(), Some(true)); let borrowed_float: Value = (&42.5f64).into(); - assert_eq!(borrowed_float.as_f64(), 42.5); + assert_eq!(borrowed_float.as_f64(), Some(42.5)); let borrowed_date: Value = google_date.into(); - assert_eq!(borrowed_date.as_string(), "2023-10-27"); + assert_eq!(borrowed_date.as_str(), Some("2023-10-27")); - let vec_opt: Value = vec![Some(1_i64), None].into(); - assert_eq!(vec_opt.kind(), Kind::List); - assert_eq!(vec_opt.as_list().len(), 2); + let vec_opt_value: Value = vec![Some(1_i64), None].into(); + assert_eq!(vec_opt_value.kind(), Kind::List); + assert_eq!(vec_opt_value.as_list().expect("list should exist").len(), 2); } #[test] - fn test_to_value_string() { - let v = "hello".to_string().to_value(); - assert_eq!(v.kind(), Kind::String); - assert_eq!(v.as_string(), "hello"); + fn to_value_string() { + let value = "hello".to_string().to_value(); + assert_eq!(value.kind(), Kind::String); + assert_eq!(value.as_str(), Some("hello")); - let v = "world".to_value(); - assert_eq!(v.kind(), Kind::String); - assert_eq!(v.as_string(), "world"); + let value = "world".to_value(); + assert_eq!(value.kind(), Kind::String); + assert_eq!(value.as_str(), Some("world")); } #[test] - fn test_to_value_str_trait_bound() { + fn to_value_str_trait_bound() { fn bind_param(val: &T) -> Value { val.to_value() } - let v = bind_param("hello"); - assert_eq!(v.kind(), Kind::String); - assert_eq!(v.as_string(), "hello"); + let value = bind_param("hello"); + assert_eq!(value.kind(), Kind::String); + assert_eq!(value.as_str(), Some("hello")); let owned: String = "hello".to_string(); - assert_eq!(bind_param(&owned), v); + assert_eq!(bind_param(&owned), value); let borrowed: &str = &owned; - assert_eq!(bind_param(&borrowed), v); + assert_eq!(bind_param(&borrowed), value); } #[test] - fn test_to_value_int() { - let v = 42i64.to_value(); - assert_eq!(v.kind(), Kind::String); - assert_eq!(v.as_string(), "42"); + fn to_value_int() { + let value = 42i64.to_value(); + assert_eq!(value.kind(), Kind::String); + assert_eq!(value.as_str(), Some("42")); - let v = 42i32.to_value(); - assert_eq!(v.kind(), Kind::String); - assert_eq!(v.as_string(), "42"); + let value = 42i32.to_value(); + assert_eq!(value.kind(), Kind::String); + assert_eq!(value.as_str(), Some("42")); } #[test] - fn test_to_value_float() { - let v = 42.5f64.to_value(); - assert_eq!(v.kind(), Kind::Number); - assert_eq!(v.as_f64(), 42.5); + fn to_value_float() { + let value = 42.5f64.to_value(); + assert_eq!(value.kind(), Kind::Number); + assert_eq!(value.as_f64(), Some(42.5)); - let v = 42.5f32.to_value(); - assert_eq!(v.kind(), Kind::Number); - assert_eq!(v.as_f64(), 42.5); + let value = 42.5f32.to_value(); + assert_eq!(value.kind(), Kind::Number); + assert_eq!(value.as_f64(), Some(42.5)); } #[test] - fn test_to_value_bool() { - let v = true.to_value(); - assert_eq!(v.kind(), Kind::Bool); - assert!(v.as_bool()); + fn to_value_bool() { + let value = true.to_value(); + assert_eq!(value.kind(), Kind::Bool); + assert_eq!(value.as_bool(), Some(true)); - let v = false.to_value(); - assert_eq!(v.kind(), Kind::Bool); - assert!(!v.as_bool()); + let value = false.to_value(); + assert_eq!(value.kind(), Kind::Bool); + assert_eq!(value.as_bool(), Some(false)); } #[test] - fn test_to_value_bytes() { + fn to_value_bytes() { let bytes: Vec = vec![1, 2, 3]; - let v = bytes.to_value(); - assert_eq!(v.kind(), Kind::String); - assert_eq!(v.as_string(), "AQID"); // Base64 encoded + let value = bytes.to_value(); + assert_eq!(value.kind(), Kind::String); + assert_eq!(value.as_str(), Some("AQID")); // Base64 encoded - let slice_val: Value = (&[1u8, 2, 3][..]).into(); - assert_eq!(slice_val.kind(), Kind::String); - assert_eq!(slice_val.as_string(), "AQID"); + let slice_value: Value = (&[1u8, 2, 3][..]).into(); + assert_eq!(slice_value.kind(), Kind::String); + assert_eq!(slice_value.as_str(), Some("AQID")); } #[test] - fn test_to_value_slice_trait_bound() { + fn to_value_slice_trait_bound() { fn bind_param(val: &T) -> Value { val.to_value() } let slice: &[u8] = &[1, 2, 3]; - let v = bind_param(slice); - assert_eq!(v.kind(), Kind::String); - assert_eq!(v.as_string(), "AQID"); + let value = bind_param(slice); + assert_eq!(value.kind(), Kind::String); + assert_eq!(value.as_str(), Some("AQID")); } #[test] - fn test_to_value_decimal() { - let d = Decimal::from_str("123.456").expect("valid decimal"); - let v = d.to_value(); - assert_eq!(v.kind(), Kind::String); - assert_eq!(v.as_string(), "123.456"); + fn to_value_decimal() { + let decimal = Decimal::from_str("123.456").expect("valid decimal"); + let value = decimal.to_value(); + assert_eq!(value.kind(), Kind::String); + assert_eq!(value.as_str(), Some("123.456")); } #[test] - fn test_to_value_google_date() { + fn to_value_google_date() { let date = Date::new().set_year(2023).set_month(10).set_day(27); let value = date.to_value(); assert_eq!(value.kind(), Kind::String); - assert_eq!(value.as_string(), "2023-10-27"); + assert_eq!(value.as_str(), Some("2023-10-27")); } #[cfg(feature = "unstable-time")] #[test] - fn test_to_value_time_date() { + fn to_value_time_date() { let date = TimeDate::from_calendar_date(2023, Month::October, 27).expect("valid calendar date"); let value = date.to_value(); assert_eq!(value.kind(), Kind::String); - assert_eq!(value.as_string(), "2023-10-27"); + assert_eq!(value.as_str(), Some("2023-10-27")); } #[test] - fn test_to_value_system_time() { + fn to_value_system_time() { let system_time = SystemTime::UNIX_EPOCH + Duration::from_secs(1698400800); let value = system_time.to_value(); assert_eq!(value.kind(), Kind::String); - assert_eq!(value.as_string(), "2023-10-27T10:00:00.000000000Z"); + assert_eq!(value.as_str(), Some("2023-10-27T10:00:00.000000000Z")); } #[cfg(feature = "unstable-time")] #[test] - fn test_to_value_offset_date_time() { + fn to_value_offset_date_time() { let date_time = OffsetDateTime::parse("2023-10-27T10:00:00Z", &Rfc3339) .expect("valid date time parsing"); let value = date_time.to_value(); assert_eq!(value.kind(), Kind::String); - assert_eq!(value.as_string(), "2023-10-27T10:00:00.000000000Z"); + assert_eq!(value.as_str(), Some("2023-10-27T10:00:00.000000000Z")); } #[test] - fn test_to_value_wkt_timestamp() { + fn to_value_wkt_timestamp() { let timestamp = wkt::Timestamp::clamp(1698400800, 0); let value = timestamp.to_value(); assert_eq!(value.kind(), Kind::String); - assert_eq!(value.as_string(), "2023-10-27T10:00:00.000000000Z"); + assert_eq!(value.as_str(), Some("2023-10-27T10:00:00.000000000Z")); + // Verify boundary conditions (MIN_SECONDS and MAX_SECONDS) convert cleanly without panicking. let min_timestamp = wkt::Timestamp::clamp(wkt::Timestamp::MIN_SECONDS, 0); - let value = min_timestamp.to_value(); - assert_eq!(value.kind(), Kind::String); - assert_eq!(value.as_string(), "0001-01-01T00:00:00.000000000Z"); + let min_value = min_timestamp.to_value(); + assert_eq!(min_value.kind(), Kind::String); + assert_eq!(min_value.as_str(), Some("0001-01-01T00:00:00.000000000Z")); let max_timestamp = wkt::Timestamp::clamp(wkt::Timestamp::MAX_SECONDS, 999_999_999); - let value = max_timestamp.to_value(); - assert_eq!(value.kind(), Kind::String); - assert_eq!(value.as_string(), "9999-12-31T23:59:59.999999999Z"); + let max_value = max_timestamp.to_value(); + assert_eq!(max_value.kind(), Kind::String); + assert_eq!(max_value.as_str(), Some("9999-12-31T23:59:59.999999999Z")); } #[test] - fn test_to_value_json() { + fn to_value_json() { let json_value = serde_json::json!({"test": 123}); let value = json_value.to_value(); assert_eq!(value.kind(), Kind::String); - assert_eq!(value.as_string(), "{\"test\":123}"); + assert_eq!(value.as_str(), Some("{\"test\":123}")); } #[test] - fn test_to_value_option() { - let some_val: Option = Some(42); - let v = some_val.to_value(); - assert_eq!(v.kind(), Kind::String); - assert_eq!(v.as_string(), "42"); + fn to_value_option() { + let some_value: Option = Some(42); + let value = some_value.to_value(); + assert_eq!(value.kind(), Kind::String); + assert_eq!(value.as_str(), Some("42")); - let none_val: Option = None; - let v = none_val.to_value(); - assert_eq!(v.kind(), Kind::Null); + let none_value: Option = None; + let value = none_value.to_value(); + assert_eq!(value.kind(), Kind::Null); } #[test] - fn test_to_value_value() { - let v_original = 42i32.to_value(); - let v = v_original.to_value(); - assert_eq!(v, v_original); + fn to_value_value() { + let original_value = 42i32.to_value(); + let value = original_value.to_value(); + assert_eq!(value, original_value); } #[test] - fn test_to_value_array() { - let str_array = vec!["one".to_string(), "two".to_string()]; - let v = str_array.to_value(); - assert_eq!(v.kind(), Kind::List); - let list = v.as_list(); + fn to_value_array() { + let string_array = vec!["one".to_string(), "two".to_string()]; + let value = string_array.to_value(); + assert_eq!(value.kind(), Kind::List); + let list = value.as_list().expect("list should exist"); assert_eq!(list.len(), 2); assert_eq!( - list.get(0).expect("element 0 should exist").as_string(), - "one" + list.get(0).expect("element 0 should exist").as_str(), + Some("one") ); assert_eq!( - list.get(1).expect("element 1 should exist").as_string(), - "two" + list.get(1).expect("element 1 should exist").as_str(), + Some("two") ); let int_array = vec![42i64, 100i64]; - let v = int_array.to_value(); - assert_eq!(v.kind(), Kind::List); - let list = v.as_list(); + let value = int_array.to_value(); + assert_eq!(value.kind(), Kind::List); + let list = value.as_list().expect("list should exist"); assert_eq!(list.len(), 2); assert_eq!( - list.get(0).expect("element 0 should exist").as_string(), - "42" + list.get(0).expect("element 0 should exist").as_str(), + Some("42") ); assert_eq!( - list.get(1).expect("element 1 should exist").as_string(), - "100" + list.get(1).expect("element 1 should exist").as_str(), + Some("100") ); let bool_array = vec![true, false]; - let v = bool_array.to_value(); - assert_eq!(v.kind(), Kind::List); - let list = v.as_list(); + let value = bool_array.to_value(); + assert_eq!(value.kind(), Kind::List); + let list = value.as_list().expect("list should exist"); assert_eq!(list.len(), 2); - assert!(list.get(0).expect("element 0 should exist").as_bool()); - assert!(!list.get(1).expect("element 1 should exist").as_bool()); + assert_eq!( + list.get(0).expect("element 0 should exist").as_bool(), + Some(true) + ); + assert_eq!( + list.get(1).expect("element 1 should exist").as_bool(), + Some(false) + ); let float_array = vec![9.9f64, -2.5f64]; - let v = float_array.to_value(); - assert_eq!(v.kind(), Kind::List); - let list = v.as_list(); + let value = float_array.to_value(); + assert_eq!(value.kind(), Kind::List); + let list = value.as_list().expect("list should exist"); assert_eq!(list.len(), 2); - assert_eq!(list.get(0).expect("element 0 should exist").as_f64(), 9.9); - assert_eq!(list.get(1).expect("element 1 should exist").as_f64(), -2.5); + assert_eq!( + list.get(0).expect("element 0 should exist").as_f64(), + Some(9.9) + ); + assert_eq!( + list.get(1).expect("element 1 should exist").as_f64(), + Some(-2.5) + ); let empty_array: Vec = vec![]; - let v = empty_array.to_value(); - assert_eq!(v.kind(), Kind::List); - assert_eq!(v.as_list().len(), 0); + let value = empty_array.to_value(); + assert_eq!(value.kind(), Kind::List); + assert_eq!(value.as_list().expect("list should exist").len(), 0); let null_array: Option> = None; - let v = null_array.to_value(); - assert_eq!(v.kind(), Kind::Null); + let value = null_array.to_value(); + assert_eq!(value.kind(), Kind::Null); let opt_array: Vec> = vec![Some(42), None, Some(100)]; - let v = opt_array.to_value(); - assert_eq!(v.kind(), Kind::List); - let list = v.as_list(); + let value = opt_array.to_value(); + assert_eq!(value.kind(), Kind::List); + let list = value.as_list().expect("list should exist"); assert_eq!(list.len(), 3); assert_eq!( - list.get(0).expect("element 0 should exist").as_string(), - "42" + list.get(0).expect("element 0 should exist").as_str(), + Some("42") ); assert_eq!( list.get(1).expect("element 1 should exist").kind(), Kind::Null ); assert_eq!( - list.get(2).expect("element 2 should exist").as_string(), - "100" + list.get(2).expect("element 2 should exist").as_str(), + Some("100") ); } #[test] fn to_value_non_finite_floats() { - let f64_nan_val = f64::NAN.to_value(); - assert_eq!(f64_nan_val.kind(), Kind::String); - assert_eq!(f64_nan_val.as_string(), "NaN"); + let f64_nan_value = f64::NAN.to_value(); + assert_eq!(f64_nan_value.kind(), Kind::String); + assert_eq!(f64_nan_value.as_str(), Some("NaN")); - let f64_inf_val = f64::INFINITY.to_value(); - assert_eq!(f64_inf_val.kind(), Kind::String); - assert_eq!(f64_inf_val.as_string(), "Infinity"); + let f64_infinity_value = f64::INFINITY.to_value(); + assert_eq!(f64_infinity_value.kind(), Kind::String); + assert_eq!(f64_infinity_value.as_str(), Some("Infinity")); - let f64_neginf_val = f64::NEG_INFINITY.to_value(); - assert_eq!(f64_neginf_val.kind(), Kind::String); - assert_eq!(f64_neginf_val.as_string(), "-Infinity"); + let f64_neg_infinity_value = f64::NEG_INFINITY.to_value(); + assert_eq!(f64_neg_infinity_value.kind(), Kind::String); + assert_eq!(f64_neg_infinity_value.as_str(), Some("-Infinity")); - let f64_finite_val = 42.5f64.to_value(); - assert_eq!(f64_finite_val.kind(), Kind::Number); - assert_eq!(f64_finite_val.as_f64(), 42.5); + let f64_finite_value = 42.5f64.to_value(); + assert_eq!(f64_finite_value.kind(), Kind::Number); + assert_eq!(f64_finite_value.as_f64(), Some(42.5)); - let f32_nan_val = f32::NAN.to_value(); - assert_eq!(f32_nan_val.kind(), Kind::String); - assert_eq!(f32_nan_val.as_string(), "NaN"); + let f32_nan_value = f32::NAN.to_value(); + assert_eq!(f32_nan_value.kind(), Kind::String); + assert_eq!(f32_nan_value.as_str(), Some("NaN")); - let f32_inf_val = f32::INFINITY.to_value(); - assert_eq!(f32_inf_val.kind(), Kind::String); - assert_eq!(f32_inf_val.as_string(), "Infinity"); + let f32_infinity_value = f32::INFINITY.to_value(); + assert_eq!(f32_infinity_value.kind(), Kind::String); + assert_eq!(f32_infinity_value.as_str(), Some("Infinity")); - let f32_neginf_val = f32::NEG_INFINITY.to_value(); - assert_eq!(f32_neginf_val.kind(), Kind::String); - assert_eq!(f32_neginf_val.as_string(), "-Infinity"); + let f32_neg_infinity_value = f32::NEG_INFINITY.to_value(); + assert_eq!(f32_neg_infinity_value.kind(), Kind::String); + assert_eq!(f32_neg_infinity_value.as_str(), Some("-Infinity")); - let f32_finite_val = 12.5f32.to_value(); - assert_eq!(f32_finite_val.kind(), Kind::Number); - assert_eq!(f32_finite_val.as_f64(), 12.5); + let f32_finite_value = 12.5f32.to_value(); + assert_eq!(f32_finite_value.kind(), Kind::Number); + assert_eq!(f32_finite_value.as_f64(), Some(12.5)); } } diff --git a/src/spanner/src/transaction.rs b/src/spanner/src/transaction.rs index 8742e2b961..a651d3739b 100644 --- a/src/spanner/src/transaction.rs +++ b/src/spanner/src/transaction.rs @@ -20,6 +20,8 @@ pub use crate::read_only_transaction::{ }; pub use crate::read_write_transaction::ReadWriteTransaction; pub use crate::timestamp_bound::TimestampBound; -pub use crate::transaction_retry_policy::BasicTransactionRetryPolicy; -pub use crate::transaction_runner::TransactionRunner; +pub use crate::transaction_retry_policy::{BasicTransactionRetryPolicy, TransactionRetryPolicy}; +pub use crate::transaction_runner::{TransactionResult, TransactionRunner}; pub use crate::write_only_transaction::WriteOnlyTransaction; +pub use google_cloud_gax::retry_result::RetryResult; +pub use wkt::Timestamp; diff --git a/src/spanner/src/transaction_retry_policy.rs b/src/spanner/src/transaction_retry_policy.rs index 0d11891591..d543f9ac25 100644 --- a/src/spanner/src/transaction_retry_policy.rs +++ b/src/spanner/src/transaction_retry_policy.rs @@ -18,14 +18,41 @@ use google_cloud_gax::error::rpc::Code; use google_cloud_gax::exponential_backoff::{ExponentialBackoff, ExponentialBackoffBuilder}; use google_cloud_gax::retry_result::RetryResult; use google_cloud_gax::retry_state::RetryState; +use std::fmt::Debug; +use std::sync::Arc; use std::time::Duration; /// Defines a policy for retrying a transaction when it is aborted by Spanner. /// +/// # Example +/// ``` +/// # use google_cloud_spanner::Error; +/// # use google_cloud_spanner::transaction::{RetryResult, TransactionRetryPolicy}; +/// # use std::time::Duration; +/// #[derive(Debug)] +/// struct LimitedRetryPolicy { +/// max_attempts: u32, +/// } +/// +/// impl TransactionRetryPolicy for LimitedRetryPolicy { +/// fn on_abort(&self, error: Error, attempts: u32, _elapsed: Duration) -> RetryResult { +/// if attempts < self.max_attempts { +/// RetryResult::Continue(error) +/// } else { +/// RetryResult::Exhausted(error) +/// } +/// } +/// } +/// ``` +/// /// Spanner can abort any read/write transaction due to lock conflicts or other /// transient issues. In such cases, the client should retry the complete /// transaction. -pub trait TransactionRetryPolicy: Send + Sync { +/// +/// By default, transaction runners use [`BasicTransactionRetryPolicy`] with no +/// attempt count or timeout limits, retrying aborted transactions until completion +/// or runner timeout. +pub trait TransactionRetryPolicy: Debug + Send + Sync { /// Evaluates whether an aborted transaction should be retried. /// /// * `error` the `Aborted` error that was raised. Note that this policy @@ -35,6 +62,18 @@ pub trait TransactionRetryPolicy: Send + Sync { fn on_abort(&self, error: Error, attempts: u32, elapsed: Duration) -> RetryResult; } +impl TransactionRetryPolicy for Box { + fn on_abort(&self, error: Error, attempts: u32, elapsed: Duration) -> RetryResult { + (**self).on_abort(error, attempts, elapsed) + } +} + +impl TransactionRetryPolicy for Arc { + fn on_abort(&self, error: Error, attempts: u32, elapsed: Duration) -> RetryResult { + (**self).on_abort(error, attempts, elapsed) + } +} + /// Policy for automatically retrying a transaction when it is aborted based on /// the number of attempts and total elapsed time. #[derive(Clone, Debug)] @@ -264,15 +303,49 @@ pub(crate) mod tests { #[test] fn auto_traits() { + use std::fmt::Debug; static_assertions::assert_impl_all!( BasicTransactionRetryPolicy: Send, Sync, Unpin, Clone, - std::fmt::Debug, + Debug, Default, TransactionRetryPolicy, ); + static_assertions::assert_impl_all!( + Box: TransactionRetryPolicy, + Debug, + Send, + Sync, + ); + static_assertions::assert_impl_all!( + Arc: TransactionRetryPolicy, + Debug, + Send, + Sync, + ); + } + + #[test] + fn box_and_arc_dispatch_on_abort() { + let boxed: Box = + Box::new(BasicTransactionRetryPolicy::new().with_max_attempts(2)); + let error = create_aborted_error(None); + let result = boxed.on_abort(error, 1, Duration::from_millis(10)); + assert!( + matches!(result, RetryResult::Continue(_)), + "Box should forward on_abort to inner policy" + ); + + let arc: Arc = + Arc::new(BasicTransactionRetryPolicy::new().with_max_attempts(2)); + let error = create_aborted_error(None); + let result = arc.on_abort(error, 1, Duration::from_millis(10)); + assert!( + matches!(result, RetryResult::Continue(_)), + "Arc should forward on_abort to inner policy" + ); } #[test] @@ -478,6 +551,7 @@ pub(crate) mod tests { #[tokio::test(start_paused = true)] async fn retry_aborted_with_custom_policy() { + #[derive(Debug)] struct CustomPolicy; impl TransactionRetryPolicy for CustomPolicy { fn on_abort(&self, error: Error, attempts: u32, _elapsed: Duration) -> RetryResult { diff --git a/src/spanner/src/transaction_runner.rs b/src/spanner/src/transaction_runner.rs index 134f021ca1..42c04f63bd 100644 --- a/src/spanner/src/transaction_runner.rs +++ b/src/spanner/src/transaction_runner.rs @@ -15,6 +15,7 @@ use crate::channel_pool::TransactionAffinity; use crate::database_client::DatabaseClient; use crate::model::CommitResponse; +use crate::model::commit_response::CommitStats; use crate::model::request_options::Priority; use crate::model::transaction_options::IsolationLevel; use crate::model::transaction_options::read_write::ReadLockMode; @@ -29,7 +30,7 @@ use std::sync::Arc; use std::time::Duration as StdDuration; use tokio::time::Instant; -use wkt::Duration; +use wkt::{Duration, Timestamp}; /// A builder for a [TransactionRunner] for a read/write transaction. /// @@ -52,6 +53,7 @@ use wkt::Duration; /// /// Spanner can abort any read/write transaction at any time. A [TransactionRunner] /// automatically retries aborted transactions according to the configured retry policy. +#[derive(Debug)] pub struct TransactionRunnerBuilder { builder: ReadWriteTransactionBuilder, retry_policy: Box, @@ -506,7 +508,34 @@ impl TransactionRunnerBuilder { } /// Result of a read/write transaction executed by a [TransactionRunner]. -#[derive(Debug)] +/// +/// Contains both the application-defined return value of the closure and the +/// [`CommitResponse`] returned by the Spanner service upon successful commit. +/// +/// # Example +/// ``` +/// # use google_cloud_spanner::client::Spanner; +/// # use google_cloud_spanner::statement::Statement; +/// # async fn example(client: Spanner) -> Result<(), google_cloud_spanner::Error> { +/// let db_client = client.database_client("projects/p/instances/i/databases/d").build().await?; +/// let runner = db_client.read_write_transaction().build().await?; +/// +/// let tx_result = runner +/// .run(async |transaction| { +/// let statement = Statement::builder("UPDATE Users SET Active = true WHERE Id = 1").build(); +/// let updated_rows = transaction.execute_update(statement).await?; +/// Ok(updated_rows) +/// }) +/// .await?; +/// +/// println!("Updated {} rows", tx_result.result); +/// if let Some(timestamp) = tx_result.commit_timestamp() { +/// println!("Committed at: {timestamp:?}"); +/// } +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone, Debug, PartialEq)] #[non_exhaustive] pub struct TransactionResult { /// The result returned by the closure executed within the transaction. @@ -515,7 +544,38 @@ pub struct TransactionResult { pub commit_response: CommitResponse, } +impl TransactionResult { + /// Creates a new `TransactionResult` with the given result and commit response. + pub fn new(result: T, commit_response: CommitResponse) -> Self { + Self { + result, + commit_response, + } + } + + /// Consumes the `TransactionResult`, returning the closure result. + pub fn into_inner(self) -> T { + self.result + } + + /// Consumes the `TransactionResult`, returning both the closure result and commit response. + pub fn into_parts(self) -> (T, CommitResponse) { + (self.result, self.commit_response) + } + + /// Returns the Cloud Spanner timestamp at which the transaction committed, if available. + pub fn commit_timestamp(&self) -> Option { + self.commit_response.commit_timestamp + } + + /// Returns the statistics about the commit, if requested and returned by Spanner. + pub fn commit_stats(&self) -> Option<&CommitStats> { + self.commit_response.commit_stats.as_ref() + } +} + /// A runner for read/write transactions. Aborted transactions are automatically retried. +#[derive(Debug)] pub struct TransactionRunner { builder: ReadWriteTransactionBuilder, retry_policy: Box, @@ -713,6 +773,7 @@ mod tests { use spanner_grpc_mock::google::spanner::v1::mutation::Operation; use spanner_grpc_mock::google::spanner::v1::transaction_options::Mode; use spanner_grpc_mock::google::spanner::v1::transaction_selector::Selector as ProtoSelector; + use std::fmt::Debug; use std::net::SocketAddr; use std::sync::Mutex; use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; @@ -783,8 +844,63 @@ mod tests { #[test] fn auto_traits() { - static_assertions::assert_impl_all!(TransactionRunnerBuilder: Send, Sync); - static_assertions::assert_impl_all!(TransactionRunner: Send, Sync); + static_assertions::assert_impl_all!(TransactionRunnerBuilder: Debug, Send, Sync); + static_assertions::assert_impl_all!(TransactionRunner: Debug, Send, Sync); + static_assertions::assert_impl_all!( + TransactionResult: Clone, + Debug, + PartialEq, + Send, + Sync + ); + } + + #[test] + fn transaction_result_constructor_and_derives() { + use crate::model::CommitResponse as ModelCommitResponse; + use wkt::Timestamp; + + let commit_timestamp = Timestamp::clamp(1_234_567_890, 123); + let commit_response = ModelCommitResponse::default().set_commit_timestamp(commit_timestamp); + let transaction_result = TransactionResult::new(42_i64, commit_response.clone()); + assert_eq!( + transaction_result.result, 42_i64, + "expected result field to match input" + ); + assert_eq!( + transaction_result.commit_response, commit_response, + "expected commit_response field to match input" + ); + assert_eq!( + transaction_result.commit_timestamp(), + Some(commit_timestamp), + "expected commit_timestamp to match commit_response" + ); + assert_eq!( + transaction_result.clone(), + transaction_result, + "expected clone to equal original" + ); + let debug_representation = format!("{transaction_result:?}"); + assert!( + debug_representation.contains("42"), + "debug representation should contain result value" + ); + + let (unwrapped_result, unwrapped_response) = transaction_result.clone().into_parts(); + assert_eq!( + unwrapped_result, 42_i64, + "expected into_parts result to match" + ); + assert_eq!( + unwrapped_response, commit_response, + "expected into_parts response to match" + ); + assert_eq!( + transaction_result.into_inner(), + 42_i64, + "expected into_inner to return result value" + ); } #[tokio_test_no_panics] @@ -1278,9 +1394,14 @@ mod tests { let mut last_val = None; while let Some(row_res) = rs.next().await { let row = row_res?; - last_val = Some(row.raw_values()[0].as_string().to_string()); + last_val = Some( + row.raw_values()[0] + .as_str() + .expect("raw value should be string") + .to_string(), + ); } - Ok(last_val.unwrap()) + Ok(last_val.expect("at least one row should have been returned")) }) .await?; @@ -3490,6 +3611,7 @@ mod tests { use crate::transaction_retry_policy::TransactionRetryPolicy; use google_cloud_gax::retry_result::RetryResult; + #[derive(Debug)] struct GuardCheckRetryPolicy { pinned_entry: Arc>>>, active_rw_counts_during_backoff: Arc>>, diff --git a/src/spanner/src/value.rs b/src/spanner/src/value.rs index 7439349412..01bcbeca05 100644 --- a/src/spanner/src/value.rs +++ b/src/spanner/src/value.rs @@ -18,11 +18,11 @@ pub(crate) const SPANNER_TIMESTAMP_FORMAT: &[time::format_description::FormatIte pub(crate) const SPANNER_DATE_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!("[year]-[month]-[day]"); -pub use crate::from_value::FromValue; +pub use crate::from_value::{ConvertError, FromValue}; pub use crate::to_value::ToValue; pub use crate::types::{Type, TypeCode}; pub use google_cloud_type::model::Date; -pub use wkt::Timestamp; +pub use wkt::{Duration, Timestamp}; use prost_types::Value as ProtoValue; use serde_json::Number as JsonNumber; @@ -86,69 +86,192 @@ impl Value { } } - /// Returns the underlying string value if the kind is String. - pub fn try_as_string(&self) -> Option<&str> { + /// Returns `true` if the value is null, or `false` otherwise. + /// + /// # Example + /// + /// ``` + /// use google_cloud_spanner::value::Value; + /// + /// let value = Value::null(); + /// assert!(value.is_null()); + /// + /// let not_null = Value::from("hello"); + /// assert!(!not_null.is_null()); + /// ``` + pub fn is_null(&self) -> bool { + matches!( + self.0.kind, + Some(prost_types::value::Kind::NullValue(_)) | None + ) + } + + /// Returns the underlying string slice if the value is a string, or `None` otherwise. + /// + /// # Example + /// + /// ``` + /// use google_cloud_spanner::value::Value; + /// + /// let value = Value::from("hello"); + /// assert_eq!(value.as_str(), Some("hello")); + /// + /// let number_value = Value::from(42.5); + /// assert_eq!(number_value.as_str(), None); + /// ``` + pub fn as_str(&self) -> Option<&str> { match &self.0.kind { - Some(prost_types::value::Kind::StringValue(s)) => Some(s), + Some(prost_types::value::Kind::StringValue(string_value)) => Some(string_value), _ => None, } } - /// Returns the underlying string value. Panics if the kind is not String. - pub fn as_string(&self) -> &str { - self.try_as_string().expect("value is not a String") + /// Returns the underlying string slice if the value is a string, or `None` otherwise. + /// + /// # Deprecation + /// + /// Use [`as_str`](Value::as_str) instead. + #[deprecated(note = "use `as_str` instead")] + pub fn as_string(&self) -> Option<&str> { + self.as_str() } - /// Returns the underlying bool value if the kind is Bool. - pub fn try_as_bool(&self) -> Option { + /// Returns the underlying string value if the kind is String. + /// + /// # Deprecation + /// + /// Use [`as_str`](Value::as_str) instead. + #[deprecated(note = "use `as_str` instead")] + pub fn try_as_string(&self) -> Option<&str> { + self.as_str() + } + + /// Returns the underlying boolean value if the value is a boolean, or `None` otherwise. + /// + /// # Example + /// + /// ``` + /// use google_cloud_spanner::value::Value; + /// + /// let value = Value::from(true); + /// assert_eq!(value.as_bool(), Some(true)); + /// + /// let string_value = Value::from("true"); + /// assert_eq!(string_value.as_bool(), None); + /// ``` + pub fn as_bool(&self) -> Option { match &self.0.kind { - Some(prost_types::value::Kind::BoolValue(b)) => Some(*b), + Some(prost_types::value::Kind::BoolValue(bool_value)) => Some(*bool_value), _ => None, } } - /// Returns the underlying bool value. Panics if the kind is not Bool. - pub fn as_bool(&self) -> bool { - self.try_as_bool().expect("value is not a Bool") + /// Returns the underlying bool value if the kind is Bool. + /// + /// # Deprecation + /// + /// Use [`as_bool`](Value::as_bool) instead. + #[deprecated(note = "use `as_bool` instead")] + pub fn try_as_bool(&self) -> Option { + self.as_bool() } - /// Returns the underlying number value if the kind is Number. - pub fn try_as_f64(&self) -> Option { + /// Returns the underlying number value as an `f64` if the value is a number, or `None` otherwise. + /// + /// # Example + /// + /// ``` + /// use google_cloud_spanner::value::Value; + /// + /// let value = Value::from(42.5); + /// assert_eq!(value.as_f64(), Some(42.5)); + /// + /// let string_value = Value::from("42.5"); + /// assert_eq!(string_value.as_f64(), None); + /// ``` + /// + /// # Non-Finite Floats + /// + /// Spanner encodes non-finite IEEE 754 floats (`NaN`, `Infinity`, and `-Infinity`) + /// as string values on the wire. Consequently, `as_f64()` returns `None` for non-finite + /// floats. To decode floating point values with full IEEE 754 support including `NaN` + /// and infinity, use [`Row::try_get`](crate::row::Row::try_get) or [`FromValue`]. + pub fn as_f64(&self) -> Option { match &self.0.kind { - Some(prost_types::value::Kind::NumberValue(n)) => Some(*n), + Some(prost_types::value::Kind::NumberValue(number_value)) => Some(*number_value), _ => None, } } - /// Returns the underlying number value as an `f64`. Panics if the kind is not Number. - pub fn as_f64(&self) -> f64 { - self.try_as_f64().expect("value is not a Number") + /// Returns the underlying number value if the kind is Number. + /// + /// # Deprecation + /// + /// Use [`as_f64`](Value::as_f64) instead. + #[deprecated(note = "use `as_f64` instead")] + pub fn try_as_f64(&self) -> Option { + self.as_f64() } - /// Returns the underlying struct value as a map of Values if the kind is Struct. - pub fn try_as_struct(&self) -> Option<&Struct> { + /// Returns a reference to the underlying [`Struct`] if the value is a struct, or `None` otherwise. + /// + /// # Example + /// + /// ``` + /// use google_cloud_spanner::value::Value; + /// + /// let value = Value::null(); + /// assert_eq!(value.as_struct(), None); + /// ``` + pub fn as_struct(&self) -> Option<&Struct> { match &self.0.kind { - Some(prost_types::value::Kind::StructValue(s)) => Some(Struct::from_ref(s)), + Some(prost_types::value::Kind::StructValue(struct_value)) => { + Some(Struct::from_ref(struct_value)) + } _ => None, } } - /// Returns the underlying struct value. Panics if the kind is not Struct. - pub fn as_struct(&self) -> &Struct { - self.try_as_struct().expect("value is not a Struct") + /// Returns the underlying struct value as a map of Values if the kind is Struct. + /// + /// # Deprecation + /// + /// Use [`as_struct`](Value::as_struct) instead. + #[deprecated(note = "use `as_struct` instead")] + pub fn try_as_struct(&self) -> Option<&Struct> { + self.as_struct() } - /// Returns the underlying list value as a vector of Values if the kind is List. - pub fn try_as_list(&self) -> Option<&List> { + /// Returns a reference to the underlying [`List`] if the value is a list, or `None` otherwise. + /// + /// # Example + /// + /// ``` + /// use google_cloud_spanner::value::Value; + /// + /// let value = Value::from(vec![1i64, 2i64]); + /// assert_eq!(value.as_list().map(|list| list.len()), Some(2)); + /// + /// let null_value = Value::null(); + /// assert_eq!(null_value.as_list(), None); + /// ``` + pub fn as_list(&self) -> Option<&List> { match &self.0.kind { - Some(prost_types::value::Kind::ListValue(l)) => Some(List::from_ref(l)), + Some(prost_types::value::Kind::ListValue(list_value)) => { + Some(List::from_ref(list_value)) + } _ => None, } } - /// Returns the underlying list value. Panics if the kind is not List. - pub fn as_list(&self) -> &List { - self.try_as_list().expect("value is not a List") + /// Returns the underlying list value as a vector of Values if the kind is List. + /// + /// # Deprecation + /// + /// Use [`as_list`](Value::as_list) instead. + #[deprecated(note = "use `as_list` instead")] + pub fn try_as_list(&self) -> Option<&List> { + self.as_list() } } @@ -261,6 +384,7 @@ mod tests { use serde_json::Map as JsonMap; use serde_json::Value as JsonValue; use std::collections::BTreeMap; + use std::fmt::Debug; use std::hash::Hash; #[test] @@ -409,10 +533,9 @@ mod tests { kind: Some(prost_types::value::Kind::StringValue("NaN".to_string())), }); assert_eq!(nan_value.kind(), Kind::String); - assert_eq!(nan_value.try_as_string(), Some("NaN")); - assert_eq!(nan_value.as_string(), "NaN"); + assert_eq!(nan_value.as_str(), Some("NaN")); assert_eq!( - nan_value.try_as_f64(), + nan_value.as_f64(), None, "StringValue('NaN') is represented as Kind::String in untyped Value; typed access is via FromValue" ); @@ -423,10 +546,9 @@ mod tests { )), }); assert_eq!(infinity_value.kind(), Kind::String); - assert_eq!(infinity_value.try_as_string(), Some("Infinity")); - assert_eq!(infinity_value.as_string(), "Infinity"); + assert_eq!(infinity_value.as_str(), Some("Infinity")); assert_eq!( - infinity_value.try_as_f64(), + infinity_value.as_f64(), None, "StringValue('Infinity') is represented as Kind::String in untyped Value; typed access is via FromValue" ); @@ -437,46 +559,61 @@ mod tests { )), }); assert_eq!(negative_infinity_value.kind(), Kind::String); - assert_eq!(negative_infinity_value.try_as_string(), Some("-Infinity")); - assert_eq!(negative_infinity_value.as_string(), "-Infinity"); + assert_eq!(negative_infinity_value.as_str(), Some("-Infinity")); assert_eq!( - negative_infinity_value.try_as_f64(), + negative_infinity_value.as_f64(), None, "StringValue('-Infinity') is represented as Kind::String in untyped Value; typed access is via FromValue" ); } #[test] - fn test_value_kind_and_accessors() { - let v_null = Value(ProtoValue { + fn value_kind_and_accessors() { + let null_value = Value(ProtoValue { kind: Some(prost_types::value::Kind::NullValue(0)), }); - assert_eq!(v_null.kind(), Kind::Null); - assert!(v_null.try_as_string().is_none()); - - let v_string = Value(ProtoValue { + assert_eq!(null_value.kind(), Kind::Null); + assert!(null_value.is_null()); + assert_eq!(null_value.as_str(), None); + assert_eq!(null_value.as_bool(), None); + assert_eq!(null_value.as_f64(), None); + assert_eq!(null_value.as_struct(), None); + assert_eq!(null_value.as_list(), None); + + let string_value = Value(ProtoValue { kind: Some(prost_types::value::Kind::StringValue("foo".to_string())), }); - assert_eq!(v_string.kind(), Kind::String); - assert_eq!(v_string.try_as_string(), Some("foo")); - assert_eq!(v_string.as_string(), "foo"); - assert!(v_string.try_as_bool().is_none()); - - let v_bool = Value(ProtoValue { + assert_eq!(string_value.kind(), Kind::String); + assert!(!string_value.is_null()); + assert_eq!(string_value.as_str(), Some("foo")); + assert_eq!(string_value.as_bool(), None); + assert_eq!(string_value.as_f64(), None); + assert_eq!(string_value.as_struct(), None); + assert_eq!(string_value.as_list(), None); + + let bool_value = Value(ProtoValue { kind: Some(prost_types::value::Kind::BoolValue(true)), }); - assert_eq!(v_bool.kind(), Kind::Bool); - assert_eq!(v_bool.try_as_bool(), Some(true)); - assert!(v_bool.as_bool()); - - let v_number = Value(ProtoValue { + assert_eq!(bool_value.kind(), Kind::Bool); + assert!(!bool_value.is_null()); + assert_eq!(bool_value.as_bool(), Some(true)); + assert_eq!(bool_value.as_str(), None); + assert_eq!(bool_value.as_f64(), None); + assert_eq!(bool_value.as_struct(), None); + assert_eq!(bool_value.as_list(), None); + + let number_value = Value(ProtoValue { kind: Some(prost_types::value::Kind::NumberValue(42.0)), }); - assert_eq!(v_number.kind(), Kind::Number); - assert_eq!(v_number.try_as_f64(), Some(42.0)); - assert_eq!(v_number.as_f64(), 42.0); - - let v_list = Value(ProtoValue { + assert_eq!(number_value.kind(), Kind::Number); + assert!(!number_value.is_null()); + assert_eq!(number_value.as_f64(), Some(42.0)); + assert_eq!(number_value.as_str(), None); + assert_eq!(number_value.as_bool(), None); + assert_eq!(number_value.as_struct(), None); + assert_eq!(number_value.as_list(), None); + + let list_value = Value(ProtoValue { kind: Some(prost_types::value::Kind::ListValue( prost_types::ListValue { values: vec![ProtoValue { @@ -485,15 +622,22 @@ mod tests { }, )), }); - assert_eq!(v_list.kind(), Kind::List); - let list = v_list.try_as_list().unwrap(); + assert_eq!(list_value.kind(), Kind::List); + assert!(!list_value.is_null()); + assert_eq!(list_value.as_str(), None); + assert_eq!(list_value.as_bool(), None); + assert_eq!(list_value.as_f64(), None); + assert_eq!(list_value.as_struct(), None); + let list = list_value.as_list().expect("list must be Some"); assert_eq!(list.len(), 1); - assert_eq!(list.get(0).unwrap().try_as_f64(), Some(1.0)); - assert_eq!(v_list.as_list().len(), 1); + assert_eq!( + list.get(0).expect("element 0 must exist").as_f64(), + Some(1.0) + ); - let v_struct = Value(ProtoValue { + let struct_value = Value(ProtoValue { kind: Some(prost_types::value::Kind::StructValue(prost_types::Struct { - fields: std::collections::BTreeMap::from([( + fields: BTreeMap::from([( "a".to_string(), ProtoValue { kind: Some(prost_types::value::Kind::NumberValue(1.0)), @@ -501,24 +645,80 @@ mod tests { )]), })), }); - assert_eq!(v_struct.kind(), Kind::Struct); - let map = v_struct.try_as_struct().unwrap(); + assert_eq!(struct_value.kind(), Kind::Struct); + assert!(!struct_value.is_null()); + assert_eq!(struct_value.as_str(), None); + assert_eq!(struct_value.as_bool(), None); + assert_eq!(struct_value.as_f64(), None); + assert_eq!(struct_value.as_list(), None); + let map = struct_value.as_struct().expect("struct must be Some"); assert_eq!(map.len(), 1); - assert_eq!(map.get("a").unwrap().try_as_f64(), Some(1.0)); - assert_eq!(v_struct.as_struct().len(), 1); + assert_eq!( + map.get("a").expect("field 'a' must exist").as_f64(), + Some(1.0) + ); + } + + #[test] + fn is_null() { + assert!(Value::null().is_null()); + assert!(Value(ProtoValue { kind: None }).is_null()); + assert!( + Value(ProtoValue { + kind: Some(prost_types::value::Kind::NullValue(0)), + }) + .is_null() + ); + assert!(!Value::from("hello").is_null()); + assert!(!Value::from(true).is_null()); + assert!(!Value::from(42.5).is_null()); + } + + #[test] + #[allow(deprecated)] + fn deprecated_try_as_accessors_match_as_accessors() { + let string_value = Value(ProtoValue { + kind: Some(prost_types::value::Kind::StringValue("test".to_string())), + }); + assert_eq!(string_value.try_as_string(), string_value.as_str()); + assert_eq!(string_value.as_string(), string_value.as_str()); + + let bool_value = Value(ProtoValue { + kind: Some(prost_types::value::Kind::BoolValue(false)), + }); + assert_eq!(bool_value.try_as_bool(), bool_value.as_bool()); + + let number_value = Value(ProtoValue { + kind: Some(prost_types::value::Kind::NumberValue(42.5)), + }); + assert_eq!(number_value.try_as_f64(), number_value.as_f64()); + + let list_value = Value(ProtoValue { + kind: Some(prost_types::value::Kind::ListValue( + prost_types::ListValue::default(), + )), + }); + assert_eq!(list_value.try_as_list(), list_value.as_list()); + + let struct_value = Value(ProtoValue { + kind: Some(prost_types::value::Kind::StructValue( + prost_types::Struct::default(), + )), + }); + assert_eq!(struct_value.try_as_struct(), struct_value.as_struct()); } #[test] - fn test_auto_traits() { - static_assertions::assert_impl_all!(Value: Send, Sync, Clone, std::fmt::Debug); - static_assertions::assert_impl_all!(Struct: Send, Sync, Clone, std::fmt::Debug); - static_assertions::assert_impl_all!(List: Send, Sync, Clone, std::fmt::Debug); + fn auto_traits() { + static_assertions::assert_impl_all!(Value: Send, Sync, Clone, Debug); + static_assertions::assert_impl_all!(Struct: Send, Sync, Clone, Debug); + static_assertions::assert_impl_all!(List: Send, Sync, Clone, Debug); static_assertions::assert_impl_all!( Kind: Send, Sync, Clone, Copy, - std::fmt::Debug, + Debug, PartialEq, Eq, Hash diff --git a/src/spanner/src/write_only_transaction.rs b/src/spanner/src/write_only_transaction.rs index 6867cba0f1..7b1882f26a 100644 --- a/src/spanner/src/write_only_transaction.rs +++ b/src/spanner/src/write_only_transaction.rs @@ -32,6 +32,7 @@ use std::sync::{Arc, Mutex}; use wkt::Duration; /// A builder for [WriteOnlyTransaction]. +#[derive(Debug)] pub struct WriteOnlyTransactionBuilder { client: DatabaseClient, transaction_tag: Option, @@ -355,6 +356,7 @@ impl WriteOnlyTransactionBuilder { /// A write-only transaction. /// /// A write-only transaction can be used to execute blind writes. +#[derive(Debug)] pub struct WriteOnlyTransaction { pub(crate) session_name: String, client: DatabaseClient, @@ -618,8 +620,14 @@ mod tests { use spanner_grpc_mock::google::spanner::v1::Transaction; use spanner_grpc_mock::google::spanner::v1::commit_response::CommitStats; use spanner_grpc_mock::google::spanner::v1::transaction_options::Mode; + use std::fmt::Debug; use std::time::Duration as StdDuration; - use wkt::Duration; + + #[test] + fn auto_traits() { + static_assertions::assert_impl_all!(WriteOnlyTransactionBuilder: Debug, Send, Sync); + static_assertions::assert_impl_all!(WriteOnlyTransaction: Debug, Send, Sync); + } pub(crate) async fn setup_db_client( mock: spanner_grpc_mock::MockSpanner, diff --git a/src/spanner/tests/execute_query.rs b/src/spanner/tests/execute_query.rs index ef71674bb3..3f03c52ece 100644 --- a/src/spanner/tests/execute_query.rs +++ b/src/spanner/tests/execute_query.rs @@ -103,8 +103,8 @@ async fn test_execute_query() -> anyhow::Result<()> { let row = rs.next().await.expect("has row").expect("has valid row"); // Assert 1 row, 1 column with value "1" - let val: &str = row.raw_values()[0].as_string(); - assert_eq!(val, "1"); + let val = row.raw_values()[0].as_str(); + assert_eq!(val, Some("1")); assert!(rs.next().await.is_none()); diff --git a/src/spanner/tests/unnameable_types_visibility.rs b/src/spanner/tests/unnameable_types_visibility.rs new file mode 100644 index 0000000000..d9c704a35a --- /dev/null +++ b/src/spanner/tests/unnameable_types_visibility.rs @@ -0,0 +1,207 @@ +// Copyright 2026 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 +// +// https://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. + +//! Regression test verifying that previously unnameable public types +//! (`TransactionResult`, `TransactionRetryPolicy`, `ColumnIndex`, and `RowError`) +//! can be named, implemented, or bound from an external-crate perspective. + +use google_cloud_spanner::Error; +use google_cloud_spanner::error::{ConvertError, RowError}; +use google_cloud_spanner::model::CommitResponse; +use google_cloud_spanner::result::{ + ColumnIndex, RowError as ResultRowError, TransactionResult as ResultTransactionResult, +}; +use google_cloud_spanner::retry_policy::{ + BasicTransactionRetryPolicy as RetryPolicyBasicTransactionRetryPolicy, RetryResult, + TransactionRetryPolicy as RetryPolicyTransactionRetryPolicy, +}; +use google_cloud_spanner::transaction::{ + BasicTransactionRetryPolicy, RetryResult as TransactionRetryResult, TransactionResult, + TransactionRetryPolicy, +}; +use google_cloud_spanner::value::ConvertError as ValueConvertError; +use std::sync::Arc; +use std::time::Duration; +use wkt::Timestamp; + +#[test] +fn external_can_name_and_construct_transaction_result() { + let commit_timestamp = Timestamp::clamp(1_234_567_890, 500); + let commit_response = CommitResponse::default().set_commit_timestamp(commit_timestamp); + let transaction_result: TransactionResult = + TransactionResult::new("success".to_string(), commit_response.clone()); + + assert_eq!( + transaction_result.result, "success", + "result field should match input" + ); + assert_eq!( + transaction_result.commit_response, commit_response, + "commit_response field should match input" + ); + assert_eq!( + transaction_result.commit_timestamp(), + Some(commit_timestamp), + "commit_timestamp accessor should return expected timestamp" + ); + assert!( + transaction_result.commit_stats().is_none(), + "commit_stats accessor should return None when not requested" + ); + + // Verify re-export via google_cloud_spanner::result + let result_from_alias: ResultTransactionResult = transaction_result.clone(); + assert_eq!( + result_from_alias, transaction_result, + "result_from_alias should equal transaction_result" + ); + + // Verify consuming accessors + let (inner_result, inner_response) = transaction_result.clone().into_parts(); + assert_eq!( + inner_result, "success", + "into_parts result should match input" + ); + assert_eq!( + inner_response, commit_response, + "into_parts response should match input" + ); + assert_eq!( + transaction_result.into_inner(), + "success", + "into_inner should return result value" + ); +} + +#[derive(Debug)] +struct ExternalCustomRetryPolicy; + +impl TransactionRetryPolicy for ExternalCustomRetryPolicy { + fn on_abort(&self, error: Error, attempts: u32, _elapsed: Duration) -> RetryResult { + if attempts < 2 { + RetryResult::Continue(error) + } else { + RetryResult::Exhausted(error) + } + } +} + +#[test] +fn external_can_implement_transaction_retry_policy() { + let custom_policy = ExternalCustomRetryPolicy; + let boxed_policy: Box = Box::new(custom_policy); + + // Verify Box and Arc blanket implementation for TransactionRetryPolicy + let _wrapped_box: Box = Box::new(boxed_policy); + let arc_policy: Arc = Arc::new(ExternalCustomRetryPolicy); + let _wrapped_arc: Box = Box::new(arc_policy); + + // Verify re-exports via google_cloud_spanner::retry_policy and google_cloud_spanner::transaction + let _basic_from_retry_policy = RetryPolicyBasicTransactionRetryPolicy::new(); + let _boxed_from_retry_policy: Box = + Box::new(BasicTransactionRetryPolicy::new()); + + // Verify RetryResult can also be named from transaction module + let _: fn(Error) -> TransactionRetryResult = TransactionRetryResult::Continue; +} + +#[test] +fn external_can_use_column_index_as_bound() { + fn accepts_column_index(_index: I) -> bool { + true + } + assert!( + accepts_column_index("column_name"), + "&str should satisfy ColumnIndex" + ); + assert!( + accepts_column_index("column_name".to_string()), + "String should satisfy ColumnIndex" + ); + let string_column = "column_name".to_string(); + assert!( + accepts_column_index(&string_column), + "&String should satisfy ColumnIndex" + ); + assert!( + accepts_column_index(0_usize), + "usize should satisfy ColumnIndex" + ); +} + +#[test] +fn external_can_name_and_match_row_error() { + let column_error = RowError::ColumnNotFound("missing_col".to_string()); + assert_eq!( + column_error.to_string(), + "Could not find column: 'missing_col'", + "RowError display format should match 'Could not find column: \\'missing_col\\''" + ); + + match &column_error { + RowError::ColumnNotFound(name) => { + assert_eq!(name, "missing_col", "column name should match"); + } + RowError::IndexOutOfRange { index, len } => { + panic!("unexpected IndexOutOfRange: index={index}, len={len}"); + } + _ => panic!("unexpected error variant"), + } + + let index_error = RowError::IndexOutOfRange { index: 3, len: 2 }; + match &index_error { + RowError::IndexOutOfRange { index, len } => { + assert_eq!(*index, 3, "index should match"); + assert_eq!(*len, 2, "len should match"); + } + RowError::ColumnNotFound(name) => { + panic!("unexpected ColumnNotFound: {name}"); + } + _ => panic!("unexpected error variant"), + } + + // Verify re-export via google_cloud_spanner::result::RowError + let result_row_error: ResultRowError = column_error.clone(); + assert_eq!( + result_row_error, column_error, + "re-exported ResultRowError should equal original RowError" + ); +} + +#[test] +fn external_can_extract_errors() { + let row_error = RowError::ColumnNotFound("missing".to_string()); + let error = Error::deser(row_error.clone()); + let extracted_row = RowError::extract(&error).expect("should extract RowError"); + assert_eq!( + *extracted_row, row_error, + "extracted RowError should match original" + ); + + let convert_error = ConvertError::NotNull; + let error_from_convert = Error::deser(convert_error); + let extracted_convert = + ConvertError::extract(&error_from_convert).expect("should extract ConvertError"); + assert!( + matches!(extracted_convert, ConvertError::NotNull), + "extracted ConvertError should match NotNull" + ); + + // Verify ConvertError re-export via google_cloud_spanner::value + let _value_convert_error: ValueConvertError = ConvertError::NotNull; + + // Verify TlsError re-export via google_cloud_spanner::error + let _tls_error: google_cloud_spanner::error::TlsError = + google_cloud_spanner::error::TlsError::MissingClientKey; +} diff --git a/src/storage/src/generated/gapic/transport.rs b/src/storage/src/generated/gapic/transport.rs index 2d69aa4cfa..80bf3f84cb 100644 --- a/src/storage/src/generated/gapic/transport.rs +++ b/src/storage/src/generated/gapic/transport.rs @@ -74,7 +74,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new("google.storage.v2.Storage", "DeleteBucket")); @@ -142,7 +142,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new("google.storage.v2.Storage", "GetBucket")); @@ -210,7 +210,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new("google.storage.v2.Storage", "CreateBucket")); @@ -302,7 +302,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new("google.storage.v2.Storage", "ListBuckets")); @@ -370,7 +370,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new( @@ -443,7 +443,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new("google.storage.v2.Storage", "UpdateBucket")); @@ -517,7 +517,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new( @@ -594,7 +594,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new("google.storage.v2.Storage", "DeleteObject")); @@ -662,7 +662,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new( @@ -733,7 +733,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new("google.storage.v2.Storage", "GetObject")); @@ -801,7 +801,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new("google.storage.v2.Storage", "UpdateObject")); @@ -875,7 +875,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new("google.storage.v2.Storage", "ListObjects")); @@ -943,7 +943,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new( @@ -1034,7 +1034,7 @@ impl super::stub::StorageControl for StorageControl { grpc::tonic::{Extensions, GrpcMethod}, prost::ToProto, }; - let options = google_cloud_gax::options::internal::set_default_idempotency(options, false); + let options = req.resolve_idempotency(options); let extensions = { let mut e = Extensions::new(); e.insert(GrpcMethod::new("google.storage.v2.Storage", "MoveObject")); diff --git a/src/storage/src/idempotency.rs b/src/storage/src/idempotency.rs new file mode 100644 index 0000000000..08b5644df4 --- /dev/null +++ b/src/storage/src/idempotency.rs @@ -0,0 +1,307 @@ +// Copyright 2026 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 +// +// https://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. + +//! Idempotency rules and deduplication tokens for Cloud Storage requests. +//! +//! `librarian.yaml` sets `idempotency_hook: resolve_idempotency` for +//! `google/storage/v2`, so every generated `google.storage.v2.Storage` RPC calls +//! `req.resolve_idempotency(options)`. A new RPC needs an impl below; the +//! generated code does not compile without one. +//! +//! The rules follow the [GCS retry strategy]. Negative preconditions +//! (`*_not_match`) never make a request idempotent, because they can match again +//! on a retry. +//! +//! Handwritten operations (uploads) call [mutation] once, before their retry +//! loop, so every attempt sends the same token. +//! +//! [GCS retry strategy]: https://cloud.google.com/storage/docs/retry-strategy#idempotency-operations + +use google_cloud_gax::options::RequestOptions; +use google_cloud_gax::options::internal::{RequestOptionsExt, set_default_idempotency}; +use http::{HeaderMap, HeaderValue}; + +/// The header Cloud Storage uses to deduplicate retried mutations. +pub(crate) const IDEMPOTENCY_TOKEN_HEADER: &str = "x-goog-gcs-idempotency-token"; + +/// Reads and lists are always idempotent and never carry a deduplication token. +fn read(options: RequestOptions) -> RequestOptions { + set_default_idempotency(options, true) +} + +/// Mutations are retried only if idempotent, and then carry a deduplication token. +/// +/// An explicit `with_idempotency()` from the application takes precedence over +/// `idempotent`. A token already in the headers (from a previous call or from the +/// application) is kept. +pub(crate) fn mutation(options: RequestOptions, idempotent: bool) -> RequestOptions { + let options = set_default_idempotency(options, idempotent); + if options.idempotent() != Some(true) { + return options; + } + if options + .get_extension::() + .is_some_and(|h| h.contains_key(IDEMPOTENCY_TOKEN_HEADER)) + { + return options; + } + let mut headers = options + .get_extension::() + .cloned() + .unwrap_or_default(); + let token = HeaderValue::try_from(uuid::Uuid::new_v4().to_string()) + .expect("a hyphenated UUID is always a valid header value"); + headers.insert(IDEMPOTENCY_TOKEN_HEADER, token); + options.insert_extension(headers) +} + +impl crate::model::GetObjectRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + read(options) + } +} + +impl crate::model::ListObjectsRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + read(options) + } +} + +impl crate::model::GetBucketRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + read(options) + } +} + +impl crate::model::ListBucketsRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + read(options) + } +} + +impl crate::model::CreateBucketRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + mutation(options, true) + } +} + +impl crate::model::DeleteBucketRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + mutation(options, true) + } +} + +impl crate::model::LockBucketRetentionPolicyRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + mutation(options, true) + } +} + +impl crate::model::UpdateBucketRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + mutation(options, self.if_metageneration_match.is_some()) + } +} + +impl crate::model::ComposeObjectRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + mutation(options, self.if_generation_match.is_some()) + } +} + +impl crate::model::DeleteObjectRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + mutation( + options, + self.generation > 0 || self.if_generation_match.is_some(), + ) + } +} + +impl crate::model::RestoreObjectRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + // `generation` is a required field selecting the soft-deleted version to + // restore. Restoring creates a new live generation, so only + // `if_generation_match` protects against a double restore. + mutation(options, self.if_generation_match.is_some()) + } +} + +impl crate::model::UpdateObjectRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + mutation(options, self.if_metageneration_match.is_some()) + } +} + +impl crate::model::RewriteObjectRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + mutation(options, self.if_generation_match.is_some()) + } +} + +impl crate::model::MoveObjectRequest { + pub(crate) fn resolve_idempotency(&self, options: RequestOptions) -> RequestOptions { + mutation(options, self.if_generation_match.is_some()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::{ + ComposeObjectRequest, CreateBucketRequest, DeleteBucketRequest, DeleteObjectRequest, + GetBucketRequest, GetObjectRequest, ListBucketsRequest, ListObjectsRequest, + LockBucketRetentionPolicyRequest, MoveObjectRequest, RestoreObjectRequest, + RewriteObjectRequest, UpdateBucketRequest, UpdateObjectRequest, + }; + use test_case::test_case; + + fn token(options: &RequestOptions) -> Option<&HeaderValue> { + options + .get_extension::() + .and_then(|h| h.get(IDEMPOTENCY_TOKEN_HEADER)) + } + + fn assert_mutation(got: RequestOptions, want: bool) { + assert_eq!(got.idempotent(), Some(want)); + assert_eq!(token(&got).is_some(), want, "token iff idempotent"); + } + + #[test] + fn reads_are_idempotent_without_token() { + for got in [ + GetObjectRequest::new().resolve_idempotency(RequestOptions::default()), + ListObjectsRequest::new().resolve_idempotency(RequestOptions::default()), + GetBucketRequest::new().resolve_idempotency(RequestOptions::default()), + ListBucketsRequest::new().resolve_idempotency(RequestOptions::default()), + ] { + assert_eq!(got.idempotent(), Some(true)); + assert!(token(&got).is_none(), "{got:?}"); + } + } + + #[test] + fn bucket_create_delete_lock_are_always_idempotent() { + for got in [ + CreateBucketRequest::new().resolve_idempotency(RequestOptions::default()), + DeleteBucketRequest::new().resolve_idempotency(RequestOptions::default()), + LockBucketRetentionPolicyRequest::new().resolve_idempotency(RequestOptions::default()), + ] { + assert_mutation(got, true); + } + } + + #[test_case(UpdateBucketRequest::new(), false; "unconditioned")] + #[test_case(UpdateBucketRequest::new().set_if_metageneration_match(1), true; "if_metageneration_match")] + #[test_case(UpdateBucketRequest::new().set_if_metageneration_not_match(1), false; "if_metageneration_not_match")] + fn update_bucket(req: UpdateBucketRequest, want: bool) { + assert_mutation(req.resolve_idempotency(RequestOptions::default()), want); + } + + #[test_case(UpdateObjectRequest::new(), false; "unconditioned")] + #[test_case(UpdateObjectRequest::new().set_if_metageneration_match(1), true; "if_metageneration_match")] + #[test_case(UpdateObjectRequest::new().set_if_metageneration_not_match(1), false; "if_metageneration_not_match")] + #[test_case(UpdateObjectRequest::new().set_if_generation_match(1), false; "if_generation_match alone")] + fn update_object(req: UpdateObjectRequest, want: bool) { + assert_mutation(req.resolve_idempotency(RequestOptions::default()), want); + } + + #[test_case(ComposeObjectRequest::new(), false; "unconditioned")] + #[test_case(ComposeObjectRequest::new().set_if_generation_match(1), true; "if_generation_match")] + #[test_case(ComposeObjectRequest::new().set_if_metageneration_match(1), false; "if_metageneration_match alone")] + fn compose_object(req: ComposeObjectRequest, want: bool) { + assert_mutation(req.resolve_idempotency(RequestOptions::default()), want); + } + + #[test_case(DeleteObjectRequest::new(), false; "unconditioned")] + #[test_case(DeleteObjectRequest::new().set_generation(1), true; "generation")] + #[test_case(DeleteObjectRequest::new().set_if_generation_match(1), true; "if_generation_match")] + #[test_case(DeleteObjectRequest::new().set_if_generation_not_match(1), false; "if_generation_not_match")] + #[test_case(DeleteObjectRequest::new().set_if_metageneration_match(1), false; "if_metageneration_match alone")] + fn delete_object(req: DeleteObjectRequest, want: bool) { + assert_mutation(req.resolve_idempotency(RequestOptions::default()), want); + } + + #[test_case(RestoreObjectRequest::new().set_generation(1), false; "generation alone")] + #[test_case(RestoreObjectRequest::new().set_generation(1).set_if_generation_match(1), true; "if_generation_match")] + #[test_case(RestoreObjectRequest::new().set_generation(1).set_if_generation_not_match(1), false; "if_generation_not_match")] + fn restore_object(req: RestoreObjectRequest, want: bool) { + assert_mutation(req.resolve_idempotency(RequestOptions::default()), want); + } + + #[test_case(RewriteObjectRequest::new(), false; "unconditioned")] + #[test_case(RewriteObjectRequest::new().set_if_generation_match(1), true; "if_generation_match")] + #[test_case(RewriteObjectRequest::new().set_if_generation_not_match(1), false; "if_generation_not_match")] + #[test_case(RewriteObjectRequest::new().set_if_source_generation_match(1), false; "if_source_generation_match alone")] + fn rewrite_object(req: RewriteObjectRequest, want: bool) { + assert_mutation(req.resolve_idempotency(RequestOptions::default()), want); + } + + #[test_case(MoveObjectRequest::new(), false; "unconditioned")] + #[test_case(MoveObjectRequest::new().set_if_generation_match(1), true; "if_generation_match")] + #[test_case(MoveObjectRequest::new().set_if_generation_not_match(1), false; "if_generation_not_match")] + #[test_case(MoveObjectRequest::new().set_if_source_generation_match(1), false; "if_source_generation_match alone")] + fn move_object(req: MoveObjectRequest, want: bool) { + assert_mutation(req.resolve_idempotency(RequestOptions::default()), want); + } + + #[test_case(true, false; "with_idempotency(true) on unconditioned")] + #[test_case(false, true; "with_idempotency(false) on conditioned")] + fn explicit_override_wins(explicit: bool, rule: bool) { + let mut options = RequestOptions::default(); + options.set_idempotency(explicit); + assert_mutation(mutation(options, rule), explicit); + } + + #[test] + fn existing_token_is_kept() { + let mut headers = HeaderMap::new(); + headers.insert( + IDEMPOTENCY_TOKEN_HEADER, + HeaderValue::from_static("caller-token"), + ); + let options = RequestOptions::default().insert_extension(headers); + let got = mutation(options, true); + assert_eq!( + token(&got).map(|v| v.as_bytes()), + Some("caller-token".as_bytes()) + ); + } + + #[test] + fn other_headers_are_kept() { + let mut headers = HeaderMap::new(); + headers.insert("x-goog-custom", HeaderValue::from_static("keep-me")); + let options = RequestOptions::default().insert_extension(headers); + let got = mutation(options, true); + let headers = got.get_extension::().expect("headers are set"); + assert_eq!( + headers.get("x-goog-custom").map(|v| v.as_bytes()), + Some("keep-me".as_bytes()) + ); + assert!(token(&got).is_some()); + } + + /// Guards against regenerating `gapic/transport.rs` without the hook, which + /// would silently make every `google.storage.v2.Storage` RPC non-idempotent. + #[test] + fn generated_transport_uses_hook() { + let source = include_str!("generated/gapic/transport.rs"); + assert!(source.contains("req.resolve_idempotency(options)")); + assert!( + !source.contains("set_default_idempotency("), + "regenerate with `idempotency_hook: resolve_idempotency` in librarian.yaml" + ); + } +} diff --git a/src/storage/src/lib.rs b/src/storage/src/lib.rs index 7e83502c40..957b3b010e 100644 --- a/src/storage/src/lib.rs +++ b/src/storage/src/lib.rs @@ -85,6 +85,7 @@ pub mod http { } mod control; +mod idempotency; mod storage; pub mod client { diff --git a/tests/spanner/src/concurrent_inline_begin.rs b/tests/spanner/src/concurrent_inline_begin.rs index 19892c5303..fd32de6ea0 100644 --- a/tests/spanner/src/concurrent_inline_begin.rs +++ b/tests/spanner/src/concurrent_inline_begin.rs @@ -20,6 +20,7 @@ use google_cloud_spanner::result::{ResultSet, Row}; use google_cloud_spanner::transaction::BeginTransactionOption; use google_cloud_spanner::transaction::TimestampBound; use google_cloud_test_utils::resource_names::LowercaseAlphanumeric; +use google_cloud_wkt::Timestamp; use http::{Request, Response, StatusCode, Uri}; use http_body::Frame; use http_body_util::Full; @@ -28,7 +29,6 @@ use prost::Message; use spanner_grpc_mock::google::spanner::v1 as spanner_v1; use std::collections::HashMap; use std::sync::Arc; -use time::OffsetDateTime; use tokio::sync::{Barrier, Mutex}; use tonic::Status; use tonic::body::Body; @@ -81,8 +81,8 @@ pub async fn test_concurrent_inline_begin_with_snapshot_consistency() -> anyhow: .build() .execute_query("SELECT CURRENT_TIMESTAMP") .await?; - let row: Row = rs.next().await.unwrap().unwrap(); - let snapshot_time: OffsetDateTime = row.try_get(0)?; + let row: Row = rs.next().await.transpose()?.expect("current timestamp row"); + let snapshot_time: Timestamp = row.try_get(0)?; // 3. Setup Table 2 (Does NOT exist at snapshot time) let statement = format!( diff --git a/tests/spanner/src/query.rs b/tests/spanner/src/query.rs index 2b6f394d3b..288a9fbaec 100644 --- a/tests/spanner/src/query.rs +++ b/tests/spanner/src/query.rs @@ -157,7 +157,7 @@ pub async fn query_with_parameters(db_client: &DatabaseClient) -> anyhow::Result } assert_eq!(rows.len(), 1); - assert_eq!(rows[0].raw_values()[0].as_string(), "Bob"); + assert_eq!(rows[0].raw_values()[0].as_str(), Some("Bob")); Ok(()) } @@ -251,8 +251,8 @@ async fn test_multi_use_read_only_transaction( // The read timestamp is now always available. assert!(tx.read_timestamp().is_some()); - let val1 = row1.raw_values()[0].as_string(); - assert_eq!(val1, "1"); + let val1 = row1.raw_values()[0].as_str(); + assert_eq!(val1, Some("1")); let next1 = rs1.next().await.transpose()?; assert!(next1.is_none(), "{next1:?}"); @@ -261,8 +261,8 @@ async fn test_multi_use_read_only_transaction( .execute_query(Statement::builder("SELECT 2 AS col_int").build()) .await?; let row2 = rs2.next().await.transpose()?.expect("should yield a row"); - let val2 = row2.raw_values()[0].as_string(); - assert_eq!(val2, "2"); + let val2 = row2.raw_values()[0].as_str(); + assert_eq!(val2, Some("2")); let next2 = rs2.next().await.transpose()?; assert!(next2.is_none(), "{next2:?}"); @@ -290,10 +290,10 @@ pub async fn multi_use_read_only_transaction_interleaved( .await?; let row2 = rs2.next().await.transpose()?.expect("should yield a row"); - assert_eq!(row2.raw_values()[0].as_string(), "2"); + assert_eq!(row2.raw_values()[0].as_str(), Some("2")); let row1 = rs1.next().await.transpose()?.expect("should yield a row"); - assert_eq!(row1.raw_values()[0].as_string(), "1"); + assert_eq!(row1.raw_values()[0].as_str(), Some("1")); Ok(()) } @@ -342,8 +342,8 @@ pub async fn multi_use_read_only_transaction_invalid_query_fallback( .await?; let row2 = rs2.next().await.transpose()?.expect("should yield a row"); - let val2 = row2.raw_values()[0].as_string(); - assert_eq!(val2, "2"); + let val2 = row2.raw_values()[0].as_str(); + assert_eq!(val2, Some("2")); Ok(()) } @@ -360,115 +360,154 @@ fn verify_null_row(row: &Row) { fn verify_row_1(row: &Row) { let raw_values = row.raw_values(); assert_eq!(raw_values.len(), 20, "Row should have exactly 20 columns"); - assert_eq!(raw_values[0].as_string(), "1"); // INT64 is encoded as string - assert_eq!(raw_values[1].as_f64(), 1.0); - assert_eq!(raw_values[2].as_f64(), 1.0); // FLOAT32 is encoded as f64 - assert!(raw_values[3].as_bool()); - assert_eq!(raw_values[4].as_string(), "One"); - assert_eq!(raw_values[5].as_string(), "T25l"); // Base64 'One' - assert_eq!(raw_values[6].as_string(), "{\"value\":1}"); // JSON - assert_eq!(raw_values[7].as_string(), "1"); // NUMERIC is encoded as string - assert_eq!(raw_values[8].as_string(), "2026-03-09"); - assert_eq!(raw_values[9].as_string(), "2026-03-09T16:20:00Z"); - - assert_eq!(raw_values[10].as_list().len(), 1); - assert_eq!(raw_values[10].as_list().get(0).unwrap().as_string(), "1"); - assert_eq!(raw_values[11].as_list().len(), 1); - assert_eq!(raw_values[11].as_list().get(0).unwrap().as_f64(), 1.0); - assert_eq!(raw_values[12].as_list().len(), 1); - assert_eq!(raw_values[12].as_list().get(0).unwrap().as_f64(), 1.0); - assert_eq!(raw_values[13].as_list().len(), 1); - assert!(raw_values[13].as_list().get(0).unwrap().as_bool()); - assert_eq!(raw_values[14].as_list().len(), 1); - assert_eq!(raw_values[14].as_list().get(0).unwrap().as_string(), "One"); - assert_eq!(raw_values[15].as_list().len(), 1); - assert_eq!(raw_values[15].as_list().get(0).unwrap().as_string(), "T25l"); - assert_eq!(raw_values[16].as_list().len(), 1); + assert_eq!(raw_values[0].as_str(), Some("1")); // INT64 is encoded as string + assert_eq!(raw_values[1].as_f64(), Some(1.0)); + assert_eq!(raw_values[2].as_f64(), Some(1.0)); // FLOAT32 is encoded as f64 + assert_eq!(raw_values[3].as_bool(), Some(true)); + assert_eq!(raw_values[4].as_str(), Some("One")); + assert_eq!(raw_values[5].as_str(), Some("T25l")); // Base64 'One' + assert_eq!(raw_values[6].as_str(), Some("{\"value\":1}")); // JSON + assert_eq!(raw_values[7].as_str(), Some("1")); // NUMERIC is encoded as string + assert_eq!(raw_values[8].as_str(), Some("2026-03-09")); + assert_eq!(raw_values[9].as_str(), Some("2026-03-09T16:20:00Z")); + + let list_int64 = raw_values[10].as_list().expect("list int64 should exist"); + assert_eq!(list_int64.len(), 1); + assert_eq!(list_int64.get(0).expect("elem 0").as_str(), Some("1")); + + let list_float64 = raw_values[11].as_list().expect("list float64 should exist"); + assert_eq!(list_float64.len(), 1); + assert_eq!(list_float64.get(0).expect("elem 0").as_f64(), Some(1.0)); + + let list_float32 = raw_values[12].as_list().expect("list float32 should exist"); + assert_eq!(list_float32.len(), 1); + assert_eq!(list_float32.get(0).expect("elem 0").as_f64(), Some(1.0)); + + let list_bool = raw_values[13].as_list().expect("list bool should exist"); + assert_eq!(list_bool.len(), 1); + assert_eq!(list_bool.get(0).expect("elem 0").as_bool(), Some(true)); + + let list_string = raw_values[14].as_list().expect("list string should exist"); + assert_eq!(list_string.len(), 1); + assert_eq!(list_string.get(0).expect("elem 0").as_str(), Some("One")); + + let list_bytes = raw_values[15].as_list().expect("list bytes should exist"); + assert_eq!(list_bytes.len(), 1); + assert_eq!(list_bytes.get(0).expect("elem 0").as_str(), Some("T25l")); + + let list_json = raw_values[16].as_list().expect("list json should exist"); + assert_eq!(list_json.len(), 1); assert_eq!( - raw_values[16].as_list().get(0).unwrap().as_string(), - "{\"value\":1}" + list_json.get(0).expect("elem 0").as_str(), + Some("{\"value\":1}") ); - assert_eq!(raw_values[17].as_list().len(), 1); - assert_eq!(raw_values[17].as_list().get(0).unwrap().as_string(), "1"); - assert_eq!(raw_values[18].as_list().len(), 1); + + let list_numeric = raw_values[17].as_list().expect("list numeric should exist"); + assert_eq!(list_numeric.len(), 1); + assert_eq!(list_numeric.get(0).expect("elem 0").as_str(), Some("1")); + + let list_date = raw_values[18].as_list().expect("list date should exist"); + assert_eq!(list_date.len(), 1); assert_eq!( - raw_values[18].as_list().get(0).unwrap().as_string(), - "2026-03-09" + list_date.get(0).expect("elem 0").as_str(), + Some("2026-03-09") ); - assert_eq!(raw_values[19].as_list().len(), 1); + + let list_timestamp = raw_values[19] + .as_list() + .expect("list timestamp should exist"); + assert_eq!(list_timestamp.len(), 1); assert_eq!( - raw_values[19].as_list().get(0).unwrap().as_string(), - "2026-03-09T16:20:00Z" + list_timestamp.get(0).expect("elem 0").as_str(), + Some("2026-03-09T16:20:00Z") ); } fn verify_row_2(row: &Row) { let raw_values = row.raw_values(); assert_eq!(raw_values.len(), 20, "Row should have exactly 20 columns"); - assert_eq!(raw_values[0].as_string(), "2"); - assert_eq!(raw_values[1].as_f64(), 2.0); - assert_eq!(raw_values[2].as_f64(), 2.0); - assert!(!raw_values[3].as_bool()); - assert_eq!(raw_values[4].as_string(), "Two"); - assert_eq!(raw_values[5].as_string(), "VHdv"); // Base64 'Two' - assert_eq!(raw_values[6].as_string(), "{\"value\":2}"); - assert_eq!(raw_values[7].as_string(), "2"); - assert_eq!(raw_values[8].as_string(), "2026-03-10"); - assert_eq!(raw_values[9].as_string(), "2026-03-10T16:20:00Z"); - - assert_eq!(raw_values[10].as_list().len(), 2); - assert_eq!(raw_values[10].as_list().get(0).unwrap().as_string(), "2"); - assert_eq!(raw_values[10].as_list().get(1).unwrap().as_string(), "3"); - assert_eq!(raw_values[11].as_list().len(), 2); - assert_eq!(raw_values[11].as_list().get(0).unwrap().as_f64(), 2.0); - assert_eq!(raw_values[11].as_list().get(1).unwrap().as_f64(), 3.0); - assert_eq!(raw_values[12].as_list().len(), 2); - assert_eq!(raw_values[12].as_list().get(0).unwrap().as_f64(), 2.0); - assert_eq!(raw_values[12].as_list().get(1).unwrap().as_f64(), 3.0); - assert_eq!(raw_values[13].as_list().len(), 2); - assert!(!raw_values[13].as_list().get(0).unwrap().as_bool()); - assert!(raw_values[13].as_list().get(1).unwrap().as_bool()); - assert_eq!(raw_values[14].as_list().len(), 2); - assert_eq!(raw_values[14].as_list().get(0).unwrap().as_string(), "Two"); - assert_eq!( - raw_values[14].as_list().get(1).unwrap().as_string(), - "Three" - ); - assert_eq!(raw_values[15].as_list().len(), 2); - assert_eq!(raw_values[15].as_list().get(0).unwrap().as_string(), "VHdv"); + assert_eq!(raw_values[0].as_str(), Some("2")); + assert_eq!(raw_values[1].as_f64(), Some(2.0)); + assert_eq!(raw_values[2].as_f64(), Some(2.0)); + assert_eq!(raw_values[3].as_bool(), Some(false)); + assert_eq!(raw_values[4].as_str(), Some("Two")); + assert_eq!(raw_values[5].as_str(), Some("VHdv")); // Base64 'Two' + assert_eq!(raw_values[6].as_str(), Some("{\"value\":2}")); + assert_eq!(raw_values[7].as_str(), Some("2")); + assert_eq!(raw_values[8].as_str(), Some("2026-03-10")); + assert_eq!(raw_values[9].as_str(), Some("2026-03-10T16:20:00Z")); + + let list_int64 = raw_values[10].as_list().expect("list int64 should exist"); + assert_eq!(list_int64.len(), 2); + assert_eq!(list_int64.get(0).expect("elem 0").as_str(), Some("2")); + assert_eq!(list_int64.get(1).expect("elem 1").as_str(), Some("3")); + + let list_float64 = raw_values[11].as_list().expect("list float64 should exist"); + assert_eq!(list_float64.len(), 2); + assert_eq!(list_float64.get(0).expect("elem 0").as_f64(), Some(2.0)); + assert_eq!(list_float64.get(1).expect("elem 1").as_f64(), Some(3.0)); + + let list_float32 = raw_values[12].as_list().expect("list float32 should exist"); + assert_eq!(list_float32.len(), 2); + assert_eq!(list_float32.get(0).expect("elem 0").as_f64(), Some(2.0)); + assert_eq!(list_float32.get(1).expect("elem 1").as_f64(), Some(3.0)); + + let list_bool = raw_values[13].as_list().expect("list bool should exist"); + assert_eq!(list_bool.len(), 2); + assert_eq!(list_bool.get(0).expect("elem 0").as_bool(), Some(false)); + assert_eq!(list_bool.get(1).expect("elem 1").as_bool(), Some(true)); + + let list_string = raw_values[14].as_list().expect("list string should exist"); + assert_eq!(list_string.len(), 2); + assert_eq!(list_string.get(0).expect("elem 0").as_str(), Some("Two")); + assert_eq!(list_string.get(1).expect("elem 1").as_str(), Some("Three")); + + let list_bytes = raw_values[15].as_list().expect("list bytes should exist"); + assert_eq!(list_bytes.len(), 2); + assert_eq!(list_bytes.get(0).expect("elem 0").as_str(), Some("VHdv")); assert_eq!( - raw_values[15].as_list().get(1).unwrap().as_string(), - "VGhyZWU=" + list_bytes.get(1).expect("elem 1").as_str(), + Some("VGhyZWU=") ); - assert_eq!(raw_values[16].as_list().len(), 2); + + let list_json = raw_values[16].as_list().expect("list json should exist"); + assert_eq!(list_json.len(), 2); assert_eq!( - raw_values[16].as_list().get(0).unwrap().as_string(), - "{\"value\":2}" + list_json.get(0).expect("elem 0").as_str(), + Some("{\"value\":2}") ); assert_eq!( - raw_values[16].as_list().get(1).unwrap().as_string(), - "{\"value\":3}" + list_json.get(1).expect("elem 1").as_str(), + Some("{\"value\":3}") ); - assert_eq!(raw_values[17].as_list().len(), 2); - assert_eq!(raw_values[17].as_list().get(0).unwrap().as_string(), "2"); - assert_eq!(raw_values[17].as_list().get(1).unwrap().as_string(), "3"); - assert_eq!(raw_values[18].as_list().len(), 2); + + let list_numeric = raw_values[17].as_list().expect("list numeric should exist"); + assert_eq!(list_numeric.len(), 2); + assert_eq!(list_numeric.get(0).expect("elem 0").as_str(), Some("2")); + assert_eq!(list_numeric.get(1).expect("elem 1").as_str(), Some("3")); + + let list_date = raw_values[18].as_list().expect("list date should exist"); + assert_eq!(list_date.len(), 2); assert_eq!( - raw_values[18].as_list().get(0).unwrap().as_string(), - "2026-03-10" + list_date.get(0).expect("elem 0").as_str(), + Some("2026-03-10") ); assert_eq!( - raw_values[18].as_list().get(1).unwrap().as_string(), - "2026-03-11" + list_date.get(1).expect("elem 1").as_str(), + Some("2026-03-11") ); - assert_eq!(raw_values[19].as_list().len(), 2); + + let list_timestamp = raw_values[19] + .as_list() + .expect("list timestamp should exist"); + assert_eq!(list_timestamp.len(), 2); assert_eq!( - raw_values[19].as_list().get(0).unwrap().as_string(), - "2026-03-10T16:20:00Z" + list_timestamp.get(0).expect("elem 0").as_str(), + Some("2026-03-10T16:20:00Z") ); assert_eq!( - raw_values[19].as_list().get(1).unwrap().as_string(), - "2026-03-11T16:20:00Z" + list_timestamp.get(1).expect("elem 1").as_str(), + Some("2026-03-11T16:20:00Z") ); } diff --git a/tests/spanner/src/read_only_transaction_options.rs b/tests/spanner/src/read_only_transaction_options.rs index f5743efe1d..c741b74bef 100644 --- a/tests/spanner/src/read_only_transaction_options.rs +++ b/tests/spanner/src/read_only_transaction_options.rs @@ -142,7 +142,7 @@ pub async fn read_only_bounded_staleness(db_client: &DatabaseClient) -> anyhow:: let spanner_now_minus_5 = spanner_now - time::Duration::seconds(5); let tx = db_client .single_use() - .set_timestamp_bound(TimestampBound::min_read_timestamp(spanner_now_minus_5)) + .set_timestamp_bound(TimestampBound::try_min_read_timestamp(spanner_now_minus_5)?) .build(); let mut rs = tx diff --git a/tests/spanner/src/write.rs b/tests/spanner/src/write.rs index 1ffd193059..b9686826db 100644 --- a/tests/spanner/src/write.rs +++ b/tests/spanner/src/write.rs @@ -282,24 +282,24 @@ async fn write_internal( // TODO: We should implement FromValue and ToValue for specific array types. // For now, we fallback to extracting the raw Value to verify the array types. let arr_bool: Value = row1.get("ColArrayBool"); - assert_eq!(arr_bool.as_list().len(), 3); // ArrayBool - assert!( - arr_bool - .as_list() + let list_bool = arr_bool.as_list().expect("expected ColArrayBool list"); + assert_eq!(list_bool.len(), 3); // ArrayBool + assert_eq!( + list_bool .get(0) .expect("expected ArrayBool element at index 0") - .as_bool() + .as_bool(), + Some(true) ); - assert!( - !arr_bool - .as_list() + assert_eq!( + list_bool .get(1) .expect("expected ArrayBool element at index 1") - .as_bool() + .as_bool(), + Some(false) ); assert_eq!( - arr_bool - .as_list() + list_bool .get(2) .expect("expected ArrayBool element at index 2") .kind(), @@ -307,26 +307,24 @@ async fn write_internal( ); let arr_int64: Value = row1.get("ColArrayInt64"); - assert_eq!(arr_int64.as_list().len(), 3); // ArrayInt64 + let list_int64 = arr_int64.as_list().expect("expected ColArrayInt64 list"); + assert_eq!(list_int64.len(), 3); // ArrayInt64 assert_eq!( - arr_int64 - .as_list() + list_int64 .get(0) .expect("expected ArrayInt64 element at index 0") - .as_string(), - "1" + .as_str(), + Some("1") ); assert_eq!( - arr_int64 - .as_list() + list_int64 .get(1) .expect("expected ArrayInt64 element at index 1") - .as_string(), - "2" + .as_str(), + Some("2") ); assert_eq!( - arr_int64 - .as_list() + list_int64 .get(2) .expect("expected ArrayInt64 element at index 2") .kind(), @@ -334,26 +332,26 @@ async fn write_internal( ); let arr_float32: Value = row1.get("ColArrayFloat32"); - assert_eq!(arr_float32.as_list().len(), 3); // ArrayFloat32 mapped to f64 + let list_float32 = arr_float32 + .as_list() + .expect("expected ColArrayFloat32 list"); + assert_eq!(list_float32.len(), 3); // ArrayFloat32 mapped to f64 assert_eq!( - arr_float32 - .as_list() + list_float32 .get(0) .expect("expected ArrayFloat32 element at index 0") .as_f64(), - 1.0 + Some(1.0) ); assert_eq!( - arr_float32 - .as_list() + list_float32 .get(1) .expect("expected ArrayFloat32 element at index 1") .as_f64(), - 2.0 + Some(2.0) ); assert_eq!( - arr_float32 - .as_list() + list_float32 .get(2) .expect("expected ArrayFloat32 element at index 2") .kind(), @@ -361,26 +359,26 @@ async fn write_internal( ); let arr_float64: Value = row1.get("ColArrayFloat64"); - assert_eq!(arr_float64.as_list().len(), 3); // ArrayFloat64 + let list_float64 = arr_float64 + .as_list() + .expect("expected ColArrayFloat64 list"); + assert_eq!(list_float64.len(), 3); // ArrayFloat64 assert_eq!( - arr_float64 - .as_list() + list_float64 .get(0) .expect("expected ArrayFloat64 element at index 0") .as_f64(), - 1.0 + Some(1.0) ); assert_eq!( - arr_float64 - .as_list() + list_float64 .get(1) .expect("expected ArrayFloat64 element at index 1") .as_f64(), - 2.0 + Some(2.0) ); assert_eq!( - arr_float64 - .as_list() + list_float64 .get(2) .expect("expected ArrayFloat64 element at index 2") .kind(), @@ -388,26 +386,26 @@ async fn write_internal( ); let arr_numeric: Value = row1.get("ColArrayNumeric"); - assert_eq!(arr_numeric.as_list().len(), 3); // ArrayNumeric + let list_numeric = arr_numeric + .as_list() + .expect("expected ColArrayNumeric list"); + assert_eq!(list_numeric.len(), 3); // ArrayNumeric assert_eq!( - arr_numeric - .as_list() + list_numeric .get(0) .expect("expected ArrayNumeric element at index 0") - .as_string(), - "1" + .as_str(), + Some("1") ); assert_eq!( - arr_numeric - .as_list() + list_numeric .get(1) .expect("expected ArrayNumeric element at index 1") - .as_string(), - "2" + .as_str(), + Some("2") ); assert_eq!( - arr_numeric - .as_list() + list_numeric .get(2) .expect("expected ArrayNumeric element at index 2") .kind(), @@ -415,26 +413,24 @@ async fn write_internal( ); let arr_string: Value = row1.get("ColArrayString"); - assert_eq!(arr_string.as_list().len(), 3); // ArrayString + let list_string = arr_string.as_list().expect("expected ColArrayString list"); + assert_eq!(list_string.len(), 3); // ArrayString assert_eq!( - arr_string - .as_list() + list_string .get(0) .expect("expected ArrayString element at index 0") - .as_string(), - "hello" + .as_str(), + Some("hello") ); assert_eq!( - arr_string - .as_list() + list_string .get(1) .expect("expected ArrayString element at index 1") - .as_string(), - "world" + .as_str(), + Some("world") ); assert_eq!( - arr_string - .as_list() + list_string .get(2) .expect("expected ArrayString element at index 2") .kind(), @@ -442,26 +438,24 @@ async fn write_internal( ); let arr_bytes: Value = row1.get("ColArrayBytes"); - assert_eq!(arr_bytes.as_list().len(), 3); // ArrayBytes (base64 returned from Spanner REST) + let list_bytes = arr_bytes.as_list().expect("expected ColArrayBytes list"); + assert_eq!(list_bytes.len(), 3); // ArrayBytes (base64 returned from Spanner REST) assert!( - arr_bytes - .as_list() + list_bytes .get(0) .expect("expected ArrayBytes element at index 0") - .try_as_string() + .as_str() .is_some() ); assert!( - arr_bytes - .as_list() + list_bytes .get(1) .expect("expected ArrayBytes element at index 1") - .try_as_string() + .as_str() .is_some() ); assert_eq!( - arr_bytes - .as_list() + list_bytes .get(2) .expect("expected ArrayBytes element at index 2") .kind(), @@ -469,18 +463,17 @@ async fn write_internal( ); let arr_date: Value = row1.get("ColArrayDate"); - assert_eq!(arr_date.as_list().len(), 2); // ArrayDate + let list_date = arr_date.as_list().expect("expected ColArrayDate list"); + assert_eq!(list_date.len(), 2); // ArrayDate assert_eq!( - arr_date - .as_list() + list_date .get(0) .expect("expected ArrayDate element at index 0") - .as_string(), - "2026-03-09" + .as_str(), + Some("2026-03-09") ); assert_eq!( - arr_date - .as_list() + list_date .get(1) .expect("expected ArrayDate element at index 1") .kind(), @@ -488,18 +481,19 @@ async fn write_internal( ); let arr_timestamp: Value = row1.get("ColArrayTimestamp"); - assert_eq!(arr_timestamp.as_list().len(), 2); // ArrayTimestamp + let list_timestamp = arr_timestamp + .as_list() + .expect("expected ColArrayTimestamp list"); + assert_eq!(list_timestamp.len(), 2); // ArrayTimestamp assert_eq!( - arr_timestamp - .as_list() + list_timestamp .get(0) .expect("expected ArrayTimestamp element at index 0") - .as_string(), - "2026-03-09T16:20:00Z" + .as_str(), + Some("2026-03-09T16:20:00Z") ); assert_eq!( - arr_timestamp - .as_list() + list_timestamp .get(1) .expect("expected ArrayTimestamp element at index 1") .kind(), @@ -507,18 +501,17 @@ async fn write_internal( ); let arr_json: Value = row1.get("ColArrayJson"); - assert_eq!(arr_json.as_list().len(), 2); // ArrayJson + let list_json = arr_json.as_list().expect("expected ColArrayJson list"); + assert_eq!(list_json.len(), 2); // ArrayJson assert_eq!( - arr_json - .as_list() + list_json .get(0) .expect("expected ArrayJson element at index 0") - .as_string(), - "{\"value\":1}" + .as_str(), + Some("{\"value\":1}") ); assert_eq!( - arr_json - .as_list() + list_json .get(1) .expect("expected ArrayJson element at index 1") .kind(), diff --git a/tests/storage/src/bidi_read.rs b/tests/storage/src/bidi_read.rs index a886a5eaa1..ba62f70cee 100644 --- a/tests/storage/src/bidi_read.rs +++ b/tests/storage/src/bidi_read.rs @@ -12,204 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use google_cloud_storage::client::Storage; -use google_cloud_storage::model_ext::ReadRange; - -pub async fn run(bucket_name: &str) -> anyhow::Result<()> { - let client = Storage::builder().build().await?; - send(&client, bucket_name).await?; - send_and_read(&client, bucket_name).await?; - send_and_read_full(&client, bucket_name).await?; - send_and_read_md5(&client, bucket_name).await?; - send_and_read_gzip(&client, bucket_name).await?; - Ok(()) -} - -async fn send(client: &Storage, bucket_name: &str) -> anyhow::Result<()> { - let write = client - .write_object( - bucket_name, - "basic/source.txt", - String::from_iter((0..100_000).map(|_| 'a')), - ) - .set_if_generation_match(0) - .send_unbuffered() - .await?; - - let open = client.open_object(bucket_name, &write.name).send().await?; - tracing::info!("open returns: {open:?}"); - let got = open.object(); - let mut want = write.clone(); - // This field is a mismatch, but both `Some(false)` and `None` represent - // the same value. - want.event_based_hold = want.event_based_hold.or(Some(false)); - // There is a submillisecond difference, maybe rounding? - want.finalize_time = got.finalize_time; - assert_eq!(got, want); - - let mut reader = open.read_range(ReadRange::head(100)).await; - let mut count = 0_usize; - while let Some(r) = reader.next().await.transpose()? { - tracing::info!("received {} bytes", r.len()); - count += r.len(); - } - assert_eq!(count, 100_usize); - - Ok(()) -} - -pub async fn send_and_read(client: &Storage, bucket_name: &str) -> anyhow::Result<()> { - let payload = String::from_iter(('a'..='z').cycle().take(100_000)); - let write = client - .write_object(bucket_name, "open_and_read/source.txt", payload.clone()) - .set_if_generation_match(0) - .send_unbuffered() - .await?; - - let (descriptor, mut reader) = client - .open_object(bucket_name, &write.name) - .send_and_read(ReadRange::tail(100)) - .await?; - tracing::info!("object: {:?}", descriptor.object()); - tracing::info!("headers: {:?}", descriptor.headers()); - tracing::info!("reader: {:?}", reader); - let got = descriptor.object(); - let mut want = write.clone(); - // This field is a mismatch, but both `Some(false)` and `None` represent - // the same value. - want.event_based_hold = want.event_based_hold.or(Some(false)); - // There is a submillisecond difference, maybe rounding? - want.finalize_time = got.finalize_time; - assert_eq!(got, want); - - let mut data = Vec::new(); - while let Some(r) = reader.next().await.transpose()? { - tracing::info!("received {} bytes", r.len()); - data.extend_from_slice(&r); - } - assert_eq!(data, &payload.as_bytes()[(payload.len() - 100)..]); - - Ok(()) -} - -pub async fn send_and_read_md5(client: &Storage, bucket_name: &str) -> anyhow::Result<()> { - let payload = String::from_iter(('a'..='z').cycle().take(100_000)); - let write = client - .write_object(bucket_name, "open_and_read_md5/source.txt", payload.clone()) - .set_if_generation_match(0) - .send_unbuffered() - .await?; - - let (descriptor, mut reader) = client - .open_object(bucket_name, &write.name) - .compute_md5() - .send_and_read(ReadRange::all()) - .await?; - tracing::info!("object: {:?}", descriptor.object()); - tracing::info!("headers: {:?}", descriptor.headers()); - tracing::info!("reader: {:?}", reader); - let got = descriptor.object(); - let mut want = write.clone(); - want.event_based_hold = want.event_based_hold.or(Some(false)); - want.finalize_time = got.finalize_time; - assert_eq!(got, want); - - let mut data = Vec::new(); - while let Some(r) = reader.next().await.transpose()? { - tracing::info!("received {} bytes", r.len()); - data.extend_from_slice(&r); - } - assert_eq!(data, payload.as_bytes()); - - Ok(()) -} - -pub async fn send_and_read_full(client: &Storage, bucket_name: &str) -> anyhow::Result<()> { - let payload = String::from_iter(('a'..='z').cycle().take(100_000)); - let write = client - .write_object( - bucket_name, - "open_and_read_full/source.txt", - payload.clone(), - ) - .set_if_generation_match(0) - .send_unbuffered() - .await?; - - let (descriptor, mut reader) = client - .open_object(bucket_name, &write.name) - .send_and_read(ReadRange::all()) - .await?; - tracing::info!("object: {:?}", descriptor.object()); - tracing::info!("headers: {:?}", descriptor.headers()); - tracing::info!("reader: {:?}", reader); - let got = descriptor.object(); - let mut want = write.clone(); - want.event_based_hold = want.event_based_hold.or(Some(false)); - want.finalize_time = got.finalize_time; - assert_eq!(got, want); - - let mut data = Vec::new(); - while let Some(r) = reader.next().await.transpose()? { - tracing::info!("received {} bytes", r.len()); - data.extend_from_slice(&r); - } - assert_eq!(data, payload.as_bytes()); - - Ok(()) -} - -/// This test verifies the checksum validation behavior for gzip-encoded objects -/// over the gRPC Bidi read stream. -/// -/// Unlike the JSON REST API, which often transcodes (decompresses) gzip objects -/// on the fly, the gRPC Bidi read stream delivers the raw, compressed bytes directly. -/// Because no on-the-fly decompression occurs, the CRC32C checksum of the received -/// chunks will naturally match the server's stored checksum of the compressed object. -/// -/// We explicitly expect `RangeReader`'s automatic checksum validation to succeed -/// without throwing a `ChecksumMismatch` error, proving that we do not need to -/// bypass checksum validation for `content-encoding: gzip` objects in gRPC. -pub async fn send_and_read_gzip(client: &Storage, bucket_name: &str) -> anyhow::Result<()> { - use std::io::Write; - let payload = String::from_iter(('a'..='z').cycle().take(100_000)); - - // Compress the payload - let mut e = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); - e.write_all(payload.as_bytes())?; - let compressed_payload = e.finish()?; - - let write = client - .write_object( - bucket_name, - "open_and_read_gzip/source.txt", - bytes::Bytes::from_owner(compressed_payload.clone()), - ) - .set_if_generation_match(0) - .set_content_encoding("gzip") - .send_unbuffered() - .await?; - - let (descriptor, mut reader) = client - .open_object(bucket_name, &write.name) - .send_and_read(ReadRange::all()) - .await?; - tracing::info!("object: {:?}", descriptor.object()); - tracing::info!("headers: {:?}", descriptor.headers()); - tracing::info!("reader: {:?}", reader); - let got = descriptor.object(); - let mut want = write.clone(); - want.event_based_hold = want.event_based_hold.or(Some(false)); - want.finalize_time = got.finalize_time; - assert_eq!(got, want); - - let mut data = Vec::new(); - while let Some(r) = reader.next().await.transpose()? { - tracing::info!("received {} bytes", r.len()); - data.extend_from_slice(&r); - } - // Verify we received the EXACT compressed payload, meaning gRPC did not decompress it. - assert_eq!(data, compressed_payload); - - Ok(()) -} +//! Bidirectional Read Integration Tests. +//! +//! Separated into two distinct test suites: +//! - [conformance]: Cross-SDK conformance tests for bidi read sessions. +//! - [features]: Client builder options, encodings, and regression tests. + +pub mod conformance; +pub mod features; diff --git a/tests/storage/src/bidi_read/conformance.rs b/tests/storage/src/bidi_read/conformance.rs new file mode 100644 index 0000000000..17c359fa58 --- /dev/null +++ b/tests/storage/src/bidi_read/conformance.rs @@ -0,0 +1,572 @@ +// Copyright 2026 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 +// +// https://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. + +//! Cross-SDK conformance tests for bidirectional reads. + +use bytes::Bytes; +use futures::FutureExt as _; +use google_cloud_gax::error::Error; +use google_cloud_gax::error::rpc::{Code, Status}; +use google_cloud_gax::exponential_backoff::ExponentialBackoffBuilder; +use google_cloud_gax::options::RequestOptionsBuilder as _; +use google_cloud_gax::paginator::ItemPaginator as _; +use google_cloud_gax::retry_policy::RetryPolicyExt as _; +use google_cloud_lro::Poller as _; +use google_cloud_storage::client::{Storage, StorageControl}; +use google_cloud_storage::model::bucket::iam_config::UniformBucketLevelAccess; +use google_cloud_storage::model::bucket::{ + CustomPlacementConfig, HierarchicalNamespace, IamConfig, +}; +use google_cloud_storage::model::{Bucket, RapidCache}; +use google_cloud_storage::model_ext::ReadRange; +use google_cloud_storage::object_descriptor::ObjectDescriptor; +use google_cloud_storage::read_object::ReadObjectResponse; +use google_cloud_storage::retry_policy::RetryableErrors; +use google_cloud_test_utils::resource_names::{LowercaseAlphanumeric, random_bucket_id}; +use google_cloud_test_utils::runtime_config::{project_id, region_id, zone_id}; +use std::panic::AssertUnwindSafe; +use std::time::Duration; + +/// Runs the bidi read conformance tests against each supported bucket type. +pub async fn run() -> anyhow::Result<()> { + println!("\n========================================================"); + println!(" Running Bidi Read Conformance Integration Test Suite"); + println!("========================================================"); + + let clients = Clients::new().await?; + + test_non_existent_bucket_read(&clients).await?; + + with_bucket( + &clients, + BucketType::RegionalStandard { hns: false }, + async |bucket, bucket_type| { + test_read_post_stream_close(&clients, bucket, bucket_type).await?; + test_out_of_range(&clients, bucket, bucket_type).await?; + test_multiple_ranged_read(&clients, bucket, bucket_type).await + }, + ) + .await?; + + // Only the data path is exercised on Regional Standard (HNS). The error-path checks + // (out-of-range and post-stream-close) are duplicate because the bidi read transport + // logic is identical regardless of bucket namespace layout (flat vs HNS). + with_bucket( + &clients, + BucketType::RegionalStandard { hns: true }, + async |bucket, bucket_type| test_multiple_ranged_read(&clients, bucket, bucket_type).await, + ) + .await?; + + #[cfg(google_cloud_unstable_storage_bidi)] + with_bucket( + &clients, + BucketType::ZonalRapid, + async |bucket, bucket_type| { + test_read_post_stream_close(&clients, bucket, bucket_type).await?; + test_out_of_range(&clients, bucket, bucket_type).await?; + test_multiple_ranged_read(&clients, bucket, bucket_type).await + }, + ) + .await?; + + #[cfg(not(google_cloud_unstable_storage_bidi))] + println!( + "\nSkipping Zonal Rapid conformance tests: `google_cloud_unstable_storage_bidi` cfg is not enabled" + ); + + with_bucket( + &clients, + BucketType::RegionalRapid, + async |bucket, bucket_type| { + test_read_post_stream_close(&clients, bucket, bucket_type).await?; + test_out_of_range(&clients, bucket, bucket_type).await?; + test_multiple_ranged_read(&clients, bucket, bucket_type).await + }, + ) + .await?; + + println!("\n>>> All Bidi Read Conformance integration tests completed successfully! <<<\n"); + Ok(()) +} + +struct Clients { + /// Bidi reads and appendable writes. + grpc: Storage, + /// JSON uploads. + http: Storage, + /// Bucket, object, folder and cache management. + control: StorageControl, +} + +impl Clients { + async fn new() -> anyhow::Result { + let grpc_endpoint = std::env::var("GOOGLE_CLOUD_TEST_GRPC_ENDPOINT").map_err(|_| { + anyhow::anyhow!("GOOGLE_CLOUD_TEST_GRPC_ENDPOINT environment variable must be set") + })?; + let http_endpoint = std::env::var("GOOGLE_CLOUD_TEST_HTTP_ENDPOINT").map_err(|_| { + anyhow::anyhow!("GOOGLE_CLOUD_TEST_HTTP_ENDPOINT environment variable must be set") + })?; + + let grpc = Storage::builder() + .with_endpoint(&grpc_endpoint) + .build() + .await?; + let http = Storage::builder() + .with_endpoint(&http_endpoint) + .build() + .await?; + + let backoff = ExponentialBackoffBuilder::new() + .with_initial_delay(Duration::from_secs(2)) + .with_maximum_delay(Duration::from_secs(8)) + .build()?; + let control = StorageControl::builder() + .with_endpoint(&grpc_endpoint) + .with_backoff_policy(backoff) + .with_retry_policy(RetryableErrors.with_attempt_limit(5)) + .build() + .await?; + + Ok(Self { + grpc, + http, + control, + }) + } +} + +#[derive(Clone, Copy, Debug)] +enum BucketType { + RegionalStandard { + hns: bool, + }, + #[cfg_attr(not(google_cloud_unstable_storage_bidi), allow(dead_code))] + ZonalRapid, + RegionalRapid, +} + +impl BucketType { + fn label(self) -> &'static str { + match self { + Self::RegionalStandard { hns: false } => "Regional Standard (flat)", + Self::RegionalStandard { hns: true } => "Regional Standard (HNS)", + Self::ZonalRapid => "Zonal Rapid", + Self::RegionalRapid => "Regional Rapid (HNS)", + } + } + + fn is_hns(self) -> bool { + match self { + Self::RegionalStandard { hns } => hns, + // Zonal Rapid and Regional Rapid are only supported with HNS enabled. + Self::ZonalRapid | Self::RegionalRapid => true, + } + } + + fn has_rapid_cache(self) -> bool { + matches!(self, Self::RegionalRapid) + } + + fn is_appendable(self) -> bool { + matches!(self, Self::ZonalRapid) + } +} + +/// Creates a bucket, runs `f` on it, and deletes the bucket even if `f` fails or panics. +async fn with_bucket(clients: &Clients, bucket_type: BucketType, f: F) -> anyhow::Result<()> +where + F: AsyncFnOnce(&str, BucketType) -> anyhow::Result<()>, +{ + let bucket_id = random_bucket_id(); + println!("\n========================================================"); + println!(" Testing Bucket Type: {}", bucket_type.label()); + println!(" Bucket: {bucket_id}"); + println!("========================================================"); + let bucket = create_bucket(&clients.control, bucket_type, bucket_id).await?; + // A failed `assert!` panics, so catch the unwind to make sure cleanup still runs. + let result = AssertUnwindSafe(f(&bucket.name, bucket_type)) + .catch_unwind() + .await; + cleanup_bucket( + &clients.control, + &bucket.name, + bucket_type.has_rapid_cache(), + ) + .await; + match result { + Ok(result) => result, + Err(panic) => std::panic::resume_unwind(panic), + } +} + +/// For Regional Rapid, also attaches the cache, and deletes the bucket if that fails. +async fn create_bucket( + control: &StorageControl, + bucket_type: BucketType, + bucket_id: String, +) -> anyhow::Result { + let zone = zone_id(); + let mut bucket = Bucket::new() + .set_project(format!("projects/{}", project_id()?)) + .set_location(region_id()) + .set_labels([("integration-test", "true")]) + .set_iam_config( + IamConfig::new() + .set_uniform_bucket_level_access(UniformBucketLevelAccess::new().set_enabled(true)), + ); + if bucket_type.is_hns() { + bucket = bucket.set_hierarchical_namespace(HierarchicalNamespace::new().set_enabled(true)); + } + if matches!(bucket_type, BucketType::ZonalRapid) { + bucket = bucket + .set_custom_placement_config(CustomPlacementConfig::new().set_data_locations([&zone])) + .set_storage_class("RAPID"); + } + + let bucket = control + .create_bucket() + .set_parent("projects/_") + .set_bucket_id(bucket_id) + .set_bucket(bucket) + .with_idempotency(true) + .send() + .await?; + + if bucket_type.has_rapid_cache() { + let rapid_cache = RapidCache::new() + .set_name(format!("{}/rapidCaches/{zone}", bucket.name)) + .set_zone(&zone) + .set_cache_type("rapid-cache-ultra"); + + println!("Attaching rapid-cache-ultra in {zone} (this can take a minute or more)..."); + let attached = control + .create_rapid_cache() + .set_parent(&bucket.name) + .set_rapid_cache(rapid_cache) + .poller() + .until_done() + .await; + if let Err(e) = attached { + cleanup_bucket(control, &bucket.name, true).await; + return Err(e.into()); + } + println!("SUCCESS: attached rapid-cache-ultra in {zone}"); + } + + Ok(bucket) +} + +/// Deletes a bucket and everything in it. Failures are printed, not returned. +async fn cleanup_bucket(control: &StorageControl, bucket_name: &str, has_rapid_cache: bool) { + if has_rapid_cache { + disable_rapid_caches(control, bucket_name).await; + } + let result = match project_id() { + Ok(project_id) => { + storage_samples::cleanup_bucket(control.clone(), bucket_name.to_string(), project_id) + .await + } + Err(e) => Err(e), + }; + if let Err(e) = result { + eprintln!("Warning: failed to delete bucket {bucket_name} during teardown: {e:?}"); + } +} + +async fn disable_rapid_caches(control: &StorageControl, bucket_name: &str) { + let mut caches = control + .list_rapid_caches() + .set_parent(bucket_name) + .by_item(); + while let Some(cache) = caches.next().await { + let cache = match cache { + Ok(cache) => cache, + Err(e) => { + eprintln!( + "Warning: failed to list rapid caches in {bucket_name} during teardown: {e:?}" + ); + return; + } + }; + println!("Disabling rapid cache {}...", cache.name); + let result = control + .disable_rapid_cache() + .set_name(&cache.name) + .poller() + .until_done() + .await; + // b/565175323: the cache is disabled, but the LRO returns an empty result. + if let Err(e) = result + && !format!("{e:?}").contains("neither result nor error set in LRO result") + { + eprintln!( + "Warning: failed to disable rapid cache {}: {e:?}", + cache.name + ); + } else { + println!("SUCCESS: disabled rapid cache {}", cache.name); + } + } +} + +/// Uploads `len` bytes of test data and opens the object for bidi reads. +/// +/// Set `appendable` for Zonal Rapid buckets, which only accept appendable objects; other +/// buckets only accept regular uploads. +async fn upload_and_open( + clients: &Clients, + bucket_name: &str, + len: usize, + appendable: bool, +) -> anyhow::Result<(Bytes, ObjectDescriptor)> { + let payload = Bytes::from_iter((b'a'..=b'z').cycle().take(len)); + let object_name = format!("bidi_read/{}.txt", LowercaseAlphanumeric.random_string(16)); + if appendable { + write_appendable_object(&clients.grpc, bucket_name, &object_name, payload.clone()).await?; + } else { + clients + .http + .write_object(bucket_name, &object_name, payload.clone()) + .set_if_generation_match(0) + .send_unbuffered() + .await?; + } + let descriptor = clients + .grpc + .open_object(bucket_name, &object_name) + .send() + .await?; + Ok((payload, descriptor)) +} + +async fn write_appendable_object( + #[allow(unused_variables)] client: &Storage, + #[allow(unused_variables)] bucket_name: &str, + #[allow(unused_variables)] object_name: &str, + #[allow(unused_variables)] payload: Bytes, +) -> anyhow::Result<()> { + #[cfg(google_cloud_unstable_storage_bidi)] + { + let mut writer = client + .open_appendable_object(bucket_name, object_name) + .send() + .await?; + writer.append(payload).await?; + writer.finalize().await?; + Ok(()) + } + #[cfg(not(google_cloud_unstable_storage_bidi))] + { + anyhow::bail!("appendable uploads require `--cfg google_cloud_unstable_storage_bidi`") + } +} + +async fn test_multiple_ranged_read( + clients: &Clients, + bucket_name: &str, + bucket_type: BucketType, +) -> anyhow::Result<()> { + println!( + "\n--- Testing Multiple Ranged Read ({}) ---", + bucket_type.label() + ); + const KIB: u64 = 1024; + let (payload, descriptor) = upload_and_open( + clients, + bucket_name, + 512 * KIB as usize, + bucket_type.is_appendable(), + ) + .await?; + + let ranges = [ + (0, 64 * KIB), + (64 * KIB, 128 * KIB), + (192 * KIB, 192 * KIB), + (384 * KIB, 128 * KIB), + ]; + let mut readers = Vec::new(); + for (offset, len) in ranges { + readers.push(descriptor.read_range(ReadRange::segment(offset, len)).await); + } + + // The ranges share one stream, so they must be drained concurrently to avoid a deadlock. + let buffers = futures::future::try_join_all(readers.iter_mut().map(drain_reader)).await?; + + for ((offset, len), buf) in ranges.into_iter().zip(buffers) { + let (start, end) = (offset as usize, (offset + len) as usize); + assert_eq!(buf, &payload[start..end], "range {start}..{end}"); + } + + println!( + "SUCCESS: Multiple Ranged Read ({}) -> 4 concurrent ranges verified", + bucket_type.label() + ); + Ok(()) +} + +async fn test_read_post_stream_close( + clients: &Clients, + bucket_name: &str, + bucket_type: BucketType, +) -> anyhow::Result<()> { + println!( + "\n--- Testing Read Post Stream Close ({}) ---", + bucket_type.label() + ); + let (payload, descriptor) = + upload_and_open(clients, bucket_name, 100_000, bucket_type.is_appendable()).await?; + + let mut reader = descriptor.read_range(ReadRange::head(100)).await; + assert_eq!(drain_reader(&mut reader).await?, &payload[0..100]); + + assert!(reader.next().await.is_none()); + assert!(reader.next().await.is_none()); + + // Dropping an unfinished reader must not break the descriptor. + let unconsumed_reader = descriptor.read_range(ReadRange::segment(500, 10_000)).await; + drop(unconsumed_reader); + + let mut reader = descriptor.read_range(ReadRange::segment(200, 50)).await; + assert_eq!(drain_reader(&mut reader).await?, &payload[200..250]); + + println!("SUCCESS: Read Post Stream Close ({})", bucket_type.label()); + Ok(()) +} + +async fn test_non_existent_bucket_read(clients: &Clients) -> anyhow::Result<()> { + println!("\n--- Testing Non-Existent Bucket Read ---"); + // `random_bucket_id()` is a valid, max-length bucket id that is not expected to exist. + let non_existent_bucket = format!("projects/_/buckets/{}", random_bucket_id()); + + let result = clients + .grpc + .open_object(&non_existent_bucket, "non_existent_object.txt") + .send() + .await; + + let err = match result { + Err(err) => err, + Ok(descriptor) => { + let mut reader = descriptor.read_range(ReadRange::head(100)).await; + match reader.next().await { + Some(Err(err)) => err, + other => panic!("expected NotFound error on read_range, got {other:?}"), + } + } + }; + assert_is_not_found(&err); + + println!("SUCCESS: expected NotFound or PermissionDenied received for non-existent bucket"); + Ok(()) +} + +/// Verifies that an out-of-bounds read returns `OutOfRange` or `InvalidArgument`, +/// while concurrent valid range reads on the same session succeed. +/// +/// Both ranges are dispatched concurrently on the same `ObjectDescriptor` session: +/// the valid range completes and yields the expected bytes, while the out-of-bounds range +/// yields an `OutOfRange` / `InvalidArgument` error. +async fn test_out_of_range( + clients: &Clients, + bucket_name: &str, + bucket_type: BucketType, +) -> anyhow::Result<()> { + println!( + "\n--- Testing Out Of Range Read ({}) ---", + bucket_type.label() + ); + let (payload, descriptor) = + upload_and_open(clients, bucket_name, 10_000, bucket_type.is_appendable()).await?; + + let valid_fut = async { + let mut reader = descriptor.read_range(ReadRange::head(50)).await; + drain_reader(&mut reader).await + }; + let oob_fut = async { + let mut oob_reader = descriptor + .read_range(ReadRange::segment(50_000, 1_000)) + .await; + oob_reader.next().await + }; + + let (valid_res, oob_res) = tokio::join!(valid_fut, oob_fut); + + assert_eq!(valid_res?, &payload[0..50]); + + match oob_res { + None => { + panic!("expected OutOfRange or InvalidArgument error for out of range read, got None") + } + Some(Err(err)) => { + let Some(status) = find_rpc_status(&err) else { + panic!("expected an RPC status for out of range read, got {err:?}"); + }; + assert!( + matches!(status.code, Code::OutOfRange | Code::InvalidArgument), + "unexpected status for out of range read: {status:?}" + ); + } + Some(Ok(data)) => panic!( + "unexpected data returned for out of range read: {} bytes", + data.len() + ), + } + + println!( + "SUCCESS: Out Of Range Read ({}) -> expected OutOfRange or InvalidArgument received", + bucket_type.label() + ); + Ok(()) +} + +async fn drain_reader(reader: &mut ReadObjectResponse) -> anyhow::Result> { + let mut buf = Vec::new(); + while let Some(chunk) = reader.next().await.transpose()? { + buf.extend_from_slice(&chunk); + } + Ok(buf) +} + +/// Also accepts `PermissionDenied` (allowlist check). +fn assert_is_not_found(err: &Error) { + if let Some(status) = find_rpc_status(err) { + assert!( + matches!(status.code, Code::NotFound | Code::PermissionDenied), + "expected NotFound or PermissionDenied rpc code, got {status:?}" + ); + } else if let Some(code) = err.http_status_code() { + assert!( + code == 404 || code == 403, + "expected 404 or 403 HTTP status code, got {code}" + ); + } else { + panic!("expected NotFound or PermissionDenied status, got error: {err:?}"); + } +} + +/// Bidi read errors wrap the service error, so this searches the source chain for the status. +fn find_rpc_status(err: &Error) -> Option { + std::iter::successors(Some(err as &(dyn std::error::Error + 'static)), |e| { + e.source() + }) + .filter_map(|e| { + e.downcast_ref::().or_else(|| { + e.downcast_ref::>() + .map(|a| a.as_ref()) + }) + }) + .find_map(|e| e.status().cloned()) +} diff --git a/tests/storage/src/bidi_read/features.rs b/tests/storage/src/bidi_read/features.rs new file mode 100644 index 0000000000..6464a5448d --- /dev/null +++ b/tests/storage/src/bidi_read/features.rs @@ -0,0 +1,217 @@ +// Copyright 2025 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 +// +// https://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. + +//! Feature tests for bidirectional reads: client options, encodings, and metadata. + +use google_cloud_storage::client::Storage; +use google_cloud_storage::model_ext::ReadRange; + +pub async fn run(bucket_name: &str) -> anyhow::Result<()> { + let client = Storage::builder().build().await?; + send(&client, bucket_name).await?; + send_and_read(&client, bucket_name).await?; + send_and_read_full(&client, bucket_name).await?; + send_and_read_md5(&client, bucket_name).await?; + send_and_read_gzip(&client, bucket_name).await?; + Ok(()) +} + +async fn send(client: &Storage, bucket_name: &str) -> anyhow::Result<()> { + let write = client + .write_object( + bucket_name, + "basic/source.txt", + String::from_iter((0..100_000).map(|_| 'a')), + ) + .set_if_generation_match(0) + .send_unbuffered() + .await?; + + let open = client.open_object(bucket_name, &write.name).send().await?; + tracing::info!("open returns: {open:?}"); + let got = open.object(); + let mut want = write.clone(); + // This field is a mismatch, but both `Some(false)` and `None` represent + // the same value. + want.event_based_hold = want.event_based_hold.or(Some(false)); + // There is a submillisecond difference, maybe rounding? + want.finalize_time = got.finalize_time; + assert_eq!(got, want); + + let mut reader = open.read_range(ReadRange::head(100)).await; + let mut count = 0_usize; + while let Some(r) = reader.next().await.transpose()? { + tracing::info!("received {} bytes", r.len()); + count += r.len(); + } + assert_eq!(count, 100_usize); + + Ok(()) +} + +pub async fn send_and_read(client: &Storage, bucket_name: &str) -> anyhow::Result<()> { + let payload = String::from_iter(('a'..='z').cycle().take(100_000)); + let write = client + .write_object(bucket_name, "open_and_read/source.txt", payload.clone()) + .set_if_generation_match(0) + .send_unbuffered() + .await?; + + let (descriptor, mut reader) = client + .open_object(bucket_name, &write.name) + .send_and_read(ReadRange::tail(100)) + .await?; + tracing::info!("object: {:?}", descriptor.object()); + tracing::info!("headers: {:?}", descriptor.headers()); + tracing::info!("reader: {:?}", reader); + let got = descriptor.object(); + let mut want = write.clone(); + // This field is a mismatch, but both `Some(false)` and `None` represent + // the same value. + want.event_based_hold = want.event_based_hold.or(Some(false)); + // There is a submillisecond difference, maybe rounding? + want.finalize_time = got.finalize_time; + assert_eq!(got, want); + + let mut data = Vec::new(); + while let Some(r) = reader.next().await.transpose()? { + tracing::info!("received {} bytes", r.len()); + data.extend_from_slice(&r); + } + assert_eq!(data, &payload.as_bytes()[(payload.len() - 100)..]); + + Ok(()) +} + +pub async fn send_and_read_md5(client: &Storage, bucket_name: &str) -> anyhow::Result<()> { + let payload = String::from_iter(('a'..='z').cycle().take(100_000)); + let write = client + .write_object(bucket_name, "open_and_read_md5/source.txt", payload.clone()) + .set_if_generation_match(0) + .send_unbuffered() + .await?; + + let (descriptor, mut reader) = client + .open_object(bucket_name, &write.name) + .compute_md5() + .send_and_read(ReadRange::all()) + .await?; + tracing::info!("object: {:?}", descriptor.object()); + tracing::info!("headers: {:?}", descriptor.headers()); + tracing::info!("reader: {:?}", reader); + let got = descriptor.object(); + let mut want = write.clone(); + want.event_based_hold = want.event_based_hold.or(Some(false)); + want.finalize_time = got.finalize_time; + assert_eq!(got, want); + + let mut data = Vec::new(); + while let Some(r) = reader.next().await.transpose()? { + tracing::info!("received {} bytes", r.len()); + data.extend_from_slice(&r); + } + assert_eq!(data, payload.as_bytes()); + + Ok(()) +} + +pub async fn send_and_read_full(client: &Storage, bucket_name: &str) -> anyhow::Result<()> { + let payload = String::from_iter(('a'..='z').cycle().take(100_000)); + let write = client + .write_object( + bucket_name, + "open_and_read_full/source.txt", + payload.clone(), + ) + .set_if_generation_match(0) + .send_unbuffered() + .await?; + + let (descriptor, mut reader) = client + .open_object(bucket_name, &write.name) + .send_and_read(ReadRange::all()) + .await?; + tracing::info!("object: {:?}", descriptor.object()); + tracing::info!("headers: {:?}", descriptor.headers()); + tracing::info!("reader: {:?}", reader); + let got = descriptor.object(); + let mut want = write.clone(); + want.event_based_hold = want.event_based_hold.or(Some(false)); + want.finalize_time = got.finalize_time; + assert_eq!(got, want); + + let mut data = Vec::new(); + while let Some(r) = reader.next().await.transpose()? { + tracing::info!("received {} bytes", r.len()); + data.extend_from_slice(&r); + } + assert_eq!(data, payload.as_bytes()); + + Ok(()) +} + +/// This test verifies the checksum validation behavior for gzip-encoded objects +/// over the gRPC Bidi read stream. +/// +/// Unlike the JSON REST API, which often transcodes (decompresses) gzip objects +/// on the fly, the gRPC Bidi read stream delivers the raw, compressed bytes directly. +/// Because no on-the-fly decompression occurs, the CRC32C checksum of the received +/// chunks will naturally match the server's stored checksum of the compressed object. +/// +/// We explicitly expect `RangeReader`'s automatic checksum validation to succeed +/// without throwing a `ChecksumMismatch` error, proving that we do not need to +/// bypass checksum validation for `content-encoding: gzip` objects in gRPC. +pub async fn send_and_read_gzip(client: &Storage, bucket_name: &str) -> anyhow::Result<()> { + use std::io::Write; + let payload = String::from_iter(('a'..='z').cycle().take(100_000)); + + // Compress the payload + let mut e = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); + e.write_all(payload.as_bytes())?; + let compressed_payload = e.finish()?; + + let write = client + .write_object( + bucket_name, + "open_and_read_gzip/source.txt", + bytes::Bytes::from_owner(compressed_payload.clone()), + ) + .set_if_generation_match(0) + .set_content_encoding("gzip") + .send_unbuffered() + .await?; + + let (descriptor, mut reader) = client + .open_object(bucket_name, &write.name) + .send_and_read(ReadRange::all()) + .await?; + tracing::info!("object: {:?}", descriptor.object()); + tracing::info!("headers: {:?}", descriptor.headers()); + tracing::info!("reader: {:?}", reader); + let got = descriptor.object(); + let mut want = write.clone(); + want.event_based_hold = want.event_based_hold.or(Some(false)); + want.finalize_time = got.finalize_time; + assert_eq!(got, want); + + let mut data = Vec::new(); + while let Some(r) = reader.next().await.transpose()? { + tracing::info!("received {} bytes", r.len()); + data.extend_from_slice(&r); + } + // Verify we received the EXACT compressed payload, meaning gRPC did not decompress it. + assert_eq!(data, compressed_payload); + + Ok(()) +} diff --git a/tests/storage/src/rcu_crud.rs b/tests/storage/src/rcu_crud.rs index 6e5d8f4cae..b55016fe24 100644 --- a/tests/storage/src/rcu_crud.rs +++ b/tests/storage/src/rcu_crud.rs @@ -30,7 +30,7 @@ use google_cloud_wkt::{Duration, FieldMask}; use std::time::Duration as StdDuration; /// Creates a StorageControl client with retry and backoff policies configured for RCU tests. -/// If `GOOGLE_CLOUD_TEST_STORAGE_CONTROL_ENDPOINT` is set, overrides the default endpoint. +/// If `GOOGLE_CLOUD_TEST_GRPC_ENDPOINT` is set, overrides the default endpoint. pub async fn create_client() -> anyhow::Result { let backoff = ExponentialBackoffBuilder::new() .with_initial_delay(StdDuration::from_secs(2)) @@ -41,7 +41,7 @@ pub async fn create_client() -> anyhow::Result { .with_backoff_policy(backoff) .with_retry_policy(RetryableErrors.with_attempt_limit(5)); - if let Ok(endpoint) = std::env::var("GOOGLE_CLOUD_TEST_STORAGE_CONTROL_ENDPOINT") { + if let Ok(endpoint) = std::env::var("GOOGLE_CLOUD_TEST_GRPC_ENDPOINT") { println!("StorageControl endpoint: {endpoint}"); builder = builder.with_endpoint(&endpoint); } diff --git a/tests/storage/tests/driver.rs b/tests/storage/tests/driver.rs index 96b08ceabd..33e8702632 100644 --- a/tests/storage/tests/driver.rs +++ b/tests/storage/tests/driver.rs @@ -112,21 +112,42 @@ mod storage { result } - #[tokio::test(flavor = "multi_thread")] - async fn run_storage_bidi() -> anyhow::Result<()> { - let _guard = enable_tracing(); - let (control, bucket) = integration_tests_storage::create_test_hns_bucket() - .await - .inspect_err(anydump)?; - let result = integration_tests_storage::bidi_read::run(&bucket.name) + mod bidi_read { + use super::*; + + #[tokio::test(flavor = "multi_thread")] + async fn features() -> anyhow::Result<()> { + let _guard = enable_tracing(); + let (control, bucket) = integration_tests_storage::create_test_hns_bucket() + .await + .inspect_err(anydump)?; + let result = integration_tests_storage::bidi_read::features::run(&bucket.name) + .await + .inspect_err(anydump); + let _ = storage_samples::cleanup_bucket( + control, + bucket.name.clone(), + bucket.project.clone(), + ) .await + .inspect_err(|e| tracing::error!("error cleaning up bucket {}: {e:?}", bucket.name)) .inspect_err(anydump); - let _ = - storage_samples::cleanup_bucket(control, bucket.name.clone(), bucket.project.clone()) + result + } + + #[tokio::test(flavor = "multi_thread")] + async fn conformance() -> anyhow::Result<()> { + let _guard = enable_tracing(); + if std::env::var("GOOGLE_CLOUD_TEST_GRPC_ENDPOINT").is_err() + || std::env::var("GOOGLE_CLOUD_TEST_HTTP_ENDPOINT").is_err() + { + println!("Skipping Bidi Read Conformance tests"); + return Ok(()); + } + integration_tests_storage::bidi_read::conformance::run() .await - .inspect_err(|e| tracing::error!("error cleaning up bucket {}: {e:?}", bucket.name)) - .inspect_err(anydump); - result + .inspect_err(anydump) + } } #[tokio::test(flavor = "multi_thread")] @@ -150,10 +171,8 @@ mod storage { #[tokio::test(flavor = "multi_thread")] async fn run_storage_control_rapid_cache() -> anyhow::Result<()> { let _guard = enable_tracing(); - if std::env::var("GOOGLE_CLOUD_TEST_STORAGE_CONTROL_ENDPOINT").is_err() { - println!( - "Skipping Rapid Cache Ultra tests: GOOGLE_CLOUD_TEST_STORAGE_CONTROL_ENDPOINT is not set" - ); + if std::env::var("GOOGLE_CLOUD_TEST_GRPC_ENDPOINT").is_err() { + println!("Skipping Rapid Cache Ultra tests"); return Ok(()); } let control = integration_tests_storage::rcu_crud::create_client()