launchdarkly_server_sdk/
service_endpoints.rs

1use super::client::BuildError;
2
3const 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";
6
7/// 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}
13
14impl ServiceEndpoints {
15    pub fn polling_base_url(&self) -> &str {
16        self.polling_base_url.as_ref()
17    }
18
19    pub fn streaming_base_url(&self) -> &str {
20        self.streaming_base_url.as_ref()
21    }
22
23    pub fn events_base_url(&self) -> &str {
24        self.events_base_url.as_ref()
25    }
26}
27
28/// 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}
74
75impl ServiceEndpointsBuilder {
76    /// Create a new instance of [ServiceEndpointsBuilder] with no URLs specified.
77    pub fn new() -> ServiceEndpointsBuilder {
78        ServiceEndpointsBuilder {
79            polling_base_url: None,
80            streaming_base_url: None,
81            events_base_url: None,
82        }
83    }
84
85    /// Sets a custom base URL for the polling service.
86    pub fn polling_base_url(&mut self, url: &str) -> &mut Self {
87        self.polling_base_url = Some(String::from(url));
88        self
89    }
90
91    /// Sets a custom base URL for the streaming service.
92    pub fn streaming_base_url(&mut self, url: &str) -> &mut Self {
93        self.streaming_base_url = Some(String::from(url));
94        self
95    }
96
97    /// Sets a custom base URI for the events service.
98    pub fn events_base_url(&mut self, url: &str) -> &mut Self {
99        self.events_base_url = Some(String::from(url));
100        self
101    }
102
103    /// Specifies a single base URL for a Relay Proxy instance.
104    pub fn relay_proxy(&mut self, url: &str) -> &mut Self {
105        self.polling_base_url = Some(String::from(url));
106        self.streaming_base_url = Some(String::from(url));
107        self.events_base_url = Some(String::from(url));
108        self
109    }
110
111    /// 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.
120    pub fn build(&self) -> Result<ServiceEndpoints, BuildError> {
121        match (
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)) => {
127                Ok(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}
146
147impl Default for ServiceEndpointsBuilder {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use test_case::test_case;
157
158    #[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")]
163    fn service_endpoints_trims_base_urls(url: &str, expected: &str) {
164        let 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");
170
171        assert_eq!(expected, endpoints.events_base_url());
172        assert_eq!(expected, endpoints.streaming_base_url());
173        assert_eq!(expected, endpoints.polling_base_url());
174    }
175
176    #[test]
177    fn default_configuration() -> Result<(), BuildError> {
178        let endpoints = ServiceEndpointsBuilder::new().build()?;
179        assert_eq!(
180            "https://events.launchdarkly.com",
181            endpoints.events_base_url()
182        );
183        assert_eq!(
184            "https://stream.launchdarkly.com",
185            endpoints.streaming_base_url()
186        );
187        assert_eq!("https://sdk.launchdarkly.com", endpoints.polling_base_url());
188        Ok(())
189    }
190
191    #[test]
192    fn full_custom_configuration() -> Result<(), BuildError> {
193        let 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()?;
198
199        assert_eq!(
200            "https://events.my-private-instance.com",
201            endpoints.events_base_url()
202        );
203        assert_eq!(
204            "https://stream.my-private-instance.com",
205            endpoints.streaming_base_url()
206        );
207        assert_eq!(
208            "https://sdk.my-private-instance.com",
209            endpoints.polling_base_url()
210        );
211        Ok(())
212    }
213
214    #[test]
215    fn partial_definition() {
216        assert!(ServiceEndpointsBuilder::new()
217            .polling_base_url("https://sdk.my-private-instance.com")
218            .build()
219            .is_err());
220    }
221
222    #[test]
223    fn configure_relay_proxy() -> Result<(), BuildError> {
224        let endpoints = ServiceEndpointsBuilder::new()
225            .relay_proxy("http://my-relay-hostname:8080")
226            .build()?;
227        assert_eq!("http://my-relay-hostname:8080", endpoints.events_base_url());
228        assert_eq!(
229            "http://my-relay-hostname:8080",
230            endpoints.streaming_base_url()
231        );
232        assert_eq!(
233            "http://my-relay-hostname:8080",
234            endpoints.polling_base_url()
235        );
236        Ok(())
237    }
238}