diff --git a/.github/actions/build-docs/action.yml b/.github/actions/build-docs/action.yml deleted file mode 100644 index 480322e..0000000 --- a/.github/actions/build-docs/action.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Build Documentation -description: 'Build Documentation.' - -runs: - using: composite - steps: - - name: Build Documentation - shell: bash - run: cargo doc --no-deps --all-features -p launchdarkly-server-sdk diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index 3040232..b8db4ef 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -1,11 +1,15 @@ name: CI Workflow -description: 'Shared CI workflow.' +description: "Shared CI workflow." inputs: - feature-flags: - description: 'Cargo feature flags to pass to test and clippy commands' + cargo-flags: + description: "Flags to pass to cargo commands." required: false - default: '' + default: "" + cargo-test-flags: + description: "Flags to pass to cargo test commands." + required: false + default: "" runs: using: composite @@ -16,8 +20,8 @@ runs: - name: Run tests shell: bash - run: cargo test ${{ inputs.feature-flags }} -p launchdarkly-server-sdk + run: cargo test ${{ inputs.cargo-flags }} ${{ inputs.cargo-test-flags }} -p launchdarkly-server-sdk - name: Run clippy checks shell: bash - run: cargo clippy ${{ inputs.feature-flags }} -p launchdarkly-server-sdk -- -D warnings + run: cargo clippy ${{ inputs.cargo-flags }} -p launchdarkly-server-sdk -- -D warnings diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index ccb165e..4222eb4 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -1,11 +1,11 @@ name: Contract test runner -description: 'Reusable contract runner action' +description: "Reusable contract runner action" inputs: - tls_feature: - description: 'Which TLS feature do you want to enable?' + cargo-flags: + description: "Flags to pass to cargo commands." required: true token: - description: 'GH Token used for retrieving SDK test harness.' + description: "GH Token used for retrieving SDK test harness." required: true runs: @@ -13,11 +13,11 @@ runs: steps: - name: Build contract tests shell: bash - run: TLS_FEATURE="${{ inputs.tls_feature }}" make build-contract-tests + run: CARGO_FLAGS="${{ inputs.cargo-flags }}" make build-contract-tests - name: Start contract test service shell: bash - run: TLS_FEATURE="${{ inputs.tls_feature }}" make start-contract-test-service-bg + run: CARGO_FLAGS="${{ inputs.cargo-flags }}" make start-contract-test-service-bg - uses: launchdarkly/gh-actions/actions/contract-tests@contract-tests-v1.0.2 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 466d017..e6be473 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,41 +17,31 @@ jobs: matrix: features: - name: "default" - flags: "" + - name: "no-features" - flags: "--no-default-features" - - name: "hyper" - flags: "--no-default-features --features hyper" - - name: "hyper-rustls" - flags: "--no-default-features --features hyper-rustls" + cargo-flags: "--no-default-features" + cargo-test-flags: "--lib" + skip_contract_tests: "true" - name: CI (${{ matrix.features.name }}) + - name: "hyper" + cargo-flags: "--no-default-features --features hyper" + cargo-test-flags: "--lib" - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # If you only need the current version keep this. + - name: "hyper-rustls-native-roots" + cargo-flags: "--no-default-features --features hyper-rustls-native-roots" - - name: Get Rust version - id: rust-version - run: cat ./.github/variables/rust-versions.env >> $GITHUB_OUTPUT + - name: "hyper-rustls-webpki-roots" + cargo-flags: "--no-default-features --features hyper-rustls-webpki-roots" - - name: Setup rust tooling - run: | - rustup override set ${{ steps.rust-version.outputs.target }} - rustup component add rustfmt clippy + - name: "native-tls" + cargo-flags: "--no-default-features --features native-tls" - - uses: ./.github/actions/ci - with: - feature-flags: ${{ matrix.features.flags }} - - contract-tests: - runs-on: ubuntu-latest + name: CI (${{ matrix.features.name }}) steps: - uses: actions/checkout@v4 with: - fetch-depth: 0 + fetch-depth: 0 # If you only need the current version keep this. - name: Get Rust version id: rust-version @@ -62,16 +52,15 @@ jobs: rustup override set ${{ steps.rust-version.outputs.target }} rustup component add rustfmt clippy - - name: "Run contract tests with hyper_rustls" - uses: ./.github/actions/contract-tests + - uses: ./.github/actions/ci with: - tls_feature: "hyper-rustls" - token: ${{ secrets.GITHUB_TOKEN }} + cargo-flags: ${{ matrix.features.cargo-flags }} + cargo-test-flags: ${{ matrix.features.cargo-test-flags }} - - name: "Run contract tests with hyper_tls" - uses: ./.github/actions/contract-tests + - uses: ./.github/actions/contract-tests + if: ${{ matrix.features.skip_contract_tests != 'true' }} with: - tls_feature: "tls" + cargo-flags: ${{ matrix.features.cargo-flags }} token: ${{ secrets.GITHUB_TOKEN }} build-docs: @@ -83,15 +72,14 @@ jobs: with: fetch-depth: 0 - - name: Get Rust version - id: rust-version - run: cat ./.github/variables/rust-versions.env >> $GITHUB_OUTPUT - - name: Setup rust tooling - run: | - rustup override set ${{ steps.rust-version.outputs.target }} + run: rustup override set nightly + + - name: Install cargo-docs-rs + run: cargo install cargo-docs-rs - - uses: ./.github/actions/build-docs + - name: Build documentation + run: cargo docs-rs -p launchdarkly-server-sdk musl-build: runs-on: ubuntu-latest diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index b9d1e0b..76c5f30 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -27,7 +27,6 @@ jobs: rustup component add rustfmt clippy - uses: ./.github/actions/ci - - uses: ./.github/actions/build-docs - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: "Get crates.io token" diff --git a/Makefile b/Makefile index 307093a..4f069d0 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,15 @@ TEMP_TEST_OUTPUT=/tmp/contract-test-service.log -TLS_FEATURE ?= hyper-rustls +CARGO_FLAGS ?= hyper-rustls-native-roots build-contract-tests: - cargo build -p contract-tests --release --no-default-features --features "$(TLS_FEATURE)" + cargo build -p contract-tests --release $(CARGO_FLAGS) start-contract-test-service: build-contract-tests @./target/release/contract-tests start-contract-test-service-bg: @echo "Test service output will be captured in $(TEMP_TEST_OUTPUT)" - @make start-contract-test-service >$(TEMP_TEST_OUTPUT) 2>&1 & + @$(MAKE) start-contract-test-service >$(TEMP_TEST_OUTPUT) 2>&1 & run-contract-tests: @curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/main/downloader/run.sh \ diff --git a/contract-tests/Cargo.toml b/contract-tests/Cargo.toml index 1f48685..e7dec85 100644 --- a/contract-tests/Cargo.toml +++ b/contract-tests/Cargo.toml @@ -17,12 +17,33 @@ serde = { version = "1.0.132", features = ["derive"] } serde_json = "1.0.73" futures = "0.3.12" 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-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "ring"], 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 = ["hyper-rustls"] -hyper-rustls = ["dep:hyper-rustls", "launchdarkly-server-sdk/hyper-rustls"] -tls = ["hyper-tls", "launchdarkly-server-sdk/hyper"] +default = ["hyper"] + +hyper = [ + "launchdarkly-sdk-transport/hyper", + "eventsource-client/hyper" +] +hyper-rustls-native-roots = [ + "hyper", + "dep:hyper-rustls", + "launchdarkly-sdk-transport/hyper-rustls-native-roots", + "eventsource-client/hyper-rustls-native-roots" +] +hyper-rustls-webpki-roots = [ + "hyper", + "dep:hyper-rustls", + "launchdarkly-sdk-transport/hyper-rustls-webpki-roots", + "eventsource-client/hyper-rustls-webpki-roots" +] +native-tls = [ + "hyper", + "dep:hyper-tls", + "launchdarkly-sdk-transport/native-tls", + "eventsource-client/native-tls" +] diff --git a/contract-tests/src/main.rs b/contract-tests/src/main.rs index 85bc036..2d00e03 100644 --- a/contract-tests/src/main.rs +++ b/contract-tests/src/main.rs @@ -207,31 +207,31 @@ struct AppState { https_connector: HttpsConnector, } -#[cfg(feature = "hyper-rustls")] +#[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots" +))] type HttpsConnector = hyper_rustls::HttpsConnector; -#[cfg(feature = "tls")] +#[cfg(feature = "native-tls")] type HttpsConnector = hyper_tls::HttpsConnector; +#[cfg(not(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" +)))] +type HttpsConnector = hyper_util::client::legacy::connect::HttpConnector; + #[actix_web::main] + async fn main() -> std::io::Result<()> { env_logger::init(); - #[cfg(not(any(feature = "tls", feature = "hyper-rustls")))] - { - compile_error!("one of the { \"tls\", \"hyper-rustls\" } features must be enabled"); - } - #[cfg(all(feature = "tls", feature = "hyper-rustls"))] - { - compile_error!( - "only one of the { \"tls\", \"hyper-rustls\" } features can be enabled at a time" - ); - } - let (tx, rx) = mpsc::channel::<()>(); - #[cfg(feature = "hyper-rustls")] + #[cfg(feature = "hyper-rustls-native-roots")] let https_connector = hyper_rustls::HttpsConnectorBuilder::new() .with_native_roots() .expect("Failed to load native root certificates") @@ -240,9 +240,24 @@ async fn main() -> std::io::Result<()> { .enable_http2() .build(); - #[cfg(feature = "tls")] + #[cfg(feature = "hyper-rustls-webpki-roots")] + let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + .with_webpki_roots() + .https_or_http() + .enable_http1() + .enable_http2() + .build(); + + #[cfg(feature = "native-tls")] let https_connector = hyper_tls::HttpsConnector::new(); + #[cfg(not(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + )))] + let https_connector = hyper_util::client::legacy::connect::HttpConnector::new(); + let state = web::Data::new(AppState { counter: Mutex::new(0), client_entities: Mutex::new(HashMap::new()), diff --git a/launchdarkly-server-sdk/Cargo.toml b/launchdarkly-server-sdk/Cargo.toml index 7f5b16d..89e8996 100644 --- a/launchdarkly-server-sdk/Cargo.toml +++ b/launchdarkly-server-sdk/Cargo.toml @@ -54,15 +54,34 @@ reqwest = { version = "0.12.4", features = ["json"] } testing_logger = "0.1.1" [features] -default = ["hyper-rustls"] -hyper = ["launchdarkly-sdk-transport/hyper", "eventsource-client/hyper"] -hyper-rustls = ["hyper", "launchdarkly-sdk-transport/hyper-rustls", "eventsource-client/hyper-rustls"] +default = ["hyper-rustls-native-roots"] + +hyper = [ + "launchdarkly-sdk-transport/hyper", + "eventsource-client/hyper" +] +hyper-rustls-native-roots = [ + "hyper", + "launchdarkly-sdk-transport/hyper-rustls-native-roots", + "eventsource-client/hyper-rustls-native-roots" +] +hyper-rustls-webpki-roots = [ + "hyper", + "launchdarkly-sdk-transport/hyper-rustls-webpki-roots", + "eventsource-client/hyper-rustls-webpki-roots" +] +native-tls = [ + "hyper", + "launchdarkly-sdk-transport/native-tls", + "eventsource-client/native-tls" +] + event-compression = ["flate2"] [[example]] name = "print_flags" -required-features = ["hyper-rustls"] +required-features = ["hyper-rustls-native-roots"] [[example]] name = "progress" -required-features = ["hyper-rustls"] +required-features = ["hyper-rustls-native-roots"] diff --git a/launchdarkly-server-sdk/src/config.rs b/launchdarkly-server-sdk/src/config.rs index 07bd8a7..5f5b6f3 100644 --- a/launchdarkly-server-sdk/src/config.rs +++ b/launchdarkly-server-sdk/src/config.rs @@ -1,11 +1,23 @@ use thiserror::Error; use crate::data_source_builders::{DataSourceFactory, NullDataSourceBuilder}; -use crate::events::processor_builders::{ - EventProcessorBuilder, EventProcessorFactory, NullEventProcessorBuilder, -}; + +#[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" +))] +use crate::events::processor_builders::EventProcessorBuilder; +use crate::events::processor_builders::{EventProcessorFactory, NullEventProcessorBuilder}; + use crate::stores::store_builders::{DataStoreFactory, InMemoryDataStoreBuilder}; -use crate::{ServiceEndpointsBuilder, StreamingDataSourceBuilder}; +use crate::ServiceEndpointsBuilder; +#[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" +))] +use crate::StreamingDataSourceBuilder; use std::borrow::Borrow; @@ -300,7 +312,11 @@ impl ConfigBuilder { Ok(Box::new(NullDataSourceBuilder::new())) } Some(builder) => Ok(builder), - #[cfg(feature = "hyper-rustls")] + #[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + ))] None => { let transport = launchdarkly_sdk_transport::HyperTransport::new_https() .map_err(|e| { @@ -313,9 +329,13 @@ impl ConfigBuilder { builder.transport(transport); Ok(Box::new(builder)) } - #[cfg(not(feature = "hyper-rustls"))] + #[cfg(not(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + )))] None => Err(BuildError::InvalidConfig( - "data source builder required when hyper-rustls feature is disabled".into(), + "data source builder required when hyper-rustls-native-roots, hyper-rustls-webpki-roots, or native-tls features are disabled".into(), )), }; let data_source_builder = data_source_builder_result?; @@ -328,7 +348,11 @@ impl ConfigBuilder { Ok(Box::new(NullEventProcessorBuilder::new())) } Some(builder) => Ok(builder), - #[cfg(feature = "hyper-rustls")] + #[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + ))] None => { let transport = launchdarkly_sdk_transport::HyperTransport::new_https() .map_err(|e| { @@ -341,9 +365,13 @@ impl ConfigBuilder { builder.transport(transport); Ok(Box::new(builder)) } - #[cfg(not(feature = "hyper-rustls"))] + #[cfg(not(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + )))] None => Err(BuildError::InvalidConfig( - "event processor factory required when hyper-rustls feature is disabled".into(), + "event processor factory required when hyper-rustls-native-roots, hyper-rustls-webpki-roots, or native-tls features are disabled".into(), )), }; let event_processor_builder = event_processor_builder_result?; @@ -391,6 +419,11 @@ mod tests { } #[test] + #[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + ))] fn unconfigured_config_builder_handles_application_tags_correctly() { let builder = ConfigBuilder::new("sdk-key"); let config = builder.build().expect("config should build"); @@ -402,6 +435,11 @@ mod tests { #[test_case("Invalid id", "version", Some("application-version/version".to_string()))] #[test_case("id", "Invalid version", Some("application-id/id".to_string()))] #[test_case("Invalid id", "Invalid version", None)] + #[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + ))] fn config_builder_handles_application_tags_appropriately( id: impl Into, version: impl Into, diff --git a/launchdarkly-server-sdk/src/data_source_builders.rs b/launchdarkly-server-sdk/src/data_source_builders.rs index b060111..d0a56e3 100644 --- a/launchdarkly-server-sdk/src/data_source_builders.rs +++ b/launchdarkly-server-sdk/src/data_source_builders.rs @@ -92,7 +92,11 @@ impl DataSourceFactory tags: Option, ) -> Result, BuildError> { let data_source_result = match &self.transport { - #[cfg(feature = "hyper-rustls")] + #[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + ))] None => { let transport = launchdarkly_sdk_transport::HyperTransport::new_https().map_err(|e| { @@ -108,9 +112,13 @@ impl DataSourceFactory transport, )) } - #[cfg(not(feature = "hyper-rustls"))] + #[cfg(not(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + )))] None => Err(BuildError::InvalidConfig( - "https connector required when rustls is disabled".into(), + "https connector required when hyper-rustls-native-roots, hyper-rustls-webpki-roots, or native-tls features are disabled".into(), )), Some(transport) => Ok(StreamingDataSource::new( endpoints.streaming_base_url(), @@ -254,7 +262,11 @@ impl DataSourceFactory for PollingDataSourceBuilder { ) -> Result, BuildError> { let feature_requester_builder: Result, BuildError> = match &self.transport { - #[cfg(feature = "hyper-rustls")] + #[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + ))] None => { let transport = launchdarkly_sdk_transport::HyperTransport::new_https() .map_err(|e| { @@ -269,9 +281,13 @@ impl DataSourceFactory for PollingDataSourceBuilder { transport, ))) } - #[cfg(not(feature = "hyper-rustls"))] + #[cfg(not(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + )))] None => Err(BuildError::InvalidConfig( - "transport is required when hyper-rustls feature is disabled".into(), + "transport is required when hyper-rustls-native-roots, hyper-rustls-webpki-roots, or native-tls features are disabled".into(), )), Some(transport) => Ok(Box::new(HttpFeatureRequesterBuilder::new( endpoints.polling_base_url(), diff --git a/launchdarkly-server-sdk/src/events/processor_builders.rs b/launchdarkly-server-sdk/src/events/processor_builders.rs index 1d4ebf9..714be7e 100644 --- a/launchdarkly-server-sdk/src/events/processor_builders.rs +++ b/launchdarkly-server-sdk/src/events/processor_builders.rs @@ -111,7 +111,11 @@ impl EventProcessorFactory for EventProcessorBuilder { self.compress_events, ))) } else { - #[cfg(feature = "hyper-rustls")] + #[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + ))] { let transport = launchdarkly_sdk_transport::HyperTransport::new_https().map_err(|e| { BuildError::InvalidConfig(format!( @@ -127,9 +131,13 @@ impl EventProcessorFactory for EventProcessorBuilder { self.compress_events, ))) } - #[cfg(not(feature = "hyper-rustls"))] + #[cfg(not(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + )))] Err(BuildError::InvalidConfig( - "transport is required when hyper-rustls feature is disabled".into(), + "transport is required when hyper-rustls-native-roots, hyper-rustls-webpki-roots, or native-tls features are disabled".into(), )) }; let event_sender = event_sender_result?; @@ -386,6 +394,11 @@ mod tests { #[test_case(Some("application-id/abc:application-sha/xyz".into()), "application-id/abc:application-sha/xyz")] #[test_case(None, Matcher::Missing)] + #[cfg(any( + feature = "hyper-rustls-native-roots", + feature = "hyper-rustls-webpki-roots", + feature = "native-tls" + ))] fn processor_sends_correct_headers(tag: Option, matcher: impl Into) { let mut server = mockito::Server::new(); let mock = server