Skip to main content

pdk_test/services/
grpcbin.rs

1// Copyright (c) 2026, Salesforce, Inc.,
2// All rights reserved.
3// For full license text, see the LICENSE.txt file
4
5//! gRPC testing service for integration testing
6//!
7//! This module provides predefined types and configurations for GrpcBin services
8//! in integration tests. [GrpcBin](https://github.com/moul/grpcbin) is a gRPC
9//! testing service useful for testing gRPC clients and protocol interactions.
10//!
11
12use std::time::Duration;
13
14use crate::config::{Config, ContainerConfig};
15use crate::container::Container;
16use crate::error::TestError;
17use crate::image::Image;
18use crate::port::{Port, PortAccess, PortVisibility};
19use crate::probe::{MessageProbe, MessageSource};
20use crate::service::Service;
21
22const INSECURE_PORT: Port = 9000;
23const SECURE_PORT: Port = 9001;
24const GRPC_SCHEMA: &str = "grpc";
25
26/// Default GrpcBin Docker image name.
27pub const GRPCBIN_IMAGE_NAME: &str = "moul/grpcbin";
28
29/// Struct to manage the GrpcBin configuration.
30#[derive(Clone)]
31pub struct GrpcBinConfig {
32    hostname: String,
33    version: String,
34    image_name: String,
35    visibility: PortVisibility,
36    timeout: Duration,
37}
38
39impl GrpcBinConfig {
40    /// Returns the `GrpcBin` service hostname.
41    pub fn hostname(&self) -> &str {
42        &self.hostname
43    }
44
45    /// Returns the `GrpcBin` Docker version.
46    pub fn version(&self) -> &str {
47        &self.version
48    }
49
50    /// Returns the `GrpcBin` Docker image name.
51    pub fn image_name(&self) -> &str {
52        &self.image_name
53    }
54
55    /// Returns the `GrpcBin` [`PortVisibility`].
56    pub fn visibility(&self) -> PortVisibility {
57        self.visibility
58    }
59
60    /// Returns the `GrpcBin` service readiness timeout.
61    pub fn timeout(&self) -> Duration {
62        self.timeout
63    }
64
65    /// Returns a default `GrpcBin` configuration.
66    pub fn new() -> Self {
67        Self {
68            hostname: "backend".to_string(),
69            version: "latest".to_string(),
70            image_name: GRPCBIN_IMAGE_NAME.to_string(),
71            timeout: Duration::from_secs(30),
72            visibility: Default::default(),
73        }
74    }
75
76    /// Returns a builder for [`GrpcBinConfig`].
77    pub fn builder() -> GrpcBinConfigBuilder {
78        GrpcBinConfigBuilder::new()
79    }
80}
81
82/// Builder for [`GrpcBinConfig`].
83pub struct GrpcBinConfigBuilder {
84    config: GrpcBinConfig,
85}
86
87impl GrpcBinConfigBuilder {
88    fn new() -> Self {
89        Self {
90            config: GrpcBinConfig::new(),
91        }
92    }
93
94    /// Sets the `GrpcBin` service hostname.
95    /// By default set to `"grpcbin"`.
96    pub fn hostname<T: Into<String>>(self, hostname: T) -> Self {
97        Self {
98            config: GrpcBinConfig {
99                hostname: hostname.into(),
100                ..self.config
101            },
102        }
103    }
104
105    /// Sets the `GrpcBin` Docker version.
106    /// By default set to `"latest"`.
107    pub fn version<T: Into<String>>(self, version: T) -> Self {
108        Self {
109            config: GrpcBinConfig {
110                version: version.into(),
111                ..self.config
112            },
113        }
114    }
115
116    /// Sets the `GrpcBin` [`PortVisibility`].
117    /// By default set to [`PortVisibility::Exposed`].
118    pub fn visibility(self, visibility: PortVisibility) -> Self {
119        Self {
120            config: GrpcBinConfig {
121                visibility,
122                ..self.config
123            },
124        }
125    }
126
127    /// Sets the `GrpcBin` Docker image name.
128    /// By default set to [`GRPCBIN_IMAGE_NAME`].
129    pub fn image_name<T: Into<String>>(self, image_name: T) -> Self {
130        Self {
131            config: GrpcBinConfig {
132                image_name: image_name.into(),
133                ..self.config
134            },
135        }
136    }
137
138    /// Sets the `GrpcBin` service readiness timeout.
139    pub fn timeout(self, timeout: Duration) -> Self {
140        Self {
141            config: GrpcBinConfig {
142                timeout,
143                ..self.config
144            },
145        }
146    }
147
148    /// Builds a [`GrpcBinConfig`] with the configured values.
149    pub fn build(self) -> GrpcBinConfig {
150        self.config
151    }
152}
153
154impl Default for GrpcBinConfig {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160impl Config for GrpcBinConfig {
161    fn hostname(&self) -> &str {
162        &self.hostname
163    }
164
165    fn port(&self) -> Port {
166        SECURE_PORT
167    }
168
169    fn schema(&self) -> &str {
170        GRPC_SCHEMA
171    }
172
173    fn to_container_config(&self) -> Result<ContainerConfig, TestError> {
174        Ok(ContainerConfig::builder(
175            self.hostname.clone(),
176            Image::from_repository(self.image_name()).with_version(self.version()),
177        )
178        .ports([
179            PortAccess::new(INSECURE_PORT, self.visibility),
180            PortAccess::new(SECURE_PORT, self.visibility),
181        ])
182        .readiness(
183            MessageProbe::builder("listening on :")
184                .times(2)
185                .timeout(Duration::from_secs(30))
186                .source(MessageSource::StdErr)
187                .build(),
188        )
189        .build())
190    }
191}
192
193/// Represents a `GrpcBin` service instance.
194pub struct GrpcBin {
195    socket: Option<String>,
196    address: Option<String>,
197}
198
199impl GrpcBin {
200    /// Returns the `GrpcBin` external socket.
201    pub fn socket(&self) -> Option<&str> {
202        self.socket.as_deref()
203    }
204
205    /// Returns the `GrpcBin` external address.
206    pub fn address(&self) -> Option<&str> {
207        self.address.as_deref()
208    }
209}
210
211impl Service for GrpcBin {
212    type Config = GrpcBinConfig;
213
214    fn new(_config: &Self::Config, container: &Container) -> Self {
215        let socket = container.socket(INSECURE_PORT).map(String::from);
216        let address = socket.as_ref().map(|s| format!("{GRPC_SCHEMA}://{s}"));
217
218        Self { socket, address }
219    }
220}