1use super::client::BuildError;
23const DEFAULT_POLLING_BASE_URL: &str = "https://sdk.launchdarkly.com";
4const DEFAULT_STREAM_BASE_URL: &str = "https://stream.launchdarkly.com";
5const DEFAULT_EVENTS_BASE_URL: &str = "https://events.launchdarkly.com";
67/// Specifies the base service URLs used by SDK components.
8pub struct ServiceEndpoints {
9 polling_base_url: String,
10 streaming_base_url: String,
11 events_base_url: String,
12}
1314impl ServiceEndpoints {
15pub fn polling_base_url(&self) -> &str {
16self.polling_base_url.as_ref()
17 }
1819pub fn streaming_base_url(&self) -> &str {
20self.streaming_base_url.as_ref()
21 }
2223pub fn events_base_url(&self) -> &str {
24self.events_base_url.as_ref()
25 }
26}
2728/// Used for configuring the SDKs service URLs.
29///
30/// The default behavior, if you do not change any of these properties, is that the SDK will connect
31/// to the standard endpoints in the LaunchDarkly production service. There are several use cases for
32/// changing these properties:
33///
34/// - You are using the <a href="https://docs.launchdarkly.com/home/advanced/relay-proxy">LaunchDarkly
35/// Relay Proxy</a>. In this case, use [ServiceEndpointsBuilder::relay_proxy] with the URL of the
36/// relay proxy instance.
37///
38/// - You are connecting to a private instance of LaunchDarkly, rather than the standard production
39/// services. In this case, there will be custom base URIs for each service. You need to configure
40/// each endpoint with [ServiceEndpointsBuilder::polling_base_url],
41/// [ServiceEndpointsBuilder::streaming_base_url], and [ServiceEndpointsBuilder::events_base_url].
42///
43/// - You are connecting to a test fixture that simulates the service endpoints. In this case, you
44/// may set the base URLs to whatever you want, although the SDK will still set the URL paths to
45/// the expected paths for LaunchDarkly services.
46///
47/// # Examples
48///
49/// Configure for a Relay Proxy instance.
50/// ```
51/// # use launchdarkly_server_sdk::{ServiceEndpointsBuilder, ConfigBuilder};
52/// # fn main() {
53/// ConfigBuilder::new("sdk-key").service_endpoints(ServiceEndpointsBuilder::new()
54/// .relay_proxy("http://my-relay-hostname:8080"));
55/// # }
56/// ```
57///
58/// Configure for a private LaunchDarkly instance.
59/// ```
60/// # use launchdarkly_server_sdk::{ServiceEndpointsBuilder, ConfigBuilder};
61/// # fn main() {
62/// ConfigBuilder::new("sdk-key").service_endpoints(ServiceEndpointsBuilder::new()
63/// .polling_base_url("https://sdk.my-private-instance.com")
64/// .streaming_base_url("https://stream.my-private-instance.com")
65/// .events_base_url("https://events.my-private-instance.com"));
66/// # }
67/// ```
68#[derive(Clone)]
69pub struct ServiceEndpointsBuilder {
70 polling_base_url: Option<String>,
71 streaming_base_url: Option<String>,
72 events_base_url: Option<String>,
73}
7475impl ServiceEndpointsBuilder {
76/// Create a new instance of [ServiceEndpointsBuilder] with no URLs specified.
77pub fn new() -> ServiceEndpointsBuilder {
78 ServiceEndpointsBuilder {
79 polling_base_url: None,
80 streaming_base_url: None,
81 events_base_url: None,
82 }
83 }
8485/// Sets a custom base URL for the polling service.
86pub fn polling_base_url(&mut self, url: &str) -> &mut Self {
87self.polling_base_url = Some(String::from(url));
88self
89}
9091/// Sets a custom base URL for the streaming service.
92pub fn streaming_base_url(&mut self, url: &str) -> &mut Self {
93self.streaming_base_url = Some(String::from(url));
94self
95}
9697/// Sets a custom base URI for the events service.
98pub fn events_base_url(&mut self, url: &str) -> &mut Self {
99self.events_base_url = Some(String::from(url));
100self
101}
102103/// Specifies a single base URL for a Relay Proxy instance.
104pub fn relay_proxy(&mut self, url: &str) -> &mut Self {
105self.polling_base_url = Some(String::from(url));
106self.streaming_base_url = Some(String::from(url));
107self.events_base_url = Some(String::from(url));
108self
109}
110111/// Called internally by the SDK to create a configuration instance. Applications do not need
112 /// to call this method.
113 ///
114 /// # Errors
115 ///
116 /// When using custom endpoints it is important that all of the URLs are set.
117 /// If some URLs are set, but others are not, then this will return an error.
118 /// If no URLs are set, then the default values will be used.
119 /// This prevents a combination of custom and default values from being used.
120pub fn build(&self) -> Result<ServiceEndpoints, BuildError> {
121match (
122&self.polling_base_url,
123&self.streaming_base_url,
124&self.events_base_url,
125 ) {
126 (Some(polling_base_url), Some(streaming_base_url), Some(events_base_url)) => {
127Ok(ServiceEndpoints {
128 polling_base_url: polling_base_url.trim_end_matches('/').to_string(),
129 streaming_base_url: streaming_base_url.trim_end_matches('/').to_string(),
130 events_base_url: events_base_url.trim_end_matches('/').to_string(),
131 })
132 }
133 (None, None, None) => Ok(ServiceEndpoints {
134 polling_base_url: String::from(DEFAULT_POLLING_BASE_URL),
135 streaming_base_url: String::from(DEFAULT_STREAM_BASE_URL),
136 events_base_url: String::from(DEFAULT_EVENTS_BASE_URL),
137 }),
138_ => Err(BuildError::InvalidConfig(
139"If you specify any endpoints,\
140 then you must specify all endpoints."
141.into(),
142 )),
143 }
144 }
145}
146147impl Default for ServiceEndpointsBuilder {
148fn default() -> Self {
149Self::new()
150 }
151}
152153#[cfg(test)]
154mod tests {
155use super::*;
156use test_case::test_case;
157158#[test_case("localhost", "localhost"; "requires no trimming")]
159 #[test_case("http://localhost", "http://localhost"; "requires no trimming with scheme")]
160 #[test_case("localhost/", "localhost"; "trims trailing slash")]
161 #[test_case("http://localhost/", "http://localhost"; "trims trailing slash with scheme")]
162 #[test_case("localhost////////", "localhost"; "trims multiple trailing slashes")]
163fn service_endpoints_trims_base_urls(url: &str, expected: &str) {
164let endpoints = ServiceEndpointsBuilder::new()
165 .polling_base_url(url)
166 .streaming_base_url(url)
167 .events_base_url(url)
168 .build()
169 .expect("Provided URLs should parse successfully");
170171assert_eq!(expected, endpoints.events_base_url());
172assert_eq!(expected, endpoints.streaming_base_url());
173assert_eq!(expected, endpoints.polling_base_url());
174 }
175176#[test]
177fn default_configuration() -> Result<(), BuildError> {
178let endpoints = ServiceEndpointsBuilder::new().build()?;
179assert_eq!(
180"https://events.launchdarkly.com",
181 endpoints.events_base_url()
182 );
183assert_eq!(
184"https://stream.launchdarkly.com",
185 endpoints.streaming_base_url()
186 );
187assert_eq!("https://sdk.launchdarkly.com", endpoints.polling_base_url());
188Ok(())
189 }
190191#[test]
192fn full_custom_configuration() -> Result<(), BuildError> {
193let endpoints = ServiceEndpointsBuilder::new()
194 .polling_base_url("https://sdk.my-private-instance.com")
195 .streaming_base_url("https://stream.my-private-instance.com")
196 .events_base_url("https://events.my-private-instance.com")
197 .build()?;
198199assert_eq!(
200"https://events.my-private-instance.com",
201 endpoints.events_base_url()
202 );
203assert_eq!(
204"https://stream.my-private-instance.com",
205 endpoints.streaming_base_url()
206 );
207assert_eq!(
208"https://sdk.my-private-instance.com",
209 endpoints.polling_base_url()
210 );
211Ok(())
212 }
213214#[test]
215fn partial_definition() {
216assert!(ServiceEndpointsBuilder::new()
217 .polling_base_url("https://sdk.my-private-instance.com")
218 .build()
219 .is_err());
220 }
221222#[test]
223fn configure_relay_proxy() -> Result<(), BuildError> {
224let endpoints = ServiceEndpointsBuilder::new()
225 .relay_proxy("http://my-relay-hostname:8080")
226 .build()?;
227assert_eq!("http://my-relay-hostname:8080", endpoints.events_base_url());
228assert_eq!(
229"http://my-relay-hostname:8080",
230 endpoints.streaming_base_url()
231 );
232assert_eq!(
233"http://my-relay-hostname:8080",
234 endpoints.polling_base_url()
235 );
236Ok(())
237 }
238}