From d21bfb44cc01dd0f798028876666784c9aa64ed6 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Mon, 12 Jan 2026 12:58:27 -0500 Subject: [PATCH 1/4] feat!: Upgrade to hyper 1.0 Update from hyper 0.14 to hyper 1.0 ecosystem, matching the eventsource-client dependency. This includes: - Upgrade http crate from 0.2 to 1.0 - Use hyper-util for legacy Client API - Use http-body-util for body types - Update hyper-rustls from 0.24 to 0.27 - Update all trait bounds and type annotations - Add tower as direct dependency for Service trait BREAKING CHANGE: hyper-related trait bounds have changed for custom connector implementations. The Future associated type now requires Unpin bound, and trait bounds have been updated to use tower::Service and hyper_util::client::legacy::connect types. Co-Authored-By: Claude Sonnet 4.5 --- contract-tests/Cargo.toml | 12 ++--- contract-tests/src/client_entity.rs | 2 +- contract-tests/src/main.rs | 12 ++--- launchdarkly-server-sdk/Cargo.toml | 9 +++- .../examples/print_flags.rs | 2 +- launchdarkly-server-sdk/src/client.rs | 27 ++++------- launchdarkly-server-sdk/src/config.rs | 4 +- launchdarkly-server-sdk/src/data_source.rs | 14 +++--- .../src/data_source_builders.rs | 17 ++++--- .../src/events/dispatcher.rs | 10 ++-- launchdarkly-server-sdk/src/events/event.rs | 6 +-- .../src/events/processor_builders.rs | 17 +++---- launchdarkly-server-sdk/src/events/sender.rs | 47 ++++++++++--------- .../src/feature_requester.rs | 47 +++++++++++-------- .../src/feature_requester_builders.rs | 22 ++++----- launchdarkly-server-sdk/src/migrations/mod.rs | 2 +- .../src/migrations/tracker.rs | 6 +-- .../src/stores/persistent_store_wrapper.rs | 19 ++++---- launchdarkly-server-sdk/src/stores/store.rs | 2 +- 19 files changed, 138 insertions(+), 139 deletions(-) diff --git a/contract-tests/Cargo.toml b/contract-tests/Cargo.toml index 1135866..2e1cf4a 100644 --- a/contract-tests/Cargo.toml +++ b/contract-tests/Cargo.toml @@ -16,15 +16,13 @@ launchdarkly-server-sdk = { path = "../launchdarkly-server-sdk/", default-featur serde = { version = "1.0.132", features = ["derive"] } serde_json = "1.0.73" futures = "0.3.12" -hyper = { version = "0.14.19", features = ["client"] } -hyper-rustls = { version = "0.24.1" , optional = true, features = ["http2"]} -hyper-tls = { version = "0.5.0", optional = true } -hyper1-tls = { package = "hyper-tls", version = "0.6.0", optional = true } -hyper-util = { version = "0.1", features = ["client-legacy", "http1", "http2", "tokio"], optional = true } +hyper-util = { version = "0.1", features = ["client-legacy", "http1", "http2", "tokio"] } +hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "webpki-roots"], optional = true } +hyper-tls = { version = "0.6.0", optional = true } reqwest = { version = "0.12.4", features = ["default", "blocking", "json"] } async-mutex = "1.4.0" [features] default = ["rustls"] -rustls = ["hyper-rustls/http1", "hyper-rustls/http2", "launchdarkly-server-sdk/rustls", "hyper-util"] -tls = ["hyper-tls", "hyper1-tls", "hyper-util"] +rustls = ["hyper-rustls", "launchdarkly-server-sdk/rustls"] +tls = ["hyper-tls"] diff --git a/contract-tests/src/client_entity.rs b/contract-tests/src/client_entity.rs index 0bcb72e..f4931e7 100644 --- a/contract-tests/src/client_entity.rs +++ b/contract-tests/src/client_entity.rs @@ -357,7 +357,7 @@ impl ClientEntity { )), } } - command => Err(format!("Invalid command requested: {}", command)), + command => Err(format!("Invalid command requested: {command}")), } } diff --git a/contract-tests/src/main.rs b/contract-tests/src/main.rs index ad400f0..e4b1b36 100644 --- a/contract-tests/src/main.rs +++ b/contract-tests/src/main.rs @@ -7,7 +7,7 @@ use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder, Resu use async_mutex::Mutex; use client_entity::ClientEntity; use futures::executor; -use hyper::client::HttpConnector; +use hyper_util::client::legacy::connect::HttpConnector; use launchdarkly_server_sdk::Reference; use serde::{self, Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; @@ -137,7 +137,7 @@ async fn create_client( .await { Ok(ce) => ce, - Err(e) => return HttpResponse::InternalServerError().body(format!("{}", e)), + Err(e) => return HttpResponse::InternalServerError().body(format!("{e}")), }; let mut counter = app_state.counter.lock().await; @@ -218,8 +218,7 @@ type StreamingHttpsConnector = hyper_util::client::legacy::connect::HttpConnecto #[cfg(feature = "tls")] type HttpsConnector = hyper_tls::HttpsConnector; #[cfg(feature = "tls")] -type StreamingHttpsConnector = - hyper1_tls::HttpsConnector; +type StreamingHttpsConnector = hyper_tls::HttpsConnector; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -241,13 +240,14 @@ async fn main() -> std::io::Result<()> { #[cfg(feature = "rustls")] let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_native_roots() + .expect("Failed to load native root certificates") .https_or_http() .enable_http1() .enable_http2() .build(); #[cfg(feature = "tls")] - let streaming_https_connector = hyper1_tls::HttpsConnector::new(); + let streaming_https_connector = hyper_tls::HttpsConnector::new(); #[cfg(feature = "tls")] let connector = hyper_tls::HttpsConnector::new(); @@ -255,7 +255,7 @@ async fn main() -> std::io::Result<()> { counter: Mutex::new(0), client_entities: Mutex::new(HashMap::new()), https_connector: connector, - streaming_https_connector: streaming_https_connector, + streaming_https_connector, }); let server = HttpServer::new(move || { diff --git a/launchdarkly-server-sdk/Cargo.toml b/launchdarkly-server-sdk/Cargo.toml index 55bae78..9e6e2b8 100644 --- a/launchdarkly-server-sdk/Cargo.toml +++ b/launchdarkly-server-sdk/Cargo.toml @@ -35,8 +35,13 @@ parking_lot = "0.12.0" tokio-stream = { version = "0.1.8", features = ["sync"] } moka = { version = "0.12.1", features = ["sync"] } uuid = {version = "1.2.2", features = ["v4"] } -hyper = { version = "0.14.19", features = ["client", "http1", "http2", "tcp"] } -hyper-rustls = { version = "0.24.1" , optional = true} +http = "1.0" +bytes = "1.11" +hyper = { version = "1.0", features = ["client", "http1", "http2"] } +hyper-util = { version = "0.1", features = ["client-legacy", "http1", "http2", "tokio"] } +http-body-util = { version = "0.1" } +hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "webpki-roots"], optional = true} +tower = { version = "0.4" } rand = "0.9" flate2 = { version = "1.0.35", optional = true } aws-lc-rs = "1.14.1" diff --git a/launchdarkly-server-sdk/examples/print_flags.rs b/launchdarkly-server-sdk/examples/print_flags.rs index 5a7c3b3..168d918 100644 --- a/launchdarkly-server-sdk/examples/print_flags.rs +++ b/launchdarkly-server-sdk/examples/print_flags.rs @@ -27,7 +27,7 @@ async fn main() { } else if let ["str", name] = bits { str_flags.push(name.to_string()); } else if let [flag_type, _] = bits { - error!("Unsupported flag type {} in {}", flag_type, flag); + error!("Unsupported flag type {flag_type} in {flag}"); exit(2); } else if let [name] = bits { bool_flags.push(name.to_string()); diff --git a/launchdarkly-server-sdk/src/client.rs b/launchdarkly-server-sdk/src/client.rs index bd3d6d9..9d2c8b3 100644 --- a/launchdarkly-server-sdk/src/client.rs +++ b/launchdarkly-server-sdk/src/client.rs @@ -294,10 +294,7 @@ impl Client { } let initialized = tokio::time::timeout(timeout, self.initialized_async_internal()).await; - match initialized { - Ok(result) => Some(result), - Err(_) => None, - } + initialized.ok() } async fn initialized_async_internal(&self) -> bool { @@ -335,7 +332,7 @@ impl Client { // broadcast channel, so sending on it would always result in an error. if !self.offline && !self.daemon_mode { if let Err(e) = self.shutdown_broadcast.send(()) { - error!("Failed to shutdown client appropriately: {}", e); + error!("Failed to shutdown client appropriately: {e}"); } } @@ -380,8 +377,7 @@ impl Client { b } else { warn!( - "bool_variation called for a non-bool flag {:?} (got {:?})", - flag_key, val + "bool_variation called for a non-bool flag {flag_key:?} (got {val:?})" ); default } @@ -400,8 +396,7 @@ impl Client { s } else { warn!( - "str_variation called for a non-string flag {:?} (got {:?})", - flag_key, val + "str_variation called for a non-string flag {flag_key:?} (got {val:?})" ); default } @@ -420,8 +415,7 @@ impl Client { f } else { warn!( - "float_variation called for a non-float flag {:?} (got {:?})", - flag_key, val + "float_variation called for a non-float flag {flag_key:?} (got {val:?})" ); default } @@ -440,8 +434,7 @@ impl Client { f } else { warn!( - "int_variation called for a non-int flag {:?} (got {:?})", - flag_key, val + "int_variation called for a non-int flag {flag_key:?} (got {val:?})" ); default } @@ -761,14 +754,12 @@ impl Client { ); } Err(e) => error!( - "Failed to build migration event, no event will be sent: {}", - e + "Failed to build migration event, no event will be sent: {e}" ), } } Err(e) => error!( - "Failed to lock migration tracker, no event will be sent: {}", - e + "Failed to lock migration tracker, no event will be sent: {e}" ), } } @@ -849,7 +840,7 @@ mod tests { use crossbeam_channel::Receiver; use eval::{ContextBuilder, MultiContextBuilder}; use futures::FutureExt; - use hyper::client::HttpConnector; + use hyper_util::client::legacy::connect::HttpConnector; use launchdarkly_server_sdk_evaluation::{Flag, Reason, Segment}; use maplit::hashmap; use std::collections::HashMap; diff --git a/launchdarkly-server-sdk/src/config.rs b/launchdarkly-server-sdk/src/config.rs index 8e738df..b4d21ad 100644 --- a/launchdarkly-server-sdk/src/config.rs +++ b/launchdarkly-server-sdk/src/config.rs @@ -86,7 +86,7 @@ impl ApplicationInfo { match tag.is_valid() { Ok(_) => self.tags.push(tag), Err(e) => { - warn!("{}", e) + warn!("{e}") } } @@ -322,7 +322,7 @@ impl ConfigBuilder { Some(builder) => Ok(builder), #[cfg(feature = "rustls")] None => Ok(Box::new(EventProcessorBuilder::< - hyper_rustls::HttpsConnector, + hyper_rustls::HttpsConnector, >::new())), #[cfg(not(feature = "rustls"))] None => Err(BuildError::InvalidConfig( diff --git a/launchdarkly-server-sdk/src/data_source.rs b/launchdarkly-server-sdk/src/data_source.rs index a533281..022abcc 100644 --- a/launchdarkly-server-sdk/src/data_source.rs +++ b/launchdarkly-server-sdk/src/data_source.rs @@ -76,7 +76,7 @@ impl StreamingDataSource { tags: &Option, transport: T, ) -> std::result::Result { - let stream_url = format!("{}/all", base_url); + let stream_url = format!("{base_url}/all"); let client_builder = ClientBuilder::for_url(&stream_url)?; let mut client_builder = client_builder @@ -126,7 +126,7 @@ impl DataSource for StreamingDataSource { continue; }, es::SSE::Comment(str)=> { - debug!("data source got a comment: {}", str); + debug!("data source got a comment: {str}"); continue; }, es::SSE::Event(ev) => ev, @@ -147,7 +147,7 @@ impl DataSource for StreamingDataSource { continue; } _ => { - error!("unhandled error on event stream: {:?}", e); + error!("unhandled error on event stream: {e:?}"); break; } } @@ -171,7 +171,7 @@ impl DataSource for StreamingDataSource { }; if let Err(e) = stored { init_success = false; - error!("error processing update: {:?}", e); + error!("error processing update: {e:?}"); } notify_init.call_once(|| (init_complete)(init_success)); @@ -213,12 +213,12 @@ impl DataSource for PollingDataSource { Ok(factory) => match factory.build(self.tags.clone()) { Ok(requester) => requester, Err(e) => { - error!("{:?}", e); + error!("{e:?}"); return; } }, Err(e) => { - error!("{:?}", e); + error!("{e:?}"); return; } }; @@ -366,7 +366,7 @@ mod tests { time::Duration, }; - use hyper::client::HttpConnector; + use hyper_util::client::legacy::connect::HttpConnector; use mockito::Matcher; use parking_lot::RwLock; use test_case::test_case; diff --git a/launchdarkly-server-sdk/src/data_source_builders.rs b/launchdarkly-server-sdk/src/data_source_builders.rs index 2d1d566..02e105b 100644 --- a/launchdarkly-server-sdk/src/data_source_builders.rs +++ b/launchdarkly-server-sdk/src/data_source_builders.rs @@ -2,13 +2,12 @@ use super::service_endpoints; use crate::data_source::{DataSource, NullDataSource, PollingDataSource, StreamingDataSource}; use crate::feature_requester_builders::{FeatureRequesterFactory, HyperFeatureRequesterBuilder}; use eventsource_client as es; -use hyper::{client::connect::Connection, service::Service, Uri}; +use http::Uri; #[cfg(feature = "rustls")] use hyper_rustls::HttpsConnectorBuilder; use std::sync::{Arc, Mutex}; use std::time::Duration; use thiserror::Error; -use tokio::io::{AsyncRead, AsyncWrite}; #[cfg(test)] use super::data_source; @@ -115,7 +114,7 @@ impl DataSourceFactory for StreamingDataSourceBuilder { )), }; let data_source = data_source_result? - .map_err(|e| BuildError::InvalidConfig(format!("invalid stream_base_url: {:?}", e)))?; + .map_err(|e| BuildError::InvalidConfig(format!("invalid stream_base_url: {e:?}")))?; Ok(Arc::new(data_source)) } @@ -176,7 +175,7 @@ impl Default for NullDataSourceBuilder { /// ``` /// # use launchdarkly_server_sdk::{PollingDataSourceBuilder, ConfigBuilder}; /// # use hyper_rustls::HttpsConnector; -/// # use hyper::client::HttpConnector; +/// # use hyper_util::client::legacy::connect::HttpConnector; /// # use std::time::Duration; /// # fn main() { /// ConfigBuilder::new("sdk-key").data_source(PollingDataSourceBuilder::>::new() @@ -207,7 +206,7 @@ pub struct PollingDataSourceBuilder { /// # use launchdarkly_server_sdk::{PollingDataSourceBuilder, ConfigBuilder}; /// # use std::time::Duration; /// # use hyper_rustls::HttpsConnector; -/// # use hyper::client::HttpConnector; +/// # use hyper_util::client::legacy::connect::HttpConnector; /// # fn main() { /// ConfigBuilder::new("sdk-key").data_source(PollingDataSourceBuilder::>::new() /// .poll_interval(Duration::from_secs(60))); @@ -243,8 +242,8 @@ impl PollingDataSourceBuilder { impl DataSourceFactory for PollingDataSourceBuilder where - C: Service + Clone + Send + Sync + 'static, - C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin, + C: tower::Service + Clone + Send + Sync + 'static, + C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { @@ -259,7 +258,7 @@ where #[cfg(feature = "rustls")] None => { let connector = HttpsConnectorBuilder::new() - .with_native_roots() + .with_webpki_roots() .https_or_http() .enable_http1() .enable_http2() @@ -342,7 +341,7 @@ impl DataSourceFactory for MockDataSourceBuilder { #[cfg(test)] mod tests { use eventsource_client::{HyperTransport, ResponseFuture}; - use hyper::client::HttpConnector; + use hyper_util::client::legacy::connect::HttpConnector; use super::*; diff --git a/launchdarkly-server-sdk/src/events/dispatcher.rs b/launchdarkly-server-sdk/src/events/dispatcher.rs index df59165..5921fb5 100644 --- a/launchdarkly-server-sdk/src/events/dispatcher.rs +++ b/launchdarkly-server-sdk/src/events/dispatcher.rs @@ -108,7 +108,7 @@ impl EventDispatcher { let rt = match rt { Ok(rt) => rt, Err(e) => { - error!("Could not start runtime for event sending: {}", e); + error!("Could not start runtime for event sending: {e}"); return; } }; @@ -128,7 +128,7 @@ impl EventDispatcher { }, Ok(_) => continue, Err(e) => { - error!("event_result_rx is disconnected. Shutting down dispatcher: {}", e); + error!("event_result_rx is disconnected. Shutting down dispatcher: {e}"); return; } }, @@ -158,7 +158,7 @@ impl EventDispatcher { return; } Err(e) => { - error!("inbox_rx is disconnected. Shutting down dispatcher: {}", e); + error!("inbox_rx is disconnected. Shutting down dispatcher: {e}"); return; } } @@ -302,11 +302,11 @@ impl EventDispatcher { let key = context.canonical_key(); if self.context_keys.get(key).is_none() { - trace!("noticing new context {:?}", key); + trace!("noticing new context {key:?}"); self.context_keys.put(key.to_owned(), ()); true } else { - trace!("ignoring already-seen context {:?}", key); + trace!("ignoring already-seen context {key:?}"); false } } diff --git a/launchdarkly-server-sdk/src/events/event.rs b/launchdarkly-server-sdk/src/events/event.rs index 741694b..665f08c 100644 --- a/launchdarkly-server-sdk/src/events/event.rs +++ b/launchdarkly-server-sdk/src/events/event.rs @@ -476,8 +476,8 @@ impl InputEvent { impl Display for InputEvent { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let json = serde_json::to_string_pretty(self) - .unwrap_or_else(|e| format!("JSON serialization failed ({}): {:?}", e, self)); - write!(f, "{}", json) + .unwrap_or_else(|e| format!("JSON serialization failed ({e}): {self:?}")); + write!(f, "{json}") } } @@ -1600,7 +1600,7 @@ mod tests { variation_index: Some(1), reason: Reason::RuleMatch { rule_index, - rule_id: format!("rule-{}", rule_index), + rule_id: format!("rule-{rule_index}"), in_experiment: rule_in_experiment, }, }; diff --git a/launchdarkly-server-sdk/src/events/processor_builders.rs b/launchdarkly-server-sdk/src/events/processor_builders.rs index 75b7e8e..9bf40f7 100644 --- a/launchdarkly-server-sdk/src/events/processor_builders.rs +++ b/launchdarkly-server-sdk/src/events/processor_builders.rs @@ -4,14 +4,11 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; -use hyper::client::connect::Connection; -use hyper::service::Service; -use hyper::Uri; +use http::Uri; #[cfg(feature = "rustls")] use hyper_rustls::HttpsConnectorBuilder; use launchdarkly_server_sdk_evaluation::Reference; use thiserror::Error; -use tokio::io::{AsyncRead, AsyncWrite}; use crate::events::sender::HyperEventSender; use crate::{service_endpoints, LAUNCHDARKLY_TAGS_HEADER}; @@ -66,7 +63,7 @@ pub trait EventProcessorFactory { /// ``` /// # use launchdarkly_server_sdk::{EventProcessorBuilder, ConfigBuilder}; /// # use hyper_rustls::HttpsConnector; -/// # use hyper::client::HttpConnector; +/// # use hyper_util::client::legacy::connect::HttpConnector; /// # use std::time::Duration; /// # fn main() { /// ConfigBuilder::new("sdk-key").event_processor(EventProcessorBuilder::>::new() @@ -90,8 +87,8 @@ pub struct EventProcessorBuilder { impl EventProcessorFactory for EventProcessorBuilder where - C: Service + Clone + Send + Sync + 'static, - C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin, + C: tower::Service + Clone + Send + Sync + 'static, + C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { @@ -125,7 +122,7 @@ where #[cfg(feature = "rustls")] { let connector = HttpsConnectorBuilder::new() - .with_native_roots() + .with_webpki_roots() .https_or_http() .enable_http1() .enable_http2() @@ -133,7 +130,7 @@ where Ok(Arc::new(HyperEventSender::new( connector, - hyper::Uri::from_str(url_string.as_str()).unwrap(), + Uri::from_str(url_string.as_str()).unwrap(), sdk_key, default_headers, self.compress_events, @@ -324,7 +321,7 @@ impl Default for NullEventProcessorBuilder { #[cfg(test)] mod tests { - use hyper::client::HttpConnector; + use hyper_util::client::legacy::connect::HttpConnector; use launchdarkly_server_sdk_evaluation::ContextBuilder; use maplit::hashset; use mockito::Matcher; diff --git a/launchdarkly-server-sdk/src/events/sender.rs b/launchdarkly-server-sdk/src/events/sender.rs index f8b6a54..778442c 100644 --- a/launchdarkly-server-sdk/src/events/sender.rs +++ b/launchdarkly-server-sdk/src/events/sender.rs @@ -13,12 +13,11 @@ use flate2::Compression; #[cfg(feature = "event-compression")] use std::io::Write; +use bytes::Bytes; use futures::future::BoxFuture; -use hyper::{client::connect::Connection, service::Service, Uri}; -use tokio::{ - io::{AsyncRead, AsyncWrite}, - time::{sleep, Duration}, -}; +use http_body_util::{BodyExt, Full}; +use hyper_util::{client::legacy::Client as HyperClient, rt::TokioExecutor}; +use tokio::time::{sleep, Duration}; use uuid::Uuid; use super::event::OutputEvent; @@ -39,9 +38,9 @@ pub trait EventSender: Send + Sync { #[derive(Clone)] pub struct HyperEventSender { - url: hyper::Uri, + url: http::Uri, sdk_key: String, - http: hyper::Client, + http: HyperClient>>, default_headers: HashMap<&'static str, String>, // used with event-compression feature @@ -51,14 +50,14 @@ pub struct HyperEventSender { impl HyperEventSender where - C: Service + Clone + Send + Sync + 'static, - C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin, + C: tower::Service + Clone + Send + Sync + 'static, + C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { pub fn new( connector: C, - url: hyper::Uri, + url: http::Uri, sdk_key: &str, default_headers: HashMap<&'static str, String>, compress_events: bool, @@ -66,13 +65,13 @@ where Self { url, sdk_key: sdk_key.to_owned(), - http: hyper::Client::builder().build(connector), + http: HyperClient::builder(TokioExecutor::new()).build(connector), default_headers, compress_events, } } - fn get_server_time_from_response(&self, response: &hyper::Response) -> u128 { + fn get_server_time_from_response(&self, response: &http::Response) -> u128 { let date_value = response .headers() .get("date") @@ -90,8 +89,8 @@ where impl EventSender for HyperEventSender where - C: Service + Clone + Send + Sync + 'static, - C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin, + C: tower::Service + Clone + Send + Sync + 'static, + C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { @@ -115,8 +114,7 @@ where Ok(json) => json, Err(e) => { error!( - "Failed to serialize event payload. Some events were dropped: {:?}", - e + "Failed to serialize event payload. Some events were dropped: {e:?}" ); return; } @@ -158,7 +156,14 @@ where request_builder = request_builder.header(*default_header.0, default_header.1.as_str()); } - let request = request_builder.body(hyper::Body::from(payload.clone())); + + // Convert Vec to BoxBody for hyper 1.0 + let body_bytes = Bytes::from(payload.clone()); + let boxed_body: http_body_util::combinators::BoxBody> = + Full::new(body_bytes) + .map_err(|e| Box::new(e) as Box) + .boxed(); + let request = request_builder.body(boxed_body); let result = self.http.request(request.unwrap()).await; @@ -168,7 +173,7 @@ where Err(e) => { // It appears this type of error will not be an HTTP error. // It will be a closed connection, aborted write, timeout, etc. - error!("Failed to send events. Some events were dropped: {:?}", e); + error!("Failed to send events. Some events were dropped: {e:?}"); result_tx .send(EventSenderResult { success: false, @@ -355,12 +360,12 @@ mod tests { assert_eq!(sender_result.time_from_server, 1234567890000); } - fn build_event_sender(url: String) -> HyperEventSender { + fn build_event_sender(url: String) -> HyperEventSender { let url = format!("{}/bulk", &url); - let url = hyper::Uri::from_str(&url).expect("Failed parsing the mock server url"); + let url = http::Uri::from_str(&url).expect("Failed parsing the mock server url"); HyperEventSender::new( - hyper::client::HttpConnector::new(), + hyper_util::client::legacy::connect::HttpConnector::new(), url, "sdk-key", HashMap::<&str, String>::new(), diff --git a/launchdarkly-server-sdk/src/feature_requester.rs b/launchdarkly-server-sdk/src/feature_requester.rs index e435036..6d3c560 100644 --- a/launchdarkly-server-sdk/src/feature_requester.rs +++ b/launchdarkly-server-sdk/src/feature_requester.rs @@ -1,6 +1,8 @@ use crate::reqwest::is_http_error_recoverable; +use bytes::Bytes; use futures::future::BoxFuture; -use hyper::Body; +use http_body_util::{BodyExt, Empty}; +use hyper_util::client::legacy::Client as HyperClient; use std::collections::HashMap; use std::sync::Arc; @@ -21,8 +23,8 @@ pub trait FeatureRequester: Send { } pub struct HyperFeatureRequester { - http: Arc>, - url: hyper::Uri, + http: Arc>>>, + url: http::Uri, sdk_key: String, cache: Option, default_headers: HashMap<&'static str, String>, @@ -30,8 +32,8 @@ pub struct HyperFeatureRequester { impl HyperFeatureRequester { pub fn new( - http: hyper::Client, - url: hyper::Uri, + http: HyperClient>>, + url: http::Uri, sdk_key: String, default_headers: HashMap<&'static str, String>, ) -> Self { @@ -47,7 +49,7 @@ impl HyperFeatureRequester { impl FeatureRequester for HyperFeatureRequester where - C: hyper::client::connect::Connect + Clone + Send + Sync + 'static, + C: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static, { fn get_all(&mut self) -> BoxFuture, FeatureRequesterError>> { Box::pin(async { @@ -73,8 +75,14 @@ where request_builder = request_builder.header("If-None-Match", cache.1.clone()); } + // Create empty body for GET request + let empty_body: http_body_util::combinators::BoxBody> = + Empty::::new() + .map_err(|e| Box::new(e) as Box) + .boxed(); + let result = http - .request(request_builder.body(Body::empty()).unwrap()) + .request(request_builder.body(empty_body).unwrap()) .await; let response = match result { @@ -82,7 +90,7 @@ where Err(e) => { // It appears this type of error will not be an HTTP error. // It will be a closed connection, aborted write, timeout, etc. - error!("An error occurred while retrieving flag information {}", e,); + error!("An error occurred while retrieving flag information {e}",); return Err(FeatureRequesterError::Temporary); } }; @@ -101,27 +109,26 @@ where .map_or_else(|_| "".into(), |s| s.into()); if response.status().is_success() { - let bytes = hyper::body::to_bytes(response.into_body()) - .await + let body_bytes = response.into_body().collect().await .map_err(|e| { error!( - "An error occurred while reading the polling response body: {}", - e + "An error occurred while reading the polling response body: {e}" ); FeatureRequesterError::Temporary - })?; - let json = serde_json::from_slice::>(bytes.as_ref()); + })? + .to_bytes(); + let json = serde_json::from_slice::>(body_bytes.as_ref()); return match json { Ok(all_data) => { if !etag.is_empty() { - debug!("Caching data for future use with etag: {}", etag); + debug!("Caching data for future use with etag: {etag}"); self.cache = Some(CachedEntry(all_data.clone(), etag)); } Ok(all_data) } Err(e) => { - error!("An error occurred while parsing the json response: {}", e); + error!("An error occurred while parsing the json response: {e}"); Err(FeatureRequesterError::Temporary) } }; @@ -246,9 +253,11 @@ mod tests { } } - fn build_feature_requester(url: String) -> HyperFeatureRequester { - let http = hyper::Client::builder().build(hyper::client::HttpConnector::new()); - let url = hyper::Uri::from_str(&url).expect("Failed parsing the mock server url"); + fn build_feature_requester(url: String) -> HyperFeatureRequester { + use hyper_util::rt::TokioExecutor; + let connector = hyper_util::client::legacy::connect::HttpConnector::new(); + let http = HyperClient::builder(TokioExecutor::new()).build(connector); + let url = http::Uri::from_str(&url).expect("Failed parsing the mock server url"); HyperFeatureRequester::new( http, diff --git a/launchdarkly-server-sdk/src/feature_requester_builders.rs b/launchdarkly-server-sdk/src/feature_requester_builders.rs index 1d7f1ee..f009393 100644 --- a/launchdarkly-server-sdk/src/feature_requester_builders.rs +++ b/launchdarkly-server-sdk/src/feature_requester_builders.rs @@ -1,12 +1,10 @@ use crate::feature_requester::{FeatureRequester, HyperFeatureRequester}; use crate::LAUNCHDARKLY_TAGS_HEADER; -use hyper::client::connect::Connection; -use hyper::service::Service; -use hyper::Uri; +use http::Uri; +use hyper_util::{client::legacy::Client as HyperClient, rt::TokioExecutor}; use std::collections::HashMap; use std::str::FromStr; use thiserror::Error; -use tokio::io::{AsyncRead, AsyncWrite}; /// Error type used to represent failures when building a [FeatureRequesterFactory] instance. #[non_exhaustive] @@ -29,19 +27,19 @@ pub trait FeatureRequesterFactory: Send { pub struct HyperFeatureRequesterBuilder { url: String, sdk_key: String, - http: hyper::Client, + http: HyperClient>>, } impl HyperFeatureRequesterBuilder where - C: Service + Clone + Send + Sync + 'static, - C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin, + C: tower::Service + Clone + Send + Sync + 'static, + C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { pub fn new(url: &str, sdk_key: &str, connector: C) -> Self { Self { - http: hyper::Client::builder().build(connector), + http: HyperClient::builder(TokioExecutor::new()).build(connector), url: url.into(), sdk_key: sdk_key.into(), } @@ -50,8 +48,8 @@ where impl FeatureRequesterFactory for HyperFeatureRequesterBuilder where - C: Service + Clone + Send + Sync + 'static, - C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin, + C: tower::Service + Clone + Send + Sync + 'static, + C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { @@ -64,7 +62,7 @@ where default_headers.insert(LAUNCHDARKLY_TAGS_HEADER, tags); } - let url = hyper::Uri::from_str(url.as_str()) + let url = Uri::from_str(url.as_str()) .map_err(|_| BuildError::InvalidConfig("Invalid base url provided".into()))?; Ok(Box::new(HyperFeatureRequester::new( @@ -78,7 +76,7 @@ where #[cfg(test)] mod tests { - use hyper::client::HttpConnector; + use hyper_util::client::legacy::connect::HttpConnector; use super::*; diff --git a/launchdarkly-server-sdk/src/migrations/mod.rs b/launchdarkly-server-sdk/src/migrations/mod.rs index 91f9541..c3dea2c 100644 --- a/launchdarkly-server-sdk/src/migrations/mod.rs +++ b/launchdarkly-server-sdk/src/migrations/mod.rs @@ -79,7 +79,7 @@ impl TryFrom for Stage { "live" => Ok(Stage::Live), "rampdown" => Ok(Stage::Rampdown), "complete" => Ok(Stage::Complete), - _ => Err(format!("Invalid stage: {}", value)), + _ => Err(format!("Invalid stage: {value}")), } } else { Err("Cannot convert non-string value to Stage".to_string()) diff --git a/launchdarkly-server-sdk/src/migrations/tracker.rs b/launchdarkly-server-sdk/src/migrations/tracker.rs index 547d294..d1ec6b1 100644 --- a/launchdarkly-server-sdk/src/migrations/tracker.rs +++ b/launchdarkly-server-sdk/src/migrations/tracker.rs @@ -140,15 +140,13 @@ impl MigrationOpTracker { if self.errors.contains(origin) { return Err(format!( - "provided error for origin {:?} without recording invocation", - origin + "provided error for origin {origin:?} without recording invocation" )); } if self.latencies.contains_key(origin) { return Err(format!( - "provided latency for origin {:?} without recording invocation", - origin + "provided latency for origin {origin:?} without recording invocation" )); } } diff --git a/launchdarkly-server-sdk/src/stores/persistent_store_wrapper.rs b/launchdarkly-server-sdk/src/stores/persistent_store_wrapper.rs index a2831e6..f54feea 100644 --- a/launchdarkly-server-sdk/src/stores/persistent_store_wrapper.rs +++ b/launchdarkly-server-sdk/src/stores/persistent_store_wrapper.rs @@ -145,14 +145,14 @@ impl Store for PersistentDataStoreWrapper { item.into() } Err(e) => { - warn!("failed to convert serialized item into flag: {}", e); + warn!("failed to convert serialized item into flag: {e}"); None } } } Ok(None) => None, Err(e) => { - warn!("persistent store failed to retrieve flag: {}", e); + warn!("persistent store failed to retrieve flag: {e}"); None } } @@ -173,14 +173,14 @@ impl Store for PersistentDataStoreWrapper { item.into() } Err(e) => { - warn!("failed to convert serialized item into segment: {}", e); + warn!("failed to convert serialized item into segment: {e}"); None } } } Ok(None) => None, Err(e) => { - warn!("persistent store failed to retrieve segment: {}", e); + warn!("persistent store failed to retrieve segment: {e}"); None } } @@ -196,8 +196,7 @@ impl DataStore for PersistentDataStoreWrapper { match serialized_data { Err(e) => warn!( - "failed to deserialize payload; cannot initialize store {}", - e + "failed to deserialize payload; cannot initialize store {e}" ), Ok(data) => { let result = self.store.init(data); @@ -208,7 +207,7 @@ impl DataStore for PersistentDataStoreWrapper { self.cache_items(all_data.into()); } Err(e) => { - error!("failed to init store: {}", e); + error!("failed to init store: {e}"); if self.flags.cache_is_infinite() { debug!("updating non-expiring cache"); self.cache_items(all_data.into()) @@ -249,13 +248,13 @@ impl DataStore for PersistentDataStoreWrapper { HashMap::from_iter(flag_iter) } Err(e) => { - warn!("failed to convert serialized items into flags: {}", e); + warn!("failed to convert serialized items into flags: {e}"); HashMap::new() } } } Err(e) => { - warn!("persistent store failed to retrieve all flags: {}", e); + warn!("persistent store failed to retrieve all flags: {e}"); HashMap::new() } } @@ -267,7 +266,7 @@ impl DataStore for PersistentDataStoreWrapper { PatchTarget::Segment(item) => self.upsert_segment(key, item), PatchTarget::Other(v) => Err(UpdateError::InvalidTarget( "flag or segment".to_string(), - format!("{:?}", v), + format!("{v:?}"), )), } } diff --git a/launchdarkly-server-sdk/src/stores/store.rs b/launchdarkly-server-sdk/src/stores/store.rs index 28d87c2..a4e4d4b 100644 --- a/launchdarkly-server-sdk/src/stores/store.rs +++ b/launchdarkly-server-sdk/src/stores/store.rs @@ -106,7 +106,7 @@ impl DataStore for InMemoryDataStore { } PatchTarget::Other(v) => Err(UpdateError::InvalidTarget( "flag or segment".to_string(), - format!("{:?}", v), + format!("{v:?}"), )), } } From 05e53e9080d700c602bd85ab32ae978508d98888 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Tue, 20 Jan 2026 10:16:25 -0500 Subject: [PATCH 2/4] favor native roots over webpki --- launchdarkly-server-sdk/src/data_source_builders.rs | 12 ++++++++++-- .../src/events/processor_builders.rs | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/launchdarkly-server-sdk/src/data_source_builders.rs b/launchdarkly-server-sdk/src/data_source_builders.rs index 02e105b..db2f258 100644 --- a/launchdarkly-server-sdk/src/data_source_builders.rs +++ b/launchdarkly-server-sdk/src/data_source_builders.rs @@ -243,7 +243,11 @@ impl PollingDataSourceBuilder { impl DataSourceFactory for PollingDataSourceBuilder where C: tower::Service + Clone + Send + Sync + 'static, - C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, + C::Response: hyper_util::client::legacy::connect::Connection + + hyper::rt::Read + + hyper::rt::Write + + Send + + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { @@ -258,7 +262,11 @@ where #[cfg(feature = "rustls")] None => { let connector = HttpsConnectorBuilder::new() - .with_webpki_roots() + .with_native_roots() + .unwrap_or_else(|_| { + log::debug!("Falling back to webpki roots for polling HTTPS connector"); + HttpsConnectorBuilder::new().with_webpki_roots() + }) .https_or_http() .enable_http1() .enable_http2() diff --git a/launchdarkly-server-sdk/src/events/processor_builders.rs b/launchdarkly-server-sdk/src/events/processor_builders.rs index 9bf40f7..d7ea7be 100644 --- a/launchdarkly-server-sdk/src/events/processor_builders.rs +++ b/launchdarkly-server-sdk/src/events/processor_builders.rs @@ -88,7 +88,11 @@ pub struct EventProcessorBuilder { impl EventProcessorFactory for EventProcessorBuilder where C: tower::Service + Clone + Send + Sync + 'static, - C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, + C::Response: hyper_util::client::legacy::connect::Connection + + hyper::rt::Read + + hyper::rt::Write + + Send + + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { @@ -122,7 +126,11 @@ where #[cfg(feature = "rustls")] { let connector = HttpsConnectorBuilder::new() - .with_webpki_roots() + .with_native_roots() + .unwrap_or_else(|_| { + log::debug!("Falling back to webpki roots for event HTTPS connector"); + HttpsConnectorBuilder::new().with_webpki_roots() + }) .https_or_http() .enable_http1() .enable_http2() From afd9394c132fdf87ae7be426f64600824c637306 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Tue, 20 Jan 2026 10:18:44 -0500 Subject: [PATCH 3/4] cargo fmt --- launchdarkly-server-sdk/src/client.rs | 24 ++++--------- launchdarkly-server-sdk/src/config.rs | 4 ++- launchdarkly-server-sdk/src/events/sender.rs | 35 +++++++++++++------ .../src/feature_requester.rs | 35 +++++++++++++------ .../src/feature_requester_builders.rs | 20 +++++++++-- .../src/stores/persistent_store_wrapper.rs | 4 +-- 6 files changed, 75 insertions(+), 47 deletions(-) diff --git a/launchdarkly-server-sdk/src/client.rs b/launchdarkly-server-sdk/src/client.rs index 9d2c8b3..a157c65 100644 --- a/launchdarkly-server-sdk/src/client.rs +++ b/launchdarkly-server-sdk/src/client.rs @@ -376,9 +376,7 @@ impl Client { if let Some(b) = val.as_bool() { b } else { - warn!( - "bool_variation called for a non-bool flag {flag_key:?} (got {val:?})" - ); + warn!("bool_variation called for a non-bool flag {flag_key:?} (got {val:?})"); default } } @@ -395,9 +393,7 @@ impl Client { if let Some(s) = val.as_string() { s } else { - warn!( - "str_variation called for a non-string flag {flag_key:?} (got {val:?})" - ); + warn!("str_variation called for a non-string flag {flag_key:?} (got {val:?})"); default } } @@ -414,9 +410,7 @@ impl Client { if let Some(f) = val.as_float() { f } else { - warn!( - "float_variation called for a non-float flag {flag_key:?} (got {val:?})" - ); + warn!("float_variation called for a non-float flag {flag_key:?} (got {val:?})"); default } } @@ -433,9 +427,7 @@ impl Client { if let Some(f) = val.as_int() { f } else { - warn!( - "int_variation called for a non-int flag {flag_key:?} (got {val:?})" - ); + warn!("int_variation called for a non-int flag {flag_key:?} (got {val:?})"); default } } @@ -753,14 +745,10 @@ impl Client { self.events_default.event_factory.new_migration_op(event), ); } - Err(e) => error!( - "Failed to build migration event, no event will be sent: {e}" - ), + Err(e) => error!("Failed to build migration event, no event will be sent: {e}"), } } - Err(e) => error!( - "Failed to lock migration tracker, no event will be sent: {e}" - ), + Err(e) => error!("Failed to lock migration tracker, no event will be sent: {e}"), } } diff --git a/launchdarkly-server-sdk/src/config.rs b/launchdarkly-server-sdk/src/config.rs index b4d21ad..846fb98 100644 --- a/launchdarkly-server-sdk/src/config.rs +++ b/launchdarkly-server-sdk/src/config.rs @@ -322,7 +322,9 @@ impl ConfigBuilder { Some(builder) => Ok(builder), #[cfg(feature = "rustls")] None => Ok(Box::new(EventProcessorBuilder::< - hyper_rustls::HttpsConnector, + hyper_rustls::HttpsConnector< + hyper_util::client::legacy::connect::HttpConnector, + >, >::new())), #[cfg(not(feature = "rustls"))] None => Err(BuildError::InvalidConfig( diff --git a/launchdarkly-server-sdk/src/events/sender.rs b/launchdarkly-server-sdk/src/events/sender.rs index 778442c..4a4fbd8 100644 --- a/launchdarkly-server-sdk/src/events/sender.rs +++ b/launchdarkly-server-sdk/src/events/sender.rs @@ -40,7 +40,10 @@ pub trait EventSender: Send + Sync { pub struct HyperEventSender { url: http::Uri, sdk_key: String, - http: HyperClient>>, + http: HyperClient< + C, + http_body_util::combinators::BoxBody>, + >, default_headers: HashMap<&'static str, String>, // used with event-compression feature @@ -51,7 +54,11 @@ pub struct HyperEventSender { impl HyperEventSender where C: tower::Service + Clone + Send + Sync + 'static, - C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, + C::Response: hyper_util::client::legacy::connect::Connection + + hyper::rt::Read + + hyper::rt::Write + + Send + + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { @@ -90,7 +97,11 @@ where impl EventSender for HyperEventSender where C: tower::Service + Clone + Send + Sync + 'static, - C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, + C::Response: hyper_util::client::legacy::connect::Connection + + hyper::rt::Read + + hyper::rt::Write + + Send + + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { @@ -113,9 +124,7 @@ where let mut payload = match serde_json::to_vec(&events) { Ok(json) => json, Err(e) => { - error!( - "Failed to serialize event payload. Some events were dropped: {e:?}" - ); + error!("Failed to serialize event payload. Some events were dropped: {e:?}"); return; } }; @@ -159,10 +168,12 @@ where // Convert Vec to BoxBody for hyper 1.0 let body_bytes = Bytes::from(payload.clone()); - let boxed_body: http_body_util::combinators::BoxBody> = - Full::new(body_bytes) - .map_err(|e| Box::new(e) as Box) - .boxed(); + let boxed_body: http_body_util::combinators::BoxBody< + Bytes, + Box, + > = Full::new(body_bytes) + .map_err(|e| Box::new(e) as Box) + .boxed(); let request = request_builder.body(boxed_body); let result = self.http.request(request.unwrap()).await; @@ -360,7 +371,9 @@ mod tests { assert_eq!(sender_result.time_from_server, 1234567890000); } - fn build_event_sender(url: String) -> HyperEventSender { + fn build_event_sender( + url: String, + ) -> HyperEventSender { let url = format!("{}/bulk", &url); let url = http::Uri::from_str(&url).expect("Failed parsing the mock server url"); diff --git a/launchdarkly-server-sdk/src/feature_requester.rs b/launchdarkly-server-sdk/src/feature_requester.rs index 6d3c560..a02acd7 100644 --- a/launchdarkly-server-sdk/src/feature_requester.rs +++ b/launchdarkly-server-sdk/src/feature_requester.rs @@ -23,7 +23,12 @@ pub trait FeatureRequester: Send { } pub struct HyperFeatureRequester { - http: Arc>>>, + http: Arc< + HyperClient< + C, + http_body_util::combinators::BoxBody>, + >, + >, url: http::Uri, sdk_key: String, cache: Option, @@ -32,7 +37,10 @@ pub struct HyperFeatureRequester { impl HyperFeatureRequester { pub fn new( - http: HyperClient>>, + http: HyperClient< + C, + http_body_util::combinators::BoxBody>, + >, url: http::Uri, sdk_key: String, default_headers: HashMap<&'static str, String>, @@ -76,10 +84,12 @@ where } // Create empty body for GET request - let empty_body: http_body_util::combinators::BoxBody> = - Empty::::new() - .map_err(|e| Box::new(e) as Box) - .boxed(); + let empty_body: http_body_util::combinators::BoxBody< + Bytes, + Box, + > = Empty::::new() + .map_err(|e| Box::new(e) as Box) + .boxed(); let result = http .request(request_builder.body(empty_body).unwrap()) @@ -109,11 +119,12 @@ where .map_or_else(|_| "".into(), |s| s.into()); if response.status().is_success() { - let body_bytes = response.into_body().collect().await + let body_bytes = response + .into_body() + .collect() + .await .map_err(|e| { - error!( - "An error occurred while reading the polling response body: {e}" - ); + error!("An error occurred while reading the polling response body: {e}"); FeatureRequesterError::Temporary })? .to_bytes(); @@ -253,7 +264,9 @@ mod tests { } } - fn build_feature_requester(url: String) -> HyperFeatureRequester { + fn build_feature_requester( + url: String, + ) -> HyperFeatureRequester { use hyper_util::rt::TokioExecutor; let connector = hyper_util::client::legacy::connect::HttpConnector::new(); let http = HyperClient::builder(TokioExecutor::new()).build(connector); diff --git a/launchdarkly-server-sdk/src/feature_requester_builders.rs b/launchdarkly-server-sdk/src/feature_requester_builders.rs index f009393..35e86bc 100644 --- a/launchdarkly-server-sdk/src/feature_requester_builders.rs +++ b/launchdarkly-server-sdk/src/feature_requester_builders.rs @@ -27,13 +27,23 @@ pub trait FeatureRequesterFactory: Send { pub struct HyperFeatureRequesterBuilder { url: String, sdk_key: String, - http: HyperClient>>, + http: HyperClient< + C, + http_body_util::combinators::BoxBody< + bytes::Bytes, + Box, + >, + >, } impl HyperFeatureRequesterBuilder where C: tower::Service + Clone + Send + Sync + 'static, - C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, + C::Response: hyper_util::client::legacy::connect::Connection + + hyper::rt::Read + + hyper::rt::Write + + Send + + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { @@ -49,7 +59,11 @@ where impl FeatureRequesterFactory for HyperFeatureRequesterBuilder where C: tower::Service + Clone + Send + Sync + 'static, - C::Response: hyper_util::client::legacy::connect::Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin, + C::Response: hyper_util::client::legacy::connect::Connection + + hyper::rt::Read + + hyper::rt::Write + + Send + + Unpin, C::Future: Send + Unpin + 'static, C::Error: Into>, { diff --git a/launchdarkly-server-sdk/src/stores/persistent_store_wrapper.rs b/launchdarkly-server-sdk/src/stores/persistent_store_wrapper.rs index f54feea..8d304a5 100644 --- a/launchdarkly-server-sdk/src/stores/persistent_store_wrapper.rs +++ b/launchdarkly-server-sdk/src/stores/persistent_store_wrapper.rs @@ -195,9 +195,7 @@ impl DataStore for PersistentDataStoreWrapper { let serialized_data = AllData::::try_from(all_data.clone()); match serialized_data { - Err(e) => warn!( - "failed to deserialize payload; cannot initialize store {e}" - ), + Err(e) => warn!("failed to deserialize payload; cannot initialize store {e}"), Ok(data) => { let result = self.store.init(data); From f4b864b252013d4d0c5c6470497002d05fe05f05 Mon Sep 17 00:00:00 2001 From: Matthew Keeler Date: Tue, 20 Jan 2026 10:23:00 -0500 Subject: [PATCH 4/4] clippy --- launchdarkly-server-sdk/src/feature_requester.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/launchdarkly-server-sdk/src/feature_requester.rs b/launchdarkly-server-sdk/src/feature_requester.rs index a02acd7..41bb197 100644 --- a/launchdarkly-server-sdk/src/feature_requester.rs +++ b/launchdarkly-server-sdk/src/feature_requester.rs @@ -22,13 +22,11 @@ pub trait FeatureRequester: Send { fn get_all(&mut self) -> BoxFuture, FeatureRequesterError>>; } +type BoxedBody = + http_body_util::combinators::BoxBody>; + pub struct HyperFeatureRequester { - http: Arc< - HyperClient< - C, - http_body_util::combinators::BoxBody>, - >, - >, + http: Arc>, url: http::Uri, sdk_key: String, cache: Option,