google_cloud_pubsub/publisher/client_builder.rs
1// Copyright 2026 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 super::base_publisher::BasePublisher;
16use gaxi::options::ClientConfig;
17use google_cloud_gax::backoff_policy::BackoffPolicyArg;
18use google_cloud_gax::client_builder::Result as BuilderResult;
19use google_cloud_gax::retry_policy::RetryPolicyArg;
20use google_cloud_gax::retry_throttler::RetryThrottlerArg;
21
22/// A builder for [`BasePublisher`].
23///
24/// # Example
25/// ```
26/// # use google_cloud_pubsub::client::BasePublisher;
27/// # async fn sample() -> anyhow::Result<()> {
28/// let builder = BasePublisher::builder();
29/// let client = builder
30/// .with_endpoint("https://pubsub.googleapis.com")
31/// .build()
32/// .await?;
33/// # Ok(()) }
34/// ```
35#[derive(Clone, Debug)]
36pub struct BasePublisherBuilder {
37 pub(super) config: ClientConfig,
38}
39
40impl BasePublisherBuilder {
41 pub(super) fn new() -> Self {
42 let mut config = ClientConfig::default();
43 config.backoff_policy = Some(std::sync::Arc::new(
44 super::backoff_policy::default_backoff_policy(),
45 ));
46 config.retry_policy = Some(std::sync::Arc::new(
47 super::retry_policy::default_retry_policy(),
48 ));
49 Self { config }
50 }
51
52 /// Creates a new client.
53 ///
54 /// # Example
55 /// ```
56 /// # use google_cloud_pubsub::client::BasePublisher;
57 /// # async fn sample() -> anyhow::Result<()> {
58 /// let client = BasePublisher::builder().build().await?;
59 /// # Ok(()) }
60 /// ```
61 pub async fn build(self) -> BuilderResult<BasePublisher> {
62 BasePublisher::new(self).await
63 }
64
65 /// Sets the endpoint.
66 ///
67 /// # Example
68 /// ```
69 /// # use google_cloud_pubsub::client::BasePublisher;
70 /// # async fn sample() -> anyhow::Result<()> {
71 /// let client = BasePublisher::builder()
72 /// .with_endpoint("https://private.googleapis.com")
73 /// .build()
74 /// .await?;
75 /// # Ok(()) }
76 /// ```
77 pub fn with_endpoint<V: Into<String>>(mut self, v: V) -> Self {
78 self.config.endpoint = Some(v.into());
79 self
80 }
81
82 /// Configure the universe domain.
83 ///
84 /// The universe domain is the default service domain for a given cloud universe.
85 /// The default value is "googleapis.com".
86 ///
87 /// # Example
88 /// ```
89 /// # use google_cloud_pubsub::client::BasePublisher;
90 /// # async fn sample() -> anyhow::Result<()> {
91 /// let client = BasePublisher::builder()
92 /// .with_universe_domain("googleapis.com")
93 /// .build()
94 /// .await?;
95 /// # Ok(()) }
96 /// ```
97 pub fn with_universe_domain<V: Into<String>>(mut self, v: V) -> Self {
98 self.config.universe_domain = Some(v.into());
99 self
100 }
101
102 /// Enables tracing.
103 ///
104 /// The client libraries can be dynamically instrumented with the Tokio
105 /// [tracing] framework. Setting this flag enables this instrumentation.
106 ///
107 /// # Example
108 /// ```
109 /// # use google_cloud_pubsub::client::BasePublisher;
110 /// # async fn sample() -> anyhow::Result<()> {
111 /// let client = BasePublisher::builder()
112 /// .with_tracing()
113 /// .build()
114 /// .await?;
115 /// # Ok(()) }
116 /// ```
117 ///
118 /// [tracing]: https://docs.rs/tracing/latest/tracing/
119 pub fn with_tracing(mut self) -> Self {
120 self.config.tracing = true;
121 self
122 }
123
124 /// Configure the authentication credentials.
125 ///
126 /// Most Google Cloud services require authentication, though some services
127 /// allow for anonymous access, and some services provide emulators where
128 /// no authentication is required. More information about valid credentials
129 /// types can be found in the [google-cloud-auth] crate documentation.
130 ///
131 /// # Example
132 /// ```
133 /// # use google_cloud_pubsub::client::BasePublisher;
134 /// # async fn sample() -> anyhow::Result<()> {
135 /// use google_cloud_auth::credentials::mds;
136 /// let client = BasePublisher::builder()
137 /// .with_credentials(
138 /// mds::Builder::default()
139 /// .with_scopes(["https://www.googleapis.com/auth/cloud-platform.read-only"])
140 /// .build()?)
141 /// .build()
142 /// .await?;
143 /// # Ok(()) }
144 /// ```
145 ///
146 /// [google-cloud-auth]: https://docs.rs/google-cloud-auth
147 pub fn with_credentials<V: Into<gaxi::options::Credentials>>(mut self, v: V) -> Self {
148 self.config.cred = Some(v.into());
149 self
150 }
151
152 /// Configure the retry policy.
153 ///
154 /// The client libraries can automatically retry operations that fail. The
155 /// retry policy controls what errors are considered retryable, sets limits
156 /// on the number of attempts or the time trying to make attempts.
157 ///
158 /// # Example
159 /// ```
160 /// # use google_cloud_pubsub::client::BasePublisher;
161 /// # async fn sample() -> anyhow::Result<()> {
162 /// use google_cloud_gax::retry_policy::RetryPolicyExt;
163 /// use google_cloud_pubsub::retry_policy::RetryableErrors;
164 /// let client = BasePublisher::builder()
165 /// .with_retry_policy(RetryableErrors.with_attempt_limit(3))
166 /// .build()
167 /// .await?;
168 /// # Ok(()) };
169 /// ```
170 pub fn with_retry_policy<V: Into<RetryPolicyArg>>(mut self, v: V) -> Self {
171 self.config.retry_policy = Some(v.into().into());
172 self
173 }
174
175 /// Configure the retry backoff policy.
176 ///
177 /// The client libraries can automatically retry operations that fail. The
178 /// backoff policy controls how long to wait in between retry attempts.
179 ///
180 /// # Example
181 /// ```
182 /// # use google_cloud_pubsub::client::BasePublisher;
183 /// # async fn sample() -> anyhow::Result<()> {
184 /// use google_cloud_gax::exponential_backoff::ExponentialBackoff;
185 /// use std::time::Duration;
186 /// let policy = ExponentialBackoff::default();
187 /// let client = BasePublisher::builder()
188 /// .with_backoff_policy(policy)
189 /// .build()
190 /// .await?;
191 /// # Ok(()) }
192 /// ```
193 pub fn with_backoff_policy<V: Into<BackoffPolicyArg>>(mut self, v: V) -> Self {
194 self.config.backoff_policy = Some(v.into().into());
195 self
196 }
197
198 /// Configure the retry throttler.
199 ///
200 /// Advanced applications may want to configure a retry throttler to
201 /// [Address Cascading Failures] and when [Handling Overload] conditions.
202 /// The client libraries throttle their retry loop, using a policy to
203 /// control the throttling algorithm. Use this method to fine tune or
204 /// customize the default retry throttler.
205 ///
206 /// [Handling Overload]: https://sre.google/sre-book/handling-overload/
207 /// [Address Cascading Failures]: https://sre.google/sre-book/addressing-cascading-failures/
208 ///
209 /// # Example
210 /// ```
211 /// # use google_cloud_pubsub::client::BasePublisher;
212 /// # async fn sample() -> anyhow::Result<()> {
213 /// use google_cloud_gax::retry_throttler::AdaptiveThrottler;
214 /// let client = BasePublisher::builder()
215 /// .with_retry_throttler(AdaptiveThrottler::default())
216 /// .build()
217 /// .await?;
218 /// # Ok(()) };
219 /// ```
220 pub fn with_retry_throttler<V: Into<RetryThrottlerArg>>(mut self, v: V) -> Self {
221 self.config.retry_throttler = v.into().into();
222 self
223 }
224
225 /// Configure the number of gRPC subchannels.
226 ///
227 /// # Example
228 /// ```
229 /// # use google_cloud_pubsub::client::BasePublisher;
230 /// # async fn sample() -> anyhow::Result<()> {
231 /// let client = BasePublisher::builder()
232 /// .with_grpc_subchannel_count(4)
233 /// .build()
234 /// .await?;
235 /// # Ok(()) }
236 /// ```
237 ///
238 /// gRPC-based clients may exhibit high latency if many requests need to be
239 /// demuxed over a single HTTP/2 connection (often called a *subchannel* in
240 /// gRPC).
241 ///
242 /// Consider using more subchannels if your application makes many
243 /// concurrent requests. Consider using fewer subchannels if your
244 /// application needs the file descriptors for other purposes.
245 pub fn with_grpc_subchannel_count(mut self, v: usize) -> Self {
246 self.config.grpc_subchannel_count = Some(v);
247 self
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use google_cloud_auth::credentials::anonymous::Builder as Anonymous;
255
256 #[test]
257 fn defaults() -> anyhow::Result<()> {
258 let builder = BasePublisherBuilder::new();
259 assert!(builder.config.endpoint.is_none(), "{builder:?}");
260 assert!(builder.config.cred.is_none(), "{builder:?}");
261 assert!(builder.config.universe_domain.is_none(), "{builder:?}");
262 assert!(!builder.config.tracing);
263 assert!(
264 format!("{:?}", builder.config).contains("AdaptiveThrottler"),
265 "{:?}",
266 builder.config
267 );
268 assert!(builder.config.backoff_policy.is_some(), "{builder:?}");
269 let debug_str = format!("{:?}", builder.config);
270 assert!(
271 debug_str.contains("initial_delay: 100ms"),
272 "actual: {debug_str}"
273 );
274 assert!(
275 debug_str.contains("maximum_delay: 60s"),
276 "actual: {debug_str}"
277 );
278 assert!(debug_str.contains("scaling: 4.0"), "actual: {debug_str}");
279 assert!(builder.config.retry_policy.is_some(), "{builder:?}");
280 assert!(
281 builder.config.grpc_subchannel_count.is_none(),
282 "{builder:?}"
283 );
284
285 Ok(())
286 }
287
288 #[tokio::test]
289 async fn setters() -> anyhow::Result<()> {
290 use google_cloud_gax::retry_policy::{AlwaysRetry, RetryPolicyExt};
291 let builder = BasePublisherBuilder::new()
292 .with_endpoint("test-endpoint.com")
293 .with_universe_domain("test-ud.com")
294 .with_credentials(Anonymous::new().build())
295 .with_tracing()
296 .with_retry_policy(AlwaysRetry.with_attempt_limit(3))
297 .with_backoff_policy(
298 google_cloud_gax::exponential_backoff::ExponentialBackoff::default(),
299 )
300 .with_retry_throttler(google_cloud_gax::retry_throttler::CircuitBreaker::default())
301 .with_grpc_subchannel_count(16);
302 assert_eq!(
303 builder.config.endpoint,
304 Some("test-endpoint.com".to_string())
305 );
306 assert_eq!(
307 builder.config.universe_domain,
308 Some("test-ud.com".to_string())
309 );
310 assert!(builder.config.cred.is_some(), "{builder:?}");
311 assert!(builder.config.tracing);
312 assert!(
313 format!("{:?}", builder.config).contains("CircuitBreaker"),
314 "{:?}",
315 builder.config
316 );
317 assert!(builder.config.retry_policy.is_some(), "{builder:?}");
318 assert!(builder.config.backoff_policy.is_some(), "{builder:?}");
319 assert_eq!(builder.config.grpc_subchannel_count, Some(16));
320
321 Ok(())
322 }
323}