Skip to main content

launchdarkly_server_sdk/
feature_requester_builders.rs

1use crate::feature_requester::{FeatureRequester, HttpFeatureRequester};
2use crate::{LAUNCHDARKLY_INSTANCE_ID_HEADER, LAUNCHDARKLY_TAGS_HEADER};
3use http::Uri;
4use launchdarkly_sdk_transport::HttpTransport;
5use std::collections::HashMap;
6use std::str::FromStr;
7use thiserror::Error;
8
9/// Error type used to represent failures when building a [FeatureRequesterFactory] instance.
10#[non_exhaustive]
11#[derive(Debug, Error)]
12pub enum BuildError {
13    /// Error used when a configuration setting is invalid.
14    #[error("feature requester factory failed to build: {0}")]
15    InvalidConfig(String),
16}
17
18/// Trait which allows creation of feature requesters.
19///
20/// Feature requesters are used by the polling data source (see [crate::PollingDataSourceBuilder])
21/// to retrieve state information from an external resource such as the LaunchDarkly API.
22pub trait FeatureRequesterFactory: Send {
23    /// Create an instance of FeatureRequester.
24    fn build(&self, tags: Option<String>) -> Result<Box<dyn FeatureRequester>, BuildError>;
25}
26
27pub struct HttpFeatureRequesterBuilder<T: HttpTransport> {
28    url: String,
29    sdk_key: String,
30    instance_id: Option<String>,
31    transport: T,
32}
33
34impl<T: HttpTransport> HttpFeatureRequesterBuilder<T> {
35    pub fn new(url: &str, sdk_key: &str, transport: T) -> Self {
36        Self {
37            transport,
38            url: url.into(),
39            sdk_key: sdk_key.into(),
40            instance_id: None,
41        }
42    }
43
44    /// Sets the per-SDK-instance identifier that will be sent on outbound polling requests as
45    /// the `X-LaunchDarkly-Instance-Id` header. This is set by the SDK at client construction
46    /// time and is not part of the public API surface of the polling data source.
47    pub fn with_instance_id(mut self, instance_id: &str) -> Self {
48        self.instance_id = Some(instance_id.into());
49        self
50    }
51}
52
53impl<T: HttpTransport> FeatureRequesterFactory for HttpFeatureRequesterBuilder<T> {
54    fn build(&self, tags: Option<String>) -> Result<Box<dyn FeatureRequester>, BuildError> {
55        let url = format!("{}/sdk/latest-all", self.url);
56
57        let mut default_headers = HashMap::<&str, String>::new();
58
59        if let Some(tags) = tags {
60            default_headers.insert(LAUNCHDARKLY_TAGS_HEADER, tags);
61        }
62        if let Some(instance_id) = &self.instance_id {
63            default_headers.insert(LAUNCHDARKLY_INSTANCE_ID_HEADER, instance_id.clone());
64        }
65
66        let url = Uri::from_str(url.as_str())
67            .map_err(|_| BuildError::InvalidConfig("Invalid base url provided".into()))?;
68
69        Ok(Box::new(HttpFeatureRequester::new(
70            self.transport.clone(),
71            url,
72            self.sdk_key.clone(),
73            default_headers,
74        )))
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn factory_handles_url_parsing_failure() {
84        let transport =
85            launchdarkly_sdk_transport::HyperTransport::new().expect("Failed to create transport");
86        let builder = HttpFeatureRequesterBuilder::new(
87            "This is clearly not a valid URL",
88            "sdk-key",
89            transport,
90        );
91        let result = builder.build(None);
92
93        match result {
94            Err(BuildError::InvalidConfig(_)) => (),
95            _ => panic!("Build did not return the right type of error"),
96        };
97    }
98}