launchdarkly_server_sdk/
feature_requester_builders.rs

1use crate::feature_requester::{FeatureRequester, HyperFeatureRequester};
2use crate::LAUNCHDARKLY_TAGS_HEADER;
3use hyper::client::connect::Connection;
4use hyper::service::Service;
5use hyper::Uri;
6use std::collections::HashMap;
7use std::str::FromStr;
8use thiserror::Error;
9use tokio::io::{AsyncRead, AsyncWrite};
10
11/// Error type used to represent failures when building a [FeatureRequesterFactory] instance.
12#[non_exhaustive]
13#[derive(Debug, Error)]
14pub enum BuildError {
15    /// Error used when a configuration setting is invalid.
16    #[error("feature requester factory failed to build: {0}")]
17    InvalidConfig(String),
18}
19
20/// Trait which allows creation of feature requesters.
21///
22/// Feature requesters are used by the polling data source (see [crate::PollingDataSourceBuilder])
23/// to retrieve state information from an external resource such as the LaunchDarkly API.
24pub trait FeatureRequesterFactory: Send {
25    /// Create an instance of FeatureRequester.
26    fn build(&self, tags: Option<String>) -> Result<Box<dyn FeatureRequester>, BuildError>;
27}
28
29pub struct HyperFeatureRequesterBuilder<C> {
30    url: String,
31    sdk_key: String,
32    http: hyper::Client<C>,
33}
34
35impl<C> HyperFeatureRequesterBuilder<C>
36where
37    C: Service<Uri> + Clone + Send + Sync + 'static,
38    C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin,
39    C::Future: Send + Unpin + 'static,
40    C::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
41{
42    pub fn new(url: &str, sdk_key: &str, connector: C) -> Self {
43        Self {
44            http: hyper::Client::builder().build(connector),
45            url: url.into(),
46            sdk_key: sdk_key.into(),
47        }
48    }
49}
50
51impl<C> FeatureRequesterFactory for HyperFeatureRequesterBuilder<C>
52where
53    C: Service<Uri> + Clone + Send + Sync + 'static,
54    C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin,
55    C::Future: Send + Unpin + 'static,
56    C::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
57{
58    fn build(&self, tags: Option<String>) -> Result<Box<dyn FeatureRequester>, BuildError> {
59        let url = format!("{}/sdk/latest-all", self.url);
60
61        let mut default_headers = HashMap::<&str, String>::new();
62
63        if let Some(tags) = tags {
64            default_headers.insert(LAUNCHDARKLY_TAGS_HEADER, tags);
65        }
66
67        let url = hyper::Uri::from_str(url.as_str())
68            .map_err(|_| BuildError::InvalidConfig("Invalid base url provided".into()))?;
69
70        Ok(Box::new(HyperFeatureRequester::new(
71            self.http.clone(),
72            url,
73            self.sdk_key.clone(),
74            default_headers,
75        )))
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use hyper::client::HttpConnector;
82
83    use super::*;
84
85    #[test]
86    fn factory_handles_url_parsing_failure() {
87        let builder = HyperFeatureRequesterBuilder::new(
88            "This is clearly not a valid URL",
89            "sdk-key",
90            HttpConnector::new(),
91        );
92        let result = builder.build(None);
93
94        match result {
95            Err(BuildError::InvalidConfig(_)) => (),
96            _ => panic!("Build did not return the right type of error"),
97        };
98    }
99}