Skip to main content

google_cloud_pubsub/publisher/
base_publisher.rs

1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::publisher::builder::PublisherPartialBuilder;
16use std::time::Duration;
17
18/// Creates [`Publisher`](crate::client::Publisher) instances.
19///
20/// A single `BasePublisher` can be used to create multiple `Publisher` clients
21/// for different topics. It manages the underlying gRPC connection and
22/// authentication.
23///
24/// # Example
25///
26/// ```
27/// # async fn sample() -> anyhow::Result<()> {
28/// # use google_cloud_pubsub::client::BasePublisher;
29/// # use google_cloud_pubsub::model::Message;
30///
31/// // Create a client.
32/// let client: BasePublisher = BasePublisher::builder().build().await?;
33///
34/// // Create a publisher for a specific topic.
35/// let publisher = client.publisher("projects/my-project/topics/my-topic").build();
36///
37/// // Publish a message.
38/// let handle = publisher.publish(Message::new().set_data("hello world"));
39/// let message_id = handle.await?;
40/// println!("Message sent with ID: {}", message_id);
41/// # Ok(())
42/// # }
43/// ```
44#[derive(Clone, Debug)]
45pub struct BasePublisher {
46    pub(crate) inner: crate::generated::gapic_dataplane::client::Publisher,
47    pub(crate) total_timeout: Option<Duration>,
48}
49
50pub use super::client_builder::BasePublisherBuilder;
51
52impl BasePublisher {
53    /// Returns a builder for [BasePublisher].
54    ///
55    /// ```
56    /// # async fn sample() -> anyhow::Result<()> {
57    /// # use google_cloud_pubsub::client::BasePublisher;
58    /// let client: BasePublisher = BasePublisher::builder().build().await?;
59    /// # Ok(()) }
60    /// ```
61    pub fn builder() -> BasePublisherBuilder {
62        BasePublisherBuilder::new()
63    }
64
65    /// Creates a new Pub/Sub publisher client with the given configuration.
66    pub(crate) async fn new(builder: BasePublisherBuilder) -> crate::ClientBuilderResult<Self> {
67        let total_timeout = builder.config.retry_policy.as_ref().and_then(|p| {
68            p.remaining_time(
69                &google_cloud_gax::retry_state::RetryState::new(false)
70                    .set_start(tokio::time::Instant::now().into_std()),
71            )
72        });
73        let inner =
74            crate::generated::gapic_dataplane::client::Publisher::new(builder.config).await?;
75        std::result::Result::Ok(Self {
76            inner,
77            total_timeout,
78        })
79    }
80
81    /// Creates a new `Publisher` for a given topic.
82    ///
83    /// ```
84    /// # async fn sample() -> anyhow::Result<()> {
85    /// # use google_cloud_pubsub::*;
86    /// # use builder::publisher::BasePublisherBuilder;
87    /// # use client::BasePublisher;
88    /// # use model::Message;
89    /// let client = BasePublisher::builder().build().await?;
90    /// let publisher = client.publisher("projects/my-project/topics/my-topic").build();
91    /// let message_id = publisher.publish(Message::new().set_data("Hello, World")).await?;
92    /// # Ok(()) }
93    /// ```
94    pub fn publisher<T>(&self, topic: T) -> PublisherPartialBuilder
95    where
96        T: Into<String>,
97    {
98        PublisherPartialBuilder::new(self.inner.clone(), topic.into())
99            .with_total_timeout(self.total_timeout)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::BasePublisher;
106    use google_cloud_auth::credentials::anonymous::Builder as Anonymous;
107    use google_cloud_gax::retry_policy::{AlwaysRetry, RetryPolicyExt};
108    use std::time::Duration;
109
110    #[tokio::test]
111    async fn builder() -> anyhow::Result<()> {
112        let client = BasePublisher::builder()
113            .with_credentials(Anonymous::new().build())
114            .build()
115            .await?;
116        let _ = client.publisher("projects/my-project/topics/my-topic".to_string());
117        Ok(())
118    }
119
120    #[tokio::test(start_paused = true)]
121    async fn default_total_timeout() -> anyhow::Result<()> {
122        let client = BasePublisher::builder()
123            .with_credentials(Anonymous::new().build())
124            .build()
125            .await?;
126        let timeout = client
127            .total_timeout
128            .expect("default total_timeout should be present");
129        assert_eq!(timeout, Duration::from_secs(600));
130
131        let partial_builder = client.publisher("projects/my-project/topics/my-topic");
132        assert_eq!(partial_builder.total_timeout, client.total_timeout);
133        Ok(())
134    }
135
136    #[tokio::test(start_paused = true)]
137    async fn custom_total_timeout() -> anyhow::Result<()> {
138        let client = BasePublisher::builder()
139            .with_credentials(Anonymous::new().build())
140            .with_retry_policy(AlwaysRetry.with_time_limit(Duration::from_secs(45)))
141            .build()
142            .await?;
143        let timeout = client
144            .total_timeout
145            .expect("custom total_timeout should be present");
146        assert_eq!(timeout, Duration::from_secs(45));
147
148        let partial_builder = client.publisher("projects/my-project/topics/my-topic");
149        assert_eq!(partial_builder.total_timeout, client.total_timeout);
150        Ok(())
151    }
152
153    #[tokio::test]
154    async fn attempt_limit_only_has_no_total_timeout() -> anyhow::Result<()> {
155        let client = BasePublisher::builder()
156            .with_credentials(Anonymous::new().build())
157            .with_retry_policy(AlwaysRetry.with_attempt_limit(3))
158            .build()
159            .await?;
160        assert_eq!(client.total_timeout, None);
161        let partial_builder = client.publisher("projects/my-project/topics/my-topic");
162        assert_eq!(partial_builder.total_timeout, None);
163        Ok(())
164    }
165}