diff --git a/.github/workflows/samples-rust-server.yaml b/.github/workflows/samples-rust-server.yaml index 2c6eb21cade5..cc0574614a3c 100644 --- a/.github/workflows/samples-rust-server.yaml +++ b/.github/workflows/samples-rust-server.yaml @@ -64,6 +64,15 @@ jobs: if cargo read-manifest | grep -q '"validate"'; then cargo build --features validate --all-targets fi + # Test TLS features if they exist + if cargo read-manifest | grep -q '"client-tls"'; then + # Client without TLS (HTTP-only) + cargo build --no-default-features --features=client --lib + # Client with TLS (using native-tls) + cargo build --no-default-features --features=client,client-tls --lib + # Server without TLS + cargo build --no-default-features --features=server --lib + fi cargo fmt cargo test cargo clippy diff --git a/modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache b/modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache index fe169216841c..519c9dcfe393 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache @@ -47,7 +47,19 @@ client = [ "serde_ignored", "percent-encoding", {{^apiUsesByteArray}}"lazy_static", "regex",{{/apiUsesByteArray}} {{/hasCallbacks}} {{! Anything added to the list below, should probably be added to the callbacks list below }} - "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url" + "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "url" +] +# TLS support - automatically selects backend based on target OS: +# - macOS/Windows/iOS: native-tls via hyper-tls +# - Other platforms: OpenSSL via hyper-openssl +# Dependencies are in target-specific sections below +client-tls = [ + "client", + "dep:native-tls", + "dep:hyper-tls", + "dep:openssl", + "dep:hyper-openssl", + "swagger/tls" ] server = [ {{#apiUsesMultipartFormData}} @@ -57,7 +69,6 @@ server = [ "mime_multipart", "swagger/multipart_related", {{/apiUsesMultipartRelated}} {{#hasCallbacks}} - "native-tls", "hyper-openssl", "hyper-tls", "openssl", {{/hasCallbacks}} {{! Anything added to the list below, should probably be added to the callbacks list above }} "serde_ignored", "hyper", "percent-encoding", "url", @@ -74,20 +85,12 @@ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk- mock = ["mockall"] validate = [{{^apiUsesByteArray}}"regex",{{/apiUsesByteArray}} "serde_valid", "swagger/serdevalid"] -[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] -native-tls = { version = "0.2", optional = true } -hyper-tls = { version = "0.6", optional = true } - -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies] -hyper-openssl = { version = "0.10", optional = true } -openssl = { version = "0.10", optional = true } - [dependencies] # Common async-trait = "0.1.88" chrono = { version = "0.4", features = ["serde"] } futures = "0.3" -swagger = { version = "7.0.0", features = ["serdejson", "server", "client", "tls"] } +swagger = { version = "7.0.0", features = ["serdejson", "server", "client"] } headers = "0.4.0" log = "0.4.27" @@ -133,6 +136,12 @@ serde_urlencoded = { version = "0.7.1", optional = true } {{/usesUrlEncodedForm}} tower-service = "0.3.3" +# TLS support - all listed here, actual usage determined by cfg attributes in code +native-tls = { version = "0.2", optional = true } +hyper-tls = { version = "0.6", optional = true } +openssl = { version = "0.10", optional = true } +hyper-openssl = { version = "0.10", optional = true } + # Server, and client callback-specific {{^apiUsesByteArray}} lazy_static = { version = "1.5", optional = true } @@ -163,15 +172,13 @@ clap = "4.5" env_logger = "0.11" tokio = { version = "1.49", features = ["full"] } native-tls = "0.2" +openssl = "0.10" +tokio-openssl = "0.6" pin-project = "1.1.10" # Bearer authentication, used in examples jsonwebtoken = {version = "10.0.0", features = ["rust_crypto"]} -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies] -tokio-openssl = "0.6" -openssl = "0.10" - [[example]] name = "client" required-features = ["client"] diff --git a/modules/openapi-generator/src/main/resources/rust-server/README.mustache b/modules/openapi-generator/src/main/resources/rust-server/README.mustache index bee28c9363b6..19a8ba411e06 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/README.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/README.mustache @@ -126,6 +126,11 @@ The generated library has a few optional features that can be activated through * `client` * This defaults to enabled and creates the basic skeleton of a client implementation based on hyper * The constructed client implements the API trait by making remote API call. +* `client-tls` + * Optional feature that provides HTTPS support with automatic TLS backend selection: + - macOS/Windows/iOS: native-tls + hyper-tls + - Linux/Unix/others: OpenSSL + hyper-openssl + * Not enabled by default to minimize dependencies. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. * `cli` @@ -134,6 +139,27 @@ The generated library has a few optional features that can be activated through * This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`. * Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks. +### Enabling HTTPS/TLS Support + +By default, only HTTP support is included. To enable HTTPS, add the `client-tls` feature: + +```toml +[dependencies] +{{{packageName}}} = { version = "{{{packageVersion}}}", features = ["client-tls"] } +``` + +**For server with callbacks that need HTTPS:** +```toml +[dependencies] +{{{packageName}}} = { version = "{{{packageVersion}}}", features = ["server", "client-tls"] } +``` + +The TLS backend is automatically selected based on your target platform: +- **macOS, Windows, iOS**: Uses `native-tls` (system TLS libraries) +- **Linux, Unix, other platforms**: Uses `openssl` + +This ensures the best compatibility and native integration on each platform. + See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. ## Documentation for API Endpoints diff --git a/modules/openapi-generator/src/main/resources/rust-server/bin-cli.mustache b/modules/openapi-generator/src/main/resources/rust-server/bin-cli.mustache index cdb4bc65d9a1..994bcd4109e9 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/bin-cli.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/bin-cli.mustache @@ -45,17 +45,17 @@ struct Cli { server_address: String, /// Path to the client private key if using client-side TLS authentication - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_certificate", "server_certificate"]))] client_key: Option, /// Path to the client's public certificate associated with the private key - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_key", "server_certificate"]))] client_certificate: Option, /// Path to CA certificate used to authenticate the server - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long)] server_certificate: Option, @@ -130,7 +130,8 @@ enum Operation { {{/apiInfo}} } -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +// On Linux/Unix with OpenSSL (client-tls feature), support certificate pinning and mutual TLS +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] fn create_client(args: &Cli, context: ClientContext) -> Result>> { if args.client_certificate.is_some() { debug!("Using mutual TLS"); @@ -156,8 +157,15 @@ fn create_client(args: &Cli, context: ClientContext) -> Result Result>> { + // Client::try_new() automatically detects the URL scheme (http:// or https://) + // and creates the appropriate client. + // Note: Certificate pinning and mutual TLS are only available on Linux/Unix with OpenSSL let client = Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; Ok(Box::new(client.with_context(context))) diff --git a/modules/openapi-generator/src/main/resources/rust-server/client-mod.mustache b/modules/openapi-generator/src/main/resources/rust-server/client-mod.mustache index 9860a93c5865..38c9ca3695d1 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/client-mod.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/client-mod.mustache @@ -118,10 +118,17 @@ impl Client< } } +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] +type HyperHttpsConnector = hyper_tls::HttpsConnector; + +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] +type HyperHttpsConnector = hyper_openssl::client::legacy::HttpsConnector; + #[derive(Debug, Clone)] pub enum HyperClient { Http(hyper_util::client::legacy::Client>), - Https(hyper_util::client::legacy::Client>), + #[cfg(feature = "client-tls")] + Https(hyper_util::client::legacy::Client>), } impl Service>> for HyperClient { @@ -132,7 +139,8 @@ impl Service>> for HyperClient { fn call(&self, req: Request>) -> Self::Future { match self { HyperClient::Http(client) => client.request(req), - HyperClient::Https(client) => client.request(req) + #[cfg(feature = "client-tls")] + HyperClient::Https(client) => client.request(req), } } } @@ -158,11 +166,17 @@ impl Client, C> where "http" => { HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build())) }, + #[cfg(feature = "client-tls")] + "https" => { + let https_connector = connector + .https() + .build() + .map_err(ClientInitError::SslError)?; + HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector)) + }, + #[cfg(not(feature = "client-tls"))] "https" => { - let connector = connector.https() - .build() - .map_err(ClientInitError::SslError)?; - HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector)) + return Err(ClientInitError::TlsNotEnabled); }, _ => { return Err(ClientInitError::InvalidScheme); @@ -206,12 +220,13 @@ impl Client< } } -#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] type HttpsConnector = hyper_tls::HttpsConnector; -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector; +#[cfg(feature = "client-tls")] impl Client< DropContextService< hyper_util::service::TowerToHyperService< @@ -226,10 +241,25 @@ impl Client< > where C: Clone + Send + Sync + 'static { - /// Create a client with a TLS connection to the server + /// Create a client with a TLS connection to the server using native-tls. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + pub fn try_new_https(base_path: &str) -> Result + { + let https_connector = Connector::builder() + .https() + .build() + .map_err(ClientInitError::SslError)?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } + + /// Create a client with a TLS connection to the server using OpenSSL. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https(base_path: &str) -> Result { let https_connector = Connector::builder() @@ -242,7 +272,7 @@ impl Client< /// Create a client with a TLS connection to the server using a pinned certificate /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https_pinned( @@ -263,7 +293,7 @@ impl Client< /// Create a client with a mutually authenticated TLS connection to the server. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server /// * `client_key` - Path to the client private key /// * `client_certificate` - Path to the client's public certificate associated with the private key @@ -325,12 +355,15 @@ pub enum ClientInitError { /// Missing Hostname MissingHost, + /// HTTPS requested but TLS features not enabled + TlsNotEnabled, + /// SSL Connection Error - #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + #[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] SslError(native_tls::Error), /// SSL Connection Error - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] SslError(openssl::error::ErrorStack), } diff --git a/modules/openapi-generator/src/main/resources/rust-server/example-client-main.mustache b/modules/openapi-generator/src/main/resources/rust-server/example-client-main.mustache index ba2e7a3b510a..4c8c9753768b 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/example-client-main.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/example-client-main.mustache @@ -115,17 +115,33 @@ fn main() { let context: ClientContext = swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default()); - let mut client : Box> = if is_https { - // Using Simple HTTPS - let client = Box::new(Client::try_new_https(&base_url) - .expect("Failed to create HTTPS client")); - Box::new(client.with_context(context)) - } else { - // Using HTTP - let client = Box::new(Client::try_new_http( - &base_url) - .expect("Failed to create HTTP client")); - Box::new(client.with_context(context)) + let mut client : Box> = { + #[cfg(feature = "client-tls")] + { + if is_https { + // Using HTTPS with native-tls + let client = Box::new(Client::try_new_https(&base_url) + .expect("Failed to create HTTPS client")); + Box::new(client.with_context(context)) + } else { + // Using HTTP + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } + } + + #[cfg(not(feature = "client-tls"))] + { + if is_https { + panic!("HTTPS requested but TLS support not enabled. \ + Enable the 'client-tls' feature to use HTTPS."); + } + // Using HTTP only + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } }; let mut rt = tokio::runtime::Runtime::new().unwrap(); diff --git a/modules/openapi-generator/src/main/resources/rust-server/server-callbacks.mustache b/modules/openapi-generator/src/main/resources/rust-server/server-callbacks.mustache index 301380b0a705..bee6406d981c 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/server-callbacks.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/server-callbacks.mustache @@ -97,16 +97,17 @@ impl Client; -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector; +#[cfg(feature = "client-tls")] impl Client>>, C>, C> where C: Clone + Send + Sync + 'static { - /// Create a client with a TLS connection to the server. + /// Create a client with a TLS connection to the server using native-tls. #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] pub fn new_https() -> Result { @@ -114,7 +115,7 @@ impl Client Result { diff --git a/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml b/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml index 11dde870fb8f..4dc568ed1840 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml +++ b/samples/server/petstore/rust-server/output/multipart-v3/Cargo.toml @@ -12,7 +12,19 @@ default = ["client", "server"] client = [ "multipart", "multipart/client", "swagger/multipart_form", "mime_multipart", "swagger/multipart_related", - "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url" + "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "url" +] +# TLS support - automatically selects backend based on target OS: +# - macOS/Windows/iOS: native-tls via hyper-tls +# - Other platforms: OpenSSL via hyper-openssl +# Dependencies are in target-specific sections below +client-tls = [ + "client", + "dep:native-tls", + "dep:hyper-tls", + "dep:openssl", + "dep:hyper-openssl", + "swagger/tls" ] server = [ "multipart", "multipart/server", "swagger/multipart_form", @@ -28,20 +40,12 @@ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk- mock = ["mockall"] validate = [ "serde_valid", "swagger/serdevalid"] -[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] -native-tls = { version = "0.2", optional = true } -hyper-tls = { version = "0.6", optional = true } - -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies] -hyper-openssl = { version = "0.10", optional = true } -openssl = { version = "0.10", optional = true } - [dependencies] # Common async-trait = "0.1.88" chrono = { version = "0.4", features = ["serde"] } futures = "0.3" -swagger = { version = "7.0.0", features = ["serdejson", "server", "client", "tls"] } +swagger = { version = "7.0.0", features = ["serdejson", "server", "client"] } headers = "0.4.0" log = "0.4.27" @@ -72,6 +76,12 @@ url = { version = "2.5", optional = true } # Client-specific tower-service = "0.3.3" +# TLS support - all listed here, actual usage determined by cfg attributes in code +native-tls = { version = "0.2", optional = true } +hyper-tls = { version = "0.6", optional = true } +openssl = { version = "0.10", optional = true } +hyper-openssl = { version = "0.10", optional = true } + # Server, and client callback-specific percent-encoding = { version = "2.3.2", optional = true } @@ -95,15 +105,13 @@ clap = "4.5" env_logger = "0.11" tokio = { version = "1.49", features = ["full"] } native-tls = "0.2" +openssl = "0.10" +tokio-openssl = "0.6" pin-project = "1.1.10" # Bearer authentication, used in examples jsonwebtoken = {version = "10.0.0", features = ["rust_crypto"]} -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies] -tokio-openssl = "0.6" -openssl = "0.10" - [[example]] name = "client" required-features = ["client"] diff --git a/samples/server/petstore/rust-server/output/multipart-v3/README.md b/samples/server/petstore/rust-server/output/multipart-v3/README.md index e3938cd7ab55..766b5cd79b2d 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/README.md +++ b/samples/server/petstore/rust-server/output/multipart-v3/README.md @@ -111,6 +111,11 @@ The generated library has a few optional features that can be activated through * `client` * This defaults to enabled and creates the basic skeleton of a client implementation based on hyper * The constructed client implements the API trait by making remote API call. +* `client-tls` + * Optional feature that provides HTTPS support with automatic TLS backend selection: + - macOS/Windows/iOS: native-tls + hyper-tls + - Linux/Unix/others: OpenSSL + hyper-openssl + * Not enabled by default to minimize dependencies. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. * `cli` @@ -119,6 +124,27 @@ The generated library has a few optional features that can be activated through * This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`. * Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks. +### Enabling HTTPS/TLS Support + +By default, only HTTP support is included. To enable HTTPS, add the `client-tls` feature: + +```toml +[dependencies] +multipart-v3 = { version = "1.0.7", features = ["client-tls"] } +``` + +**For server with callbacks that need HTTPS:** +```toml +[dependencies] +multipart-v3 = { version = "1.0.7", features = ["server", "client-tls"] } +``` + +The TLS backend is automatically selected based on your target platform: +- **macOS, Windows, iOS**: Uses `native-tls` (system TLS libraries) +- **Linux, Unix, other platforms**: Uses `openssl` + +This ensures the best compatibility and native integration on each platform. + See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. ## Documentation for API Endpoints diff --git a/samples/server/petstore/rust-server/output/multipart-v3/bin/cli.rs b/samples/server/petstore/rust-server/output/multipart-v3/bin/cli.rs index ff6a1dfd926d..df88edb242f5 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/bin/cli.rs +++ b/samples/server/petstore/rust-server/output/multipart-v3/bin/cli.rs @@ -35,17 +35,17 @@ struct Cli { server_address: String, /// Path to the client private key if using client-side TLS authentication - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_certificate", "server_certificate"]))] client_key: Option, /// Path to the client's public certificate associated with the private key - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_key", "server_certificate"]))] client_certificate: Option, /// Path to CA certificate used to authenticate the server - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long)] server_certificate: Option, @@ -83,7 +83,8 @@ enum Operation { }, } -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +// On Linux/Unix with OpenSSL (client-tls feature), support certificate pinning and mutual TLS +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] fn create_client(args: &Cli, context: ClientContext) -> Result>> { if args.client_certificate.is_some() { debug!("Using mutual TLS"); @@ -109,8 +110,15 @@ fn create_client(args: &Cli, context: ClientContext) -> Result Result>> { + // Client::try_new() automatically detects the URL scheme (http:// or https://) + // and creates the appropriate client. + // Note: Certificate pinning and mutual TLS are only available on Linux/Unix with OpenSSL let client = Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; Ok(Box::new(client.with_context(context))) diff --git a/samples/server/petstore/rust-server/output/multipart-v3/examples/client/main.rs b/samples/server/petstore/rust-server/output/multipart-v3/examples/client/main.rs index bfec3bbe3bef..7aecfea8630f 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/examples/client/main.rs +++ b/samples/server/petstore/rust-server/output/multipart-v3/examples/client/main.rs @@ -89,17 +89,33 @@ fn main() { let context: ClientContext = swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default()); - let mut client : Box> = if is_https { - // Using Simple HTTPS - let client = Box::new(Client::try_new_https(&base_url) - .expect("Failed to create HTTPS client")); - Box::new(client.with_context(context)) - } else { - // Using HTTP - let client = Box::new(Client::try_new_http( - &base_url) - .expect("Failed to create HTTP client")); - Box::new(client.with_context(context)) + let mut client : Box> = { + #[cfg(feature = "client-tls")] + { + if is_https { + // Using HTTPS with native-tls + let client = Box::new(Client::try_new_https(&base_url) + .expect("Failed to create HTTPS client")); + Box::new(client.with_context(context)) + } else { + // Using HTTP + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } + } + + #[cfg(not(feature = "client-tls"))] + { + if is_https { + panic!("HTTPS requested but TLS support not enabled. \ + Enable the 'client-tls' feature to use HTTPS."); + } + // Using HTTP only + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } }; let mut rt = tokio::runtime::Runtime::new().unwrap(); diff --git a/samples/server/petstore/rust-server/output/multipart-v3/src/client/mod.rs b/samples/server/petstore/rust-server/output/multipart-v3/src/client/mod.rs index df74eae468a1..9d334d228e1e 100644 --- a/samples/server/petstore/rust-server/output/multipart-v3/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/multipart-v3/src/client/mod.rs @@ -160,10 +160,17 @@ impl Client< } } +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] +type HyperHttpsConnector = hyper_tls::HttpsConnector; + +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] +type HyperHttpsConnector = hyper_openssl::client::legacy::HttpsConnector; + #[derive(Debug, Clone)] pub enum HyperClient { Http(hyper_util::client::legacy::Client>), - Https(hyper_util::client::legacy::Client>), + #[cfg(feature = "client-tls")] + Https(hyper_util::client::legacy::Client>), } impl Service>> for HyperClient { @@ -174,7 +181,8 @@ impl Service>> for HyperClient { fn call(&self, req: Request>) -> Self::Future { match self { HyperClient::Http(client) => client.request(req), - HyperClient::Https(client) => client.request(req) + #[cfg(feature = "client-tls")] + HyperClient::Https(client) => client.request(req), } } } @@ -200,11 +208,17 @@ impl Client, C> where "http" => { HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build())) }, + #[cfg(feature = "client-tls")] + "https" => { + let https_connector = connector + .https() + .build() + .map_err(ClientInitError::SslError)?; + HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector)) + }, + #[cfg(not(feature = "client-tls"))] "https" => { - let connector = connector.https() - .build() - .map_err(ClientInitError::SslError)?; - HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector)) + return Err(ClientInitError::TlsNotEnabled); }, _ => { return Err(ClientInitError::InvalidScheme); @@ -248,12 +262,13 @@ impl Client< } } -#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] type HttpsConnector = hyper_tls::HttpsConnector; -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector; +#[cfg(feature = "client-tls")] impl Client< DropContextService< hyper_util::service::TowerToHyperService< @@ -268,10 +283,25 @@ impl Client< > where C: Clone + Send + Sync + 'static { - /// Create a client with a TLS connection to the server + /// Create a client with a TLS connection to the server using native-tls. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + pub fn try_new_https(base_path: &str) -> Result + { + let https_connector = Connector::builder() + .https() + .build() + .map_err(ClientInitError::SslError)?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } + + /// Create a client with a TLS connection to the server using OpenSSL. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https(base_path: &str) -> Result { let https_connector = Connector::builder() @@ -284,7 +314,7 @@ impl Client< /// Create a client with a TLS connection to the server using a pinned certificate /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https_pinned( @@ -305,7 +335,7 @@ impl Client< /// Create a client with a mutually authenticated TLS connection to the server. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server /// * `client_key` - Path to the client private key /// * `client_certificate` - Path to the client's public certificate associated with the private key @@ -367,12 +397,15 @@ pub enum ClientInitError { /// Missing Hostname MissingHost, + /// HTTPS requested but TLS features not enabled + TlsNotEnabled, + /// SSL Connection Error - #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + #[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] SslError(native_tls::Error), /// SSL Connection Error - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] SslError(openssl::error::ErrorStack), } diff --git a/samples/server/petstore/rust-server/output/no-example-v3/Cargo.toml b/samples/server/petstore/rust-server/output/no-example-v3/Cargo.toml index c67e31f2c818..338e93e4fe19 100644 --- a/samples/server/petstore/rust-server/output/no-example-v3/Cargo.toml +++ b/samples/server/petstore/rust-server/output/no-example-v3/Cargo.toml @@ -10,7 +10,19 @@ edition = "2018" [features] default = ["client", "server"] client = [ - "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url" + "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "url" +] +# TLS support - automatically selects backend based on target OS: +# - macOS/Windows/iOS: native-tls via hyper-tls +# - Other platforms: OpenSSL via hyper-openssl +# Dependencies are in target-specific sections below +client-tls = [ + "client", + "dep:native-tls", + "dep:hyper-tls", + "dep:openssl", + "dep:hyper-openssl", + "swagger/tls" ] server = [ "serde_ignored", "hyper", "percent-encoding", "url", @@ -24,20 +36,12 @@ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk- mock = ["mockall"] validate = ["regex", "serde_valid", "swagger/serdevalid"] -[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] -native-tls = { version = "0.2", optional = true } -hyper-tls = { version = "0.6", optional = true } - -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies] -hyper-openssl = { version = "0.10", optional = true } -openssl = { version = "0.10", optional = true } - [dependencies] # Common async-trait = "0.1.88" chrono = { version = "0.4", features = ["serde"] } futures = "0.3" -swagger = { version = "7.0.0", features = ["serdejson", "server", "client", "tls"] } +swagger = { version = "7.0.0", features = ["serdejson", "server", "client"] } headers = "0.4.0" log = "0.4.27" @@ -64,6 +68,12 @@ url = { version = "2.5", optional = true } # Client-specific tower-service = "0.3.3" +# TLS support - all listed here, actual usage determined by cfg attributes in code +native-tls = { version = "0.2", optional = true } +hyper-tls = { version = "0.6", optional = true } +openssl = { version = "0.10", optional = true } +hyper-openssl = { version = "0.10", optional = true } + # Server, and client callback-specific lazy_static = { version = "1.5", optional = true } regex = { version = "1.12", optional = true } @@ -89,15 +99,13 @@ clap = "4.5" env_logger = "0.11" tokio = { version = "1.49", features = ["full"] } native-tls = "0.2" +openssl = "0.10" +tokio-openssl = "0.6" pin-project = "1.1.10" # Bearer authentication, used in examples jsonwebtoken = {version = "10.0.0", features = ["rust_crypto"]} -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies] -tokio-openssl = "0.6" -openssl = "0.10" - [[example]] name = "client" required-features = ["client"] diff --git a/samples/server/petstore/rust-server/output/no-example-v3/README.md b/samples/server/petstore/rust-server/output/no-example-v3/README.md index b72555bb59de..e7896ae6dbcf 100644 --- a/samples/server/petstore/rust-server/output/no-example-v3/README.md +++ b/samples/server/petstore/rust-server/output/no-example-v3/README.md @@ -108,6 +108,11 @@ The generated library has a few optional features that can be activated through * `client` * This defaults to enabled and creates the basic skeleton of a client implementation based on hyper * The constructed client implements the API trait by making remote API call. +* `client-tls` + * Optional feature that provides HTTPS support with automatic TLS backend selection: + - macOS/Windows/iOS: native-tls + hyper-tls + - Linux/Unix/others: OpenSSL + hyper-openssl + * Not enabled by default to minimize dependencies. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. * `cli` @@ -116,6 +121,27 @@ The generated library has a few optional features that can be activated through * This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`. * Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks. +### Enabling HTTPS/TLS Support + +By default, only HTTP support is included. To enable HTTPS, add the `client-tls` feature: + +```toml +[dependencies] +no-example-v3 = { version = "0.0.1", features = ["client-tls"] } +``` + +**For server with callbacks that need HTTPS:** +```toml +[dependencies] +no-example-v3 = { version = "0.0.1", features = ["server", "client-tls"] } +``` + +The TLS backend is automatically selected based on your target platform: +- **macOS, Windows, iOS**: Uses `native-tls` (system TLS libraries) +- **Linux, Unix, other platforms**: Uses `openssl` + +This ensures the best compatibility and native integration on each platform. + See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. ## Documentation for API Endpoints diff --git a/samples/server/petstore/rust-server/output/no-example-v3/bin/cli.rs b/samples/server/petstore/rust-server/output/no-example-v3/bin/cli.rs index cb16d5acbd2d..df0d77c500e3 100644 --- a/samples/server/petstore/rust-server/output/no-example-v3/bin/cli.rs +++ b/samples/server/petstore/rust-server/output/no-example-v3/bin/cli.rs @@ -33,17 +33,17 @@ struct Cli { server_address: String, /// Path to the client private key if using client-side TLS authentication - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_certificate", "server_certificate"]))] client_key: Option, /// Path to the client's public certificate associated with the private key - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_key", "server_certificate"]))] client_certificate: Option, /// Path to CA certificate used to authenticate the server - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long)] server_certificate: Option, @@ -63,7 +63,8 @@ enum Operation { }, } -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +// On Linux/Unix with OpenSSL (client-tls feature), support certificate pinning and mutual TLS +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] fn create_client(args: &Cli, context: ClientContext) -> Result>> { if args.client_certificate.is_some() { debug!("Using mutual TLS"); @@ -89,8 +90,15 @@ fn create_client(args: &Cli, context: ClientContext) -> Result Result>> { + // Client::try_new() automatically detects the URL scheme (http:// or https://) + // and creates the appropriate client. + // Note: Certificate pinning and mutual TLS are only available on Linux/Unix with OpenSSL let client = Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; Ok(Box::new(client.with_context(context))) diff --git a/samples/server/petstore/rust-server/output/no-example-v3/examples/client/main.rs b/samples/server/petstore/rust-server/output/no-example-v3/examples/client/main.rs index 5d41ca4cb1f5..dcb6aa15a9ca 100644 --- a/samples/server/petstore/rust-server/output/no-example-v3/examples/client/main.rs +++ b/samples/server/petstore/rust-server/output/no-example-v3/examples/client/main.rs @@ -85,17 +85,33 @@ fn main() { let context: ClientContext = swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default()); - let mut client : Box> = if is_https { - // Using Simple HTTPS - let client = Box::new(Client::try_new_https(&base_url) - .expect("Failed to create HTTPS client")); - Box::new(client.with_context(context)) - } else { - // Using HTTP - let client = Box::new(Client::try_new_http( - &base_url) - .expect("Failed to create HTTP client")); - Box::new(client.with_context(context)) + let mut client : Box> = { + #[cfg(feature = "client-tls")] + { + if is_https { + // Using HTTPS with native-tls + let client = Box::new(Client::try_new_https(&base_url) + .expect("Failed to create HTTPS client")); + Box::new(client.with_context(context)) + } else { + // Using HTTP + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } + } + + #[cfg(not(feature = "client-tls"))] + { + if is_https { + panic!("HTTPS requested but TLS support not enabled. \ + Enable the 'client-tls' feature to use HTTPS."); + } + // Using HTTP only + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } }; let mut rt = tokio::runtime::Runtime::new().unwrap(); diff --git a/samples/server/petstore/rust-server/output/no-example-v3/src/client/mod.rs b/samples/server/petstore/rust-server/output/no-example-v3/src/client/mod.rs index 6bdb31f4c2fc..6af5a49082e8 100644 --- a/samples/server/petstore/rust-server/output/no-example-v3/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/no-example-v3/src/client/mod.rs @@ -153,10 +153,17 @@ impl Client< } } +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] +type HyperHttpsConnector = hyper_tls::HttpsConnector; + +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] +type HyperHttpsConnector = hyper_openssl::client::legacy::HttpsConnector; + #[derive(Debug, Clone)] pub enum HyperClient { Http(hyper_util::client::legacy::Client>), - Https(hyper_util::client::legacy::Client>), + #[cfg(feature = "client-tls")] + Https(hyper_util::client::legacy::Client>), } impl Service>> for HyperClient { @@ -167,7 +174,8 @@ impl Service>> for HyperClient { fn call(&self, req: Request>) -> Self::Future { match self { HyperClient::Http(client) => client.request(req), - HyperClient::Https(client) => client.request(req) + #[cfg(feature = "client-tls")] + HyperClient::Https(client) => client.request(req), } } } @@ -193,11 +201,17 @@ impl Client, C> where "http" => { HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build())) }, + #[cfg(feature = "client-tls")] + "https" => { + let https_connector = connector + .https() + .build() + .map_err(ClientInitError::SslError)?; + HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector)) + }, + #[cfg(not(feature = "client-tls"))] "https" => { - let connector = connector.https() - .build() - .map_err(ClientInitError::SslError)?; - HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector)) + return Err(ClientInitError::TlsNotEnabled); }, _ => { return Err(ClientInitError::InvalidScheme); @@ -241,12 +255,13 @@ impl Client< } } -#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] type HttpsConnector = hyper_tls::HttpsConnector; -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector; +#[cfg(feature = "client-tls")] impl Client< DropContextService< hyper_util::service::TowerToHyperService< @@ -261,10 +276,25 @@ impl Client< > where C: Clone + Send + Sync + 'static { - /// Create a client with a TLS connection to the server + /// Create a client with a TLS connection to the server using native-tls. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + pub fn try_new_https(base_path: &str) -> Result + { + let https_connector = Connector::builder() + .https() + .build() + .map_err(ClientInitError::SslError)?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } + + /// Create a client with a TLS connection to the server using OpenSSL. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https(base_path: &str) -> Result { let https_connector = Connector::builder() @@ -277,7 +307,7 @@ impl Client< /// Create a client with a TLS connection to the server using a pinned certificate /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https_pinned( @@ -298,7 +328,7 @@ impl Client< /// Create a client with a mutually authenticated TLS connection to the server. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server /// * `client_key` - Path to the client private key /// * `client_certificate` - Path to the client's public certificate associated with the private key @@ -360,12 +390,15 @@ pub enum ClientInitError { /// Missing Hostname MissingHost, + /// HTTPS requested but TLS features not enabled + TlsNotEnabled, + /// SSL Connection Error - #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + #[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] SslError(native_tls::Error), /// SSL Connection Error - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] SslError(openssl::error::ErrorStack), } diff --git a/samples/server/petstore/rust-server/output/openapi-v3/Cargo.toml b/samples/server/petstore/rust-server/output/openapi-v3/Cargo.toml index 4bb5c8f63af7..167e9c7caef3 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/Cargo.toml +++ b/samples/server/petstore/rust-server/output/openapi-v3/Cargo.toml @@ -12,10 +12,21 @@ default = ["client", "server"] client = [ "serde_urlencoded", "serde_ignored", "percent-encoding", - "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url" + "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "url" +] +# TLS support - automatically selects backend based on target OS: +# - macOS/Windows/iOS: native-tls via hyper-tls +# - Other platforms: OpenSSL via hyper-openssl +# Dependencies are in target-specific sections below +client-tls = [ + "client", + "dep:native-tls", + "dep:hyper-tls", + "dep:openssl", + "dep:hyper-openssl", + "swagger/tls" ] server = [ - "native-tls", "hyper-openssl", "hyper-tls", "openssl", "serde_ignored", "hyper", "percent-encoding", "url", ] @@ -27,20 +38,12 @@ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk- mock = ["mockall"] validate = [ "serde_valid", "swagger/serdevalid"] -[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] -native-tls = { version = "0.2", optional = true } -hyper-tls = { version = "0.6", optional = true } - -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies] -hyper-openssl = { version = "0.10", optional = true } -openssl = { version = "0.10", optional = true } - [dependencies] # Common async-trait = "0.1.88" chrono = { version = "0.4", features = ["serde"] } futures = "0.3" -swagger = { version = "7.0.0", features = ["serdejson", "server", "client", "tls"] } +swagger = { version = "7.0.0", features = ["serdejson", "server", "client"] } headers = "0.4.0" log = "0.4.27" @@ -72,6 +75,12 @@ url = { version = "2.5", optional = true } serde_urlencoded = { version = "0.7.1", optional = true } tower-service = "0.3.3" +# TLS support - all listed here, actual usage determined by cfg attributes in code +native-tls = { version = "0.2", optional = true } +hyper-tls = { version = "0.6", optional = true } +openssl = { version = "0.10", optional = true } +hyper-openssl = { version = "0.10", optional = true } + # Server, and client callback-specific percent-encoding = { version = "2.3.2", optional = true } @@ -95,15 +104,13 @@ clap = "4.5" env_logger = "0.11" tokio = { version = "1.49", features = ["full"] } native-tls = "0.2" +openssl = "0.10" +tokio-openssl = "0.6" pin-project = "1.1.10" # Bearer authentication, used in examples jsonwebtoken = {version = "10.0.0", features = ["rust_crypto"]} -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies] -tokio-openssl = "0.6" -openssl = "0.10" - [[example]] name = "client" required-features = ["client"] diff --git a/samples/server/petstore/rust-server/output/openapi-v3/README.md b/samples/server/petstore/rust-server/output/openapi-v3/README.md index 2989d965d76c..2728f483e178 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/README.md +++ b/samples/server/petstore/rust-server/output/openapi-v3/README.md @@ -139,6 +139,11 @@ The generated library has a few optional features that can be activated through * `client` * This defaults to enabled and creates the basic skeleton of a client implementation based on hyper * The constructed client implements the API trait by making remote API call. +* `client-tls` + * Optional feature that provides HTTPS support with automatic TLS backend selection: + - macOS/Windows/iOS: native-tls + hyper-tls + - Linux/Unix/others: OpenSSL + hyper-openssl + * Not enabled by default to minimize dependencies. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. * `cli` @@ -147,6 +152,27 @@ The generated library has a few optional features that can be activated through * This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`. * Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks. +### Enabling HTTPS/TLS Support + +By default, only HTTP support is included. To enable HTTPS, add the `client-tls` feature: + +```toml +[dependencies] +openapi-v3 = { version = "1.0.7", features = ["client-tls"] } +``` + +**For server with callbacks that need HTTPS:** +```toml +[dependencies] +openapi-v3 = { version = "1.0.7", features = ["server", "client-tls"] } +``` + +The TLS backend is automatically selected based on your target platform: +- **macOS, Windows, iOS**: Uses `native-tls` (system TLS libraries) +- **Linux, Unix, other platforms**: Uses `openssl` + +This ensures the best compatibility and native integration on each platform. + See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. ## Documentation for API Endpoints diff --git a/samples/server/petstore/rust-server/output/openapi-v3/bin/cli.rs b/samples/server/petstore/rust-server/output/openapi-v3/bin/cli.rs index 30e4193c0756..271d62349e7f 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/bin/cli.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/bin/cli.rs @@ -63,17 +63,17 @@ struct Cli { server_address: String, /// Path to the client private key if using client-side TLS authentication - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_certificate", "server_certificate"]))] client_key: Option, /// Path to the client's public certificate associated with the private key - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_key", "server_certificate"]))] client_certificate: Option, /// Path to CA certificate used to authenticate the server - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long)] server_certificate: Option, @@ -214,7 +214,8 @@ enum Operation { }, } -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +// On Linux/Unix with OpenSSL (client-tls feature), support certificate pinning and mutual TLS +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] fn create_client(args: &Cli, context: ClientContext) -> Result>> { if args.client_certificate.is_some() { debug!("Using mutual TLS"); @@ -240,8 +241,15 @@ fn create_client(args: &Cli, context: ClientContext) -> Result Result>> { + // Client::try_new() automatically detects the URL scheme (http:// or https://) + // and creates the appropriate client. + // Note: Certificate pinning and mutual TLS are only available on Linux/Unix with OpenSSL let client = Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; Ok(Box::new(client.with_context(context))) diff --git a/samples/server/petstore/rust-server/output/openapi-v3/examples/client/main.rs b/samples/server/petstore/rust-server/output/openapi-v3/examples/client/main.rs index 1a59fa58bf15..1af6586cb53f 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/examples/client/main.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/examples/client/main.rs @@ -151,17 +151,33 @@ fn main() { let context: ClientContext = swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default()); - let mut client : Box> = if is_https { - // Using Simple HTTPS - let client = Box::new(Client::try_new_https(&base_url) - .expect("Failed to create HTTPS client")); - Box::new(client.with_context(context)) - } else { - // Using HTTP - let client = Box::new(Client::try_new_http( - &base_url) - .expect("Failed to create HTTP client")); - Box::new(client.with_context(context)) + let mut client : Box> = { + #[cfg(feature = "client-tls")] + { + if is_https { + // Using HTTPS with native-tls + let client = Box::new(Client::try_new_https(&base_url) + .expect("Failed to create HTTPS client")); + Box::new(client.with_context(context)) + } else { + // Using HTTP + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } + } + + #[cfg(not(feature = "client-tls"))] + { + if is_https { + panic!("HTTPS requested but TLS support not enabled. \ + Enable the 'client-tls' feature to use HTTPS."); + } + // Using HTTP only + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } }; let mut rt = tokio::runtime::Runtime::new().unwrap(); diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs index f10dc0d32b95..c52cdaf4a90b 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs @@ -185,10 +185,17 @@ impl Client< } } +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] +type HyperHttpsConnector = hyper_tls::HttpsConnector; + +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] +type HyperHttpsConnector = hyper_openssl::client::legacy::HttpsConnector; + #[derive(Debug, Clone)] pub enum HyperClient { Http(hyper_util::client::legacy::Client>), - Https(hyper_util::client::legacy::Client>), + #[cfg(feature = "client-tls")] + Https(hyper_util::client::legacy::Client>), } impl Service>> for HyperClient { @@ -199,7 +206,8 @@ impl Service>> for HyperClient { fn call(&self, req: Request>) -> Self::Future { match self { HyperClient::Http(client) => client.request(req), - HyperClient::Https(client) => client.request(req) + #[cfg(feature = "client-tls")] + HyperClient::Https(client) => client.request(req), } } } @@ -225,11 +233,17 @@ impl Client, C> where "http" => { HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build())) }, + #[cfg(feature = "client-tls")] + "https" => { + let https_connector = connector + .https() + .build() + .map_err(ClientInitError::SslError)?; + HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector)) + }, + #[cfg(not(feature = "client-tls"))] "https" => { - let connector = connector.https() - .build() - .map_err(ClientInitError::SslError)?; - HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector)) + return Err(ClientInitError::TlsNotEnabled); }, _ => { return Err(ClientInitError::InvalidScheme); @@ -273,12 +287,13 @@ impl Client< } } -#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] type HttpsConnector = hyper_tls::HttpsConnector; -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector; +#[cfg(feature = "client-tls")] impl Client< DropContextService< hyper_util::service::TowerToHyperService< @@ -293,10 +308,25 @@ impl Client< > where C: Clone + Send + Sync + 'static { - /// Create a client with a TLS connection to the server + /// Create a client with a TLS connection to the server using native-tls. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + pub fn try_new_https(base_path: &str) -> Result + { + let https_connector = Connector::builder() + .https() + .build() + .map_err(ClientInitError::SslError)?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } + + /// Create a client with a TLS connection to the server using OpenSSL. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https(base_path: &str) -> Result { let https_connector = Connector::builder() @@ -309,7 +339,7 @@ impl Client< /// Create a client with a TLS connection to the server using a pinned certificate /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https_pinned( @@ -330,7 +360,7 @@ impl Client< /// Create a client with a mutually authenticated TLS connection to the server. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server /// * `client_key` - Path to the client private key /// * `client_certificate` - Path to the client's public certificate associated with the private key @@ -392,12 +422,15 @@ pub enum ClientInitError { /// Missing Hostname MissingHost, + /// HTTPS requested but TLS features not enabled + TlsNotEnabled, + /// SSL Connection Error - #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + #[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] SslError(native_tls::Error), /// SSL Connection Error - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] SslError(openssl::error::ErrorStack), } diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/server/callbacks.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/server/callbacks.rs index 5a96b66851d4..9a0c9e74650a 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/server/callbacks.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/server/callbacks.rs @@ -123,16 +123,17 @@ impl Client; -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector; +#[cfg(feature = "client-tls")] impl Client>>, C>, C> where C: Clone + Send + Sync + 'static { - /// Create a client with a TLS connection to the server. + /// Create a client with a TLS connection to the server using native-tls. #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] pub fn new_https() -> Result { @@ -140,7 +141,7 @@ impl Client Result { diff --git a/samples/server/petstore/rust-server/output/ops-v3/Cargo.toml b/samples/server/petstore/rust-server/output/ops-v3/Cargo.toml index 2aa4340664c7..0f2226a37483 100644 --- a/samples/server/petstore/rust-server/output/ops-v3/Cargo.toml +++ b/samples/server/petstore/rust-server/output/ops-v3/Cargo.toml @@ -10,7 +10,19 @@ edition = "2018" [features] default = ["client", "server"] client = [ - "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url" + "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "url" +] +# TLS support - automatically selects backend based on target OS: +# - macOS/Windows/iOS: native-tls via hyper-tls +# - Other platforms: OpenSSL via hyper-openssl +# Dependencies are in target-specific sections below +client-tls = [ + "client", + "dep:native-tls", + "dep:hyper-tls", + "dep:openssl", + "dep:hyper-openssl", + "swagger/tls" ] server = [ "serde_ignored", "hyper", "percent-encoding", "url", @@ -24,20 +36,12 @@ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk- mock = ["mockall"] validate = ["regex", "serde_valid", "swagger/serdevalid"] -[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] -native-tls = { version = "0.2", optional = true } -hyper-tls = { version = "0.6", optional = true } - -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies] -hyper-openssl = { version = "0.10", optional = true } -openssl = { version = "0.10", optional = true } - [dependencies] # Common async-trait = "0.1.88" chrono = { version = "0.4", features = ["serde"] } futures = "0.3" -swagger = { version = "7.0.0", features = ["serdejson", "server", "client", "tls"] } +swagger = { version = "7.0.0", features = ["serdejson", "server", "client"] } headers = "0.4.0" log = "0.4.27" @@ -64,6 +68,12 @@ url = { version = "2.5", optional = true } # Client-specific tower-service = "0.3.3" +# TLS support - all listed here, actual usage determined by cfg attributes in code +native-tls = { version = "0.2", optional = true } +hyper-tls = { version = "0.6", optional = true } +openssl = { version = "0.10", optional = true } +hyper-openssl = { version = "0.10", optional = true } + # Server, and client callback-specific lazy_static = { version = "1.5", optional = true } regex = { version = "1.12", optional = true } @@ -89,15 +99,13 @@ clap = "4.5" env_logger = "0.11" tokio = { version = "1.49", features = ["full"] } native-tls = "0.2" +openssl = "0.10" +tokio-openssl = "0.6" pin-project = "1.1.10" # Bearer authentication, used in examples jsonwebtoken = {version = "10.0.0", features = ["rust_crypto"]} -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies] -tokio-openssl = "0.6" -openssl = "0.10" - [[example]] name = "client" required-features = ["client"] diff --git a/samples/server/petstore/rust-server/output/ops-v3/README.md b/samples/server/petstore/rust-server/output/ops-v3/README.md index 24c642ae0416..247c2a315180 100644 --- a/samples/server/petstore/rust-server/output/ops-v3/README.md +++ b/samples/server/petstore/rust-server/output/ops-v3/README.md @@ -145,6 +145,11 @@ The generated library has a few optional features that can be activated through * `client` * This defaults to enabled and creates the basic skeleton of a client implementation based on hyper * The constructed client implements the API trait by making remote API call. +* `client-tls` + * Optional feature that provides HTTPS support with automatic TLS backend selection: + - macOS/Windows/iOS: native-tls + hyper-tls + - Linux/Unix/others: OpenSSL + hyper-openssl + * Not enabled by default to minimize dependencies. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. * `cli` @@ -153,6 +158,27 @@ The generated library has a few optional features that can be activated through * This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`. * Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks. +### Enabling HTTPS/TLS Support + +By default, only HTTP support is included. To enable HTTPS, add the `client-tls` feature: + +```toml +[dependencies] +ops-v3 = { version = "0.0.1", features = ["client-tls"] } +``` + +**For server with callbacks that need HTTPS:** +```toml +[dependencies] +ops-v3 = { version = "0.0.1", features = ["server", "client-tls"] } +``` + +The TLS backend is automatically selected based on your target platform: +- **macOS, Windows, iOS**: Uses `native-tls` (system TLS libraries) +- **Linux, Unix, other platforms**: Uses `openssl` + +This ensures the best compatibility and native integration on each platform. + See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. ## Documentation for API Endpoints diff --git a/samples/server/petstore/rust-server/output/ops-v3/bin/cli.rs b/samples/server/petstore/rust-server/output/ops-v3/bin/cli.rs index 31a94281e196..372f0362e2bf 100644 --- a/samples/server/petstore/rust-server/output/ops-v3/bin/cli.rs +++ b/samples/server/petstore/rust-server/output/ops-v3/bin/cli.rs @@ -69,17 +69,17 @@ struct Cli { server_address: String, /// Path to the client private key if using client-side TLS authentication - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_certificate", "server_certificate"]))] client_key: Option, /// Path to the client's public certificate associated with the private key - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_key", "server_certificate"]))] client_certificate: Option, /// Path to CA certificate used to authenticate the server - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long)] server_certificate: Option, @@ -169,7 +169,8 @@ enum Operation { }, } -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +// On Linux/Unix with OpenSSL (client-tls feature), support certificate pinning and mutual TLS +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] fn create_client(args: &Cli, context: ClientContext) -> Result>> { if args.client_certificate.is_some() { debug!("Using mutual TLS"); @@ -195,8 +196,15 @@ fn create_client(args: &Cli, context: ClientContext) -> Result Result>> { + // Client::try_new() automatically detects the URL scheme (http:// or https://) + // and creates the appropriate client. + // Note: Certificate pinning and mutual TLS are only available on Linux/Unix with OpenSSL let client = Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; Ok(Box::new(client.with_context(context))) diff --git a/samples/server/petstore/rust-server/output/ops-v3/examples/client/main.rs b/samples/server/petstore/rust-server/output/ops-v3/examples/client/main.rs index b829ce8b9a4d..e9c51dfbcad6 100644 --- a/samples/server/petstore/rust-server/output/ops-v3/examples/client/main.rs +++ b/samples/server/petstore/rust-server/output/ops-v3/examples/client/main.rs @@ -157,17 +157,33 @@ fn main() { let context: ClientContext = swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default()); - let mut client : Box> = if is_https { - // Using Simple HTTPS - let client = Box::new(Client::try_new_https(&base_url) - .expect("Failed to create HTTPS client")); - Box::new(client.with_context(context)) - } else { - // Using HTTP - let client = Box::new(Client::try_new_http( - &base_url) - .expect("Failed to create HTTP client")); - Box::new(client.with_context(context)) + let mut client : Box> = { + #[cfg(feature = "client-tls")] + { + if is_https { + // Using HTTPS with native-tls + let client = Box::new(Client::try_new_https(&base_url) + .expect("Failed to create HTTPS client")); + Box::new(client.with_context(context)) + } else { + // Using HTTP + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } + } + + #[cfg(not(feature = "client-tls"))] + { + if is_https { + panic!("HTTPS requested but TLS support not enabled. \ + Enable the 'client-tls' feature to use HTTPS."); + } + // Using HTTP only + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } }; let mut rt = tokio::runtime::Runtime::new().unwrap(); diff --git a/samples/server/petstore/rust-server/output/ops-v3/src/client/mod.rs b/samples/server/petstore/rust-server/output/ops-v3/src/client/mod.rs index 90bfb329b0d0..17ef4ceafaec 100644 --- a/samples/server/petstore/rust-server/output/ops-v3/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/ops-v3/src/client/mod.rs @@ -189,10 +189,17 @@ impl Client< } } +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] +type HyperHttpsConnector = hyper_tls::HttpsConnector; + +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] +type HyperHttpsConnector = hyper_openssl::client::legacy::HttpsConnector; + #[derive(Debug, Clone)] pub enum HyperClient { Http(hyper_util::client::legacy::Client>), - Https(hyper_util::client::legacy::Client>), + #[cfg(feature = "client-tls")] + Https(hyper_util::client::legacy::Client>), } impl Service>> for HyperClient { @@ -203,7 +210,8 @@ impl Service>> for HyperClient { fn call(&self, req: Request>) -> Self::Future { match self { HyperClient::Http(client) => client.request(req), - HyperClient::Https(client) => client.request(req) + #[cfg(feature = "client-tls")] + HyperClient::Https(client) => client.request(req), } } } @@ -229,11 +237,17 @@ impl Client, C> where "http" => { HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build())) }, + #[cfg(feature = "client-tls")] + "https" => { + let https_connector = connector + .https() + .build() + .map_err(ClientInitError::SslError)?; + HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector)) + }, + #[cfg(not(feature = "client-tls"))] "https" => { - let connector = connector.https() - .build() - .map_err(ClientInitError::SslError)?; - HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector)) + return Err(ClientInitError::TlsNotEnabled); }, _ => { return Err(ClientInitError::InvalidScheme); @@ -277,12 +291,13 @@ impl Client< } } -#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] type HttpsConnector = hyper_tls::HttpsConnector; -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector; +#[cfg(feature = "client-tls")] impl Client< DropContextService< hyper_util::service::TowerToHyperService< @@ -297,10 +312,25 @@ impl Client< > where C: Clone + Send + Sync + 'static { - /// Create a client with a TLS connection to the server + /// Create a client with a TLS connection to the server using native-tls. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + pub fn try_new_https(base_path: &str) -> Result + { + let https_connector = Connector::builder() + .https() + .build() + .map_err(ClientInitError::SslError)?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } + + /// Create a client with a TLS connection to the server using OpenSSL. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https(base_path: &str) -> Result { let https_connector = Connector::builder() @@ -313,7 +343,7 @@ impl Client< /// Create a client with a TLS connection to the server using a pinned certificate /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https_pinned( @@ -334,7 +364,7 @@ impl Client< /// Create a client with a mutually authenticated TLS connection to the server. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server /// * `client_key` - Path to the client private key /// * `client_certificate` - Path to the client's public certificate associated with the private key @@ -396,12 +426,15 @@ pub enum ClientInitError { /// Missing Hostname MissingHost, + /// HTTPS requested but TLS features not enabled + TlsNotEnabled, + /// SSL Connection Error - #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + #[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] SslError(native_tls::Error), /// SSL Connection Error - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] SslError(openssl::error::ErrorStack), } diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml index afb7740ea680..948d4cbadde4 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml @@ -12,7 +12,19 @@ default = ["client", "server"] client = [ "multipart", "multipart/client", "swagger/multipart_form", "serde_urlencoded", - "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url" + "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "url" +] +# TLS support - automatically selects backend based on target OS: +# - macOS/Windows/iOS: native-tls via hyper-tls +# - Other platforms: OpenSSL via hyper-openssl +# Dependencies are in target-specific sections below +client-tls = [ + "client", + "dep:native-tls", + "dep:hyper-tls", + "dep:openssl", + "dep:hyper-openssl", + "swagger/tls" ] server = [ "multipart", "multipart/server", "swagger/multipart_form", @@ -28,20 +40,12 @@ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk- mock = ["mockall"] validate = [ "serde_valid", "swagger/serdevalid"] -[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] -native-tls = { version = "0.2", optional = true } -hyper-tls = { version = "0.6", optional = true } - -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies] -hyper-openssl = { version = "0.10", optional = true } -openssl = { version = "0.10", optional = true } - [dependencies] # Common async-trait = "0.1.88" chrono = { version = "0.4", features = ["serde"] } futures = "0.3" -swagger = { version = "7.0.0", features = ["serdejson", "server", "client", "tls"] } +swagger = { version = "7.0.0", features = ["serdejson", "server", "client"] } headers = "0.4.0" log = "0.4.27" @@ -74,6 +78,12 @@ url = { version = "2.5", optional = true } serde_urlencoded = { version = "0.7.1", optional = true } tower-service = "0.3.3" +# TLS support - all listed here, actual usage determined by cfg attributes in code +native-tls = { version = "0.2", optional = true } +hyper-tls = { version = "0.6", optional = true } +openssl = { version = "0.10", optional = true } +hyper-openssl = { version = "0.10", optional = true } + # Server, and client callback-specific percent-encoding = { version = "2.3.2", optional = true } @@ -98,15 +108,13 @@ clap = "4.5" env_logger = "0.11" tokio = { version = "1.49", features = ["full"] } native-tls = "0.2" +openssl = "0.10" +tokio-openssl = "0.6" pin-project = "1.1.10" # Bearer authentication, used in examples jsonwebtoken = {version = "10.0.0", features = ["rust_crypto"]} -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies] -tokio-openssl = "0.6" -openssl = "0.10" - [[example]] name = "client" required-features = ["client"] diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/README.md b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/README.md index 12121f696964..d8e4ef54ccb6 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/README.md +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/README.md @@ -133,6 +133,11 @@ The generated library has a few optional features that can be activated through * `client` * This defaults to enabled and creates the basic skeleton of a client implementation based on hyper * The constructed client implements the API trait by making remote API call. +* `client-tls` + * Optional feature that provides HTTPS support with automatic TLS backend selection: + - macOS/Windows/iOS: native-tls + hyper-tls + - Linux/Unix/others: OpenSSL + hyper-openssl + * Not enabled by default to minimize dependencies. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. * `cli` @@ -141,6 +146,27 @@ The generated library has a few optional features that can be activated through * This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`. * Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks. +### Enabling HTTPS/TLS Support + +By default, only HTTP support is included. To enable HTTPS, add the `client-tls` feature: + +```toml +[dependencies] +petstore-with-fake-endpoints-models-for-testing = { version = "1.0.0", features = ["client-tls"] } +``` + +**For server with callbacks that need HTTPS:** +```toml +[dependencies] +petstore-with-fake-endpoints-models-for-testing = { version = "1.0.0", features = ["server", "client-tls"] } +``` + +The TLS backend is automatically selected based on your target platform: +- **macOS, Windows, iOS**: Uses `native-tls` (system TLS libraries) +- **Linux, Unix, other platforms**: Uses `openssl` + +This ensures the best compatibility and native integration on each platform. + See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. ## Documentation for API Endpoints diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs index 5cc7d996d9a2..19a4a3ded863 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/bin/cli.rs @@ -68,17 +68,17 @@ struct Cli { server_address: String, /// Path to the client private key if using client-side TLS authentication - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_certificate", "server_certificate"]))] client_key: Option, /// Path to the client's public certificate associated with the private key - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_key", "server_certificate"]))] client_certificate: Option, /// Path to CA certificate used to authenticate the server - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long)] server_certificate: Option, @@ -342,7 +342,8 @@ enum Operation { }, } -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +// On Linux/Unix with OpenSSL (client-tls feature), support certificate pinning and mutual TLS +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] fn create_client(args: &Cli, context: ClientContext) -> Result>> { if args.client_certificate.is_some() { debug!("Using mutual TLS"); @@ -368,8 +369,15 @@ fn create_client(args: &Cli, context: ClientContext) -> Result Result>> { + // Client::try_new() automatically detects the URL scheme (http:// or https://) + // and creates the appropriate client. + // Note: Certificate pinning and mutual TLS are only available on Linux/Unix with OpenSSL let client = Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; Ok(Box::new(client.with_context(context))) diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/examples/client/main.rs b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/examples/client/main.rs index 8e8aa8e817cf..df4b9e3db338 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/examples/client/main.rs +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/examples/client/main.rs @@ -156,17 +156,33 @@ fn main() { let context: ClientContext = swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default()); - let mut client : Box> = if is_https { - // Using Simple HTTPS - let client = Box::new(Client::try_new_https(&base_url) - .expect("Failed to create HTTPS client")); - Box::new(client.with_context(context)) - } else { - // Using HTTP - let client = Box::new(Client::try_new_http( - &base_url) - .expect("Failed to create HTTP client")); - Box::new(client.with_context(context)) + let mut client : Box> = { + #[cfg(feature = "client-tls")] + { + if is_https { + // Using HTTPS with native-tls + let client = Box::new(Client::try_new_https(&base_url) + .expect("Failed to create HTTPS client")); + Box::new(client.with_context(context)) + } else { + // Using HTTP + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } + } + + #[cfg(not(feature = "client-tls"))] + { + if is_https { + panic!("HTTPS requested but TLS support not enabled. \ + Enable the 'client-tls' feature to use HTTPS."); + } + // Using HTTP only + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } }; let mut rt = tokio::runtime::Runtime::new().unwrap(); diff --git a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/client/mod.rs b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/client/mod.rs index 93a264aefb97..f3d08aea796a 100644 --- a/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/petstore-with-fake-endpoints-models-for-testing/src/client/mod.rs @@ -190,10 +190,17 @@ impl Client< } } +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] +type HyperHttpsConnector = hyper_tls::HttpsConnector; + +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] +type HyperHttpsConnector = hyper_openssl::client::legacy::HttpsConnector; + #[derive(Debug, Clone)] pub enum HyperClient { Http(hyper_util::client::legacy::Client>), - Https(hyper_util::client::legacy::Client>), + #[cfg(feature = "client-tls")] + Https(hyper_util::client::legacy::Client>), } impl Service>> for HyperClient { @@ -204,7 +211,8 @@ impl Service>> for HyperClient { fn call(&self, req: Request>) -> Self::Future { match self { HyperClient::Http(client) => client.request(req), - HyperClient::Https(client) => client.request(req) + #[cfg(feature = "client-tls")] + HyperClient::Https(client) => client.request(req), } } } @@ -230,11 +238,17 @@ impl Client, C> where "http" => { HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build())) }, + #[cfg(feature = "client-tls")] + "https" => { + let https_connector = connector + .https() + .build() + .map_err(ClientInitError::SslError)?; + HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector)) + }, + #[cfg(not(feature = "client-tls"))] "https" => { - let connector = connector.https() - .build() - .map_err(ClientInitError::SslError)?; - HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector)) + return Err(ClientInitError::TlsNotEnabled); }, _ => { return Err(ClientInitError::InvalidScheme); @@ -278,12 +292,13 @@ impl Client< } } -#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] type HttpsConnector = hyper_tls::HttpsConnector; -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector; +#[cfg(feature = "client-tls")] impl Client< DropContextService< hyper_util::service::TowerToHyperService< @@ -298,10 +313,25 @@ impl Client< > where C: Clone + Send + Sync + 'static { - /// Create a client with a TLS connection to the server + /// Create a client with a TLS connection to the server using native-tls. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + pub fn try_new_https(base_path: &str) -> Result + { + let https_connector = Connector::builder() + .https() + .build() + .map_err(ClientInitError::SslError)?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } + + /// Create a client with a TLS connection to the server using OpenSSL. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https(base_path: &str) -> Result { let https_connector = Connector::builder() @@ -314,7 +344,7 @@ impl Client< /// Create a client with a TLS connection to the server using a pinned certificate /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https_pinned( @@ -335,7 +365,7 @@ impl Client< /// Create a client with a mutually authenticated TLS connection to the server. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server /// * `client_key` - Path to the client private key /// * `client_certificate` - Path to the client's public certificate associated with the private key @@ -397,12 +427,15 @@ pub enum ClientInitError { /// Missing Hostname MissingHost, + /// HTTPS requested but TLS features not enabled + TlsNotEnabled, + /// SSL Connection Error - #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + #[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] SslError(native_tls::Error), /// SSL Connection Error - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] SslError(openssl::error::ErrorStack), } diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml b/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml index 36d6fcdc04b7..ec7d1bd02114 100644 --- a/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml @@ -10,7 +10,19 @@ edition = "2018" [features] default = ["client", "server"] client = [ - "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url" + "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "url" +] +# TLS support - automatically selects backend based on target OS: +# - macOS/Windows/iOS: native-tls via hyper-tls +# - Other platforms: OpenSSL via hyper-openssl +# Dependencies are in target-specific sections below +client-tls = [ + "client", + "dep:native-tls", + "dep:hyper-tls", + "dep:openssl", + "dep:hyper-openssl", + "swagger/tls" ] server = [ "serde_ignored", "hyper", "percent-encoding", "url", @@ -24,20 +36,12 @@ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk- mock = ["mockall"] validate = ["regex", "serde_valid", "swagger/serdevalid"] -[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] -native-tls = { version = "0.2", optional = true } -hyper-tls = { version = "0.6", optional = true } - -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies] -hyper-openssl = { version = "0.10", optional = true } -openssl = { version = "0.10", optional = true } - [dependencies] # Common async-trait = "0.1.88" chrono = { version = "0.4", features = ["serde"] } futures = "0.3" -swagger = { version = "7.0.0", features = ["serdejson", "server", "client", "tls"] } +swagger = { version = "7.0.0", features = ["serdejson", "server", "client"] } headers = "0.4.0" log = "0.4.27" @@ -64,6 +68,12 @@ url = { version = "2.5", optional = true } # Client-specific tower-service = "0.3.3" +# TLS support - all listed here, actual usage determined by cfg attributes in code +native-tls = { version = "0.2", optional = true } +hyper-tls = { version = "0.6", optional = true } +openssl = { version = "0.10", optional = true } +hyper-openssl = { version = "0.10", optional = true } + # Server, and client callback-specific lazy_static = { version = "1.5", optional = true } regex = { version = "1.12", optional = true } @@ -89,15 +99,13 @@ clap = "4.5" env_logger = "0.11" tokio = { version = "1.49", features = ["full"] } native-tls = "0.2" +openssl = "0.10" +tokio-openssl = "0.6" pin-project = "1.1.10" # Bearer authentication, used in examples jsonwebtoken = {version = "10.0.0", features = ["rust_crypto"]} -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies] -tokio-openssl = "0.6" -openssl = "0.10" - [[example]] name = "client" required-features = ["client"] diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md b/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md index d6d9b8de49b6..0c451cb6f4ca 100644 --- a/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md @@ -109,6 +109,11 @@ The generated library has a few optional features that can be activated through * `client` * This defaults to enabled and creates the basic skeleton of a client implementation based on hyper * The constructed client implements the API trait by making remote API call. +* `client-tls` + * Optional feature that provides HTTPS support with automatic TLS backend selection: + - macOS/Windows/iOS: native-tls + hyper-tls + - Linux/Unix/others: OpenSSL + hyper-openssl + * Not enabled by default to minimize dependencies. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. * `cli` @@ -117,6 +122,27 @@ The generated library has a few optional features that can be activated through * This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`. * Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks. +### Enabling HTTPS/TLS Support + +By default, only HTTP support is included. To enable HTTPS, add the `client-tls` feature: + +```toml +[dependencies] +ping-bearer-auth = { version = "1.0.0", features = ["client-tls"] } +``` + +**For server with callbacks that need HTTPS:** +```toml +[dependencies] +ping-bearer-auth = { version = "1.0.0", features = ["server", "client-tls"] } +``` + +The TLS backend is automatically selected based on your target platform: +- **macOS, Windows, iOS**: Uses `native-tls` (system TLS libraries) +- **Linux, Unix, other platforms**: Uses `openssl` + +This ensures the best compatibility and native integration on each platform. + See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. ## Documentation for API Endpoints diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/bin/cli.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/bin/cli.rs index 74286933066e..7256eebe98c3 100644 --- a/samples/server/petstore/rust-server/output/ping-bearer-auth/bin/cli.rs +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/bin/cli.rs @@ -33,17 +33,17 @@ struct Cli { server_address: String, /// Path to the client private key if using client-side TLS authentication - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_certificate", "server_certificate"]))] client_key: Option, /// Path to the client's public certificate associated with the private key - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_key", "server_certificate"]))] client_certificate: Option, /// Path to CA certificate used to authenticate the server - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long)] server_certificate: Option, @@ -65,7 +65,8 @@ enum Operation { }, } -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +// On Linux/Unix with OpenSSL (client-tls feature), support certificate pinning and mutual TLS +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] fn create_client(args: &Cli, context: ClientContext) -> Result>> { if args.client_certificate.is_some() { debug!("Using mutual TLS"); @@ -91,8 +92,15 @@ fn create_client(args: &Cli, context: ClientContext) -> Result Result>> { + // Client::try_new() automatically detects the URL scheme (http:// or https://) + // and creates the appropriate client. + // Note: Certificate pinning and mutual TLS are only available on Linux/Unix with OpenSSL let client = Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; Ok(Box::new(client.with_context(context))) diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/client/main.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/client/main.rs index 24d1f7fe28ef..c7ff5d2f012e 100644 --- a/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/client/main.rs +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/client/main.rs @@ -85,17 +85,33 @@ fn main() { let context: ClientContext = swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default()); - let mut client : Box> = if is_https { - // Using Simple HTTPS - let client = Box::new(Client::try_new_https(&base_url) - .expect("Failed to create HTTPS client")); - Box::new(client.with_context(context)) - } else { - // Using HTTP - let client = Box::new(Client::try_new_http( - &base_url) - .expect("Failed to create HTTP client")); - Box::new(client.with_context(context)) + let mut client : Box> = { + #[cfg(feature = "client-tls")] + { + if is_https { + // Using HTTPS with native-tls + let client = Box::new(Client::try_new_https(&base_url) + .expect("Failed to create HTTPS client")); + Box::new(client.with_context(context)) + } else { + // Using HTTP + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } + } + + #[cfg(not(feature = "client-tls"))] + { + if is_https { + panic!("HTTPS requested but TLS support not enabled. \ + Enable the 'client-tls' feature to use HTTPS."); + } + // Using HTTP only + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } }; let mut rt = tokio::runtime::Runtime::new().unwrap(); diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/src/client/mod.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/client/mod.rs index 7918d022c501..ef7f47000c02 100644 --- a/samples/server/petstore/rust-server/output/ping-bearer-auth/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/client/mod.rs @@ -153,10 +153,17 @@ impl Client< } } +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] +type HyperHttpsConnector = hyper_tls::HttpsConnector; + +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] +type HyperHttpsConnector = hyper_openssl::client::legacy::HttpsConnector; + #[derive(Debug, Clone)] pub enum HyperClient { Http(hyper_util::client::legacy::Client>), - Https(hyper_util::client::legacy::Client>), + #[cfg(feature = "client-tls")] + Https(hyper_util::client::legacy::Client>), } impl Service>> for HyperClient { @@ -167,7 +174,8 @@ impl Service>> for HyperClient { fn call(&self, req: Request>) -> Self::Future { match self { HyperClient::Http(client) => client.request(req), - HyperClient::Https(client) => client.request(req) + #[cfg(feature = "client-tls")] + HyperClient::Https(client) => client.request(req), } } } @@ -193,11 +201,17 @@ impl Client, C> where "http" => { HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build())) }, + #[cfg(feature = "client-tls")] + "https" => { + let https_connector = connector + .https() + .build() + .map_err(ClientInitError::SslError)?; + HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector)) + }, + #[cfg(not(feature = "client-tls"))] "https" => { - let connector = connector.https() - .build() - .map_err(ClientInitError::SslError)?; - HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector)) + return Err(ClientInitError::TlsNotEnabled); }, _ => { return Err(ClientInitError::InvalidScheme); @@ -241,12 +255,13 @@ impl Client< } } -#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] type HttpsConnector = hyper_tls::HttpsConnector; -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector; +#[cfg(feature = "client-tls")] impl Client< DropContextService< hyper_util::service::TowerToHyperService< @@ -261,10 +276,25 @@ impl Client< > where C: Clone + Send + Sync + 'static { - /// Create a client with a TLS connection to the server + /// Create a client with a TLS connection to the server using native-tls. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + pub fn try_new_https(base_path: &str) -> Result + { + let https_connector = Connector::builder() + .https() + .build() + .map_err(ClientInitError::SslError)?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } + + /// Create a client with a TLS connection to the server using OpenSSL. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https(base_path: &str) -> Result { let https_connector = Connector::builder() @@ -277,7 +307,7 @@ impl Client< /// Create a client with a TLS connection to the server using a pinned certificate /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https_pinned( @@ -298,7 +328,7 @@ impl Client< /// Create a client with a mutually authenticated TLS connection to the server. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server /// * `client_key` - Path to the client private key /// * `client_certificate` - Path to the client's public certificate associated with the private key @@ -360,12 +390,15 @@ pub enum ClientInitError { /// Missing Hostname MissingHost, + /// HTTPS requested but TLS features not enabled + TlsNotEnabled, + /// SSL Connection Error - #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + #[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] SslError(native_tls::Error), /// SSL Connection Error - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] SslError(openssl::error::ErrorStack), } diff --git a/samples/server/petstore/rust-server/output/rust-server-test/Cargo.toml b/samples/server/petstore/rust-server/output/rust-server-test/Cargo.toml index c5bde100d3dc..59bac87e1e51 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/Cargo.toml +++ b/samples/server/petstore/rust-server/output/rust-server-test/Cargo.toml @@ -10,7 +10,19 @@ edition = "2018" [features] default = ["client", "server"] client = [ - "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url" + "hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "url" +] +# TLS support - automatically selects backend based on target OS: +# - macOS/Windows/iOS: native-tls via hyper-tls +# - Other platforms: OpenSSL via hyper-openssl +# Dependencies are in target-specific sections below +client-tls = [ + "client", + "dep:native-tls", + "dep:hyper-tls", + "dep:openssl", + "dep:hyper-openssl", + "swagger/tls" ] server = [ "serde_ignored", "hyper", "percent-encoding", "url", @@ -24,20 +36,12 @@ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk- mock = ["mockall"] validate = ["regex", "serde_valid", "swagger/serdevalid"] -[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] -native-tls = { version = "0.2", optional = true } -hyper-tls = { version = "0.6", optional = true } - -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies] -hyper-openssl = { version = "0.10", optional = true } -openssl = { version = "0.10", optional = true } - [dependencies] # Common async-trait = "0.1.88" chrono = { version = "0.4", features = ["serde"] } futures = "0.3" -swagger = { version = "7.0.0", features = ["serdejson", "server", "client", "tls"] } +swagger = { version = "7.0.0", features = ["serdejson", "server", "client"] } headers = "0.4.0" log = "0.4.27" @@ -64,6 +68,12 @@ url = { version = "2.5", optional = true } # Client-specific tower-service = "0.3.3" +# TLS support - all listed here, actual usage determined by cfg attributes in code +native-tls = { version = "0.2", optional = true } +hyper-tls = { version = "0.6", optional = true } +openssl = { version = "0.10", optional = true } +hyper-openssl = { version = "0.10", optional = true } + # Server, and client callback-specific lazy_static = { version = "1.5", optional = true } regex = { version = "1.12", optional = true } @@ -89,15 +99,13 @@ clap = "4.5" env_logger = "0.11" tokio = { version = "1.49", features = ["full"] } native-tls = "0.2" +openssl = "0.10" +tokio-openssl = "0.6" pin-project = "1.1.10" # Bearer authentication, used in examples jsonwebtoken = {version = "10.0.0", features = ["rust_crypto"]} -[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies] -tokio-openssl = "0.6" -openssl = "0.10" - [[example]] name = "client" required-features = ["client"] diff --git a/samples/server/petstore/rust-server/output/rust-server-test/README.md b/samples/server/petstore/rust-server/output/rust-server-test/README.md index 05ed29e21a4c..9fd06fad6f2e 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/README.md +++ b/samples/server/petstore/rust-server/output/rust-server-test/README.md @@ -115,6 +115,11 @@ The generated library has a few optional features that can be activated through * `client` * This defaults to enabled and creates the basic skeleton of a client implementation based on hyper * The constructed client implements the API trait by making remote API call. +* `client-tls` + * Optional feature that provides HTTPS support with automatic TLS backend selection: + - macOS/Windows/iOS: native-tls + hyper-tls + - Linux/Unix/others: OpenSSL + hyper-openssl + * Not enabled by default to minimize dependencies. * `conversions` * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. * `cli` @@ -123,6 +128,27 @@ The generated library has a few optional features that can be activated through * This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`. * Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks. +### Enabling HTTPS/TLS Support + +By default, only HTTP support is included. To enable HTTPS, add the `client-tls` feature: + +```toml +[dependencies] +rust-server-test = { version = "2.3.4", features = ["client-tls"] } +``` + +**For server with callbacks that need HTTPS:** +```toml +[dependencies] +rust-server-test = { version = "2.3.4", features = ["server", "client-tls"] } +``` + +The TLS backend is automatically selected based on your target platform: +- **macOS, Windows, iOS**: Uses `native-tls` (system TLS libraries) +- **Linux, Unix, other platforms**: Uses `openssl` + +This ensures the best compatibility and native integration on each platform. + See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. ## Documentation for API Endpoints diff --git a/samples/server/petstore/rust-server/output/rust-server-test/bin/cli.rs b/samples/server/petstore/rust-server/output/rust-server-test/bin/cli.rs index 01cbbf1f003a..b9446c60ed6b 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/bin/cli.rs +++ b/samples/server/petstore/rust-server/output/rust-server-test/bin/cli.rs @@ -41,17 +41,17 @@ struct Cli { server_address: String, /// Path to the client private key if using client-side TLS authentication - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_certificate", "server_certificate"]))] client_key: Option, /// Path to the client's public certificate associated with the private key - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long, requires_all(&["client_key", "server_certificate"]))] client_certificate: Option, /// Path to CA certificate used to authenticate the server - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] #[clap(long)] server_certificate: Option, @@ -96,7 +96,8 @@ enum Operation { }, } -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +// On Linux/Unix with OpenSSL (client-tls feature), support certificate pinning and mutual TLS +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] fn create_client(args: &Cli, context: ClientContext) -> Result>> { if args.client_certificate.is_some() { debug!("Using mutual TLS"); @@ -122,8 +123,15 @@ fn create_client(args: &Cli, context: ClientContext) -> Result Result>> { + // Client::try_new() automatically detects the URL scheme (http:// or https://) + // and creates the appropriate client. + // Note: Certificate pinning and mutual TLS are only available on Linux/Unix with OpenSSL let client = Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?; Ok(Box::new(client.with_context(context))) diff --git a/samples/server/petstore/rust-server/output/rust-server-test/examples/client/main.rs b/samples/server/petstore/rust-server/output/rust-server-test/examples/client/main.rs index 63ebeec10bff..87e4794a994d 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/examples/client/main.rs +++ b/samples/server/petstore/rust-server/output/rust-server-test/examples/client/main.rs @@ -101,17 +101,33 @@ fn main() { let context: ClientContext = swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default()); - let mut client : Box> = if is_https { - // Using Simple HTTPS - let client = Box::new(Client::try_new_https(&base_url) - .expect("Failed to create HTTPS client")); - Box::new(client.with_context(context)) - } else { - // Using HTTP - let client = Box::new(Client::try_new_http( - &base_url) - .expect("Failed to create HTTP client")); - Box::new(client.with_context(context)) + let mut client : Box> = { + #[cfg(feature = "client-tls")] + { + if is_https { + // Using HTTPS with native-tls + let client = Box::new(Client::try_new_https(&base_url) + .expect("Failed to create HTTPS client")); + Box::new(client.with_context(context)) + } else { + // Using HTTP + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } + } + + #[cfg(not(feature = "client-tls"))] + { + if is_https { + panic!("HTTPS requested but TLS support not enabled. \ + Enable the 'client-tls' feature to use HTTPS."); + } + // Using HTTP only + let client = Box::new(Client::try_new_http(&base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + } }; let mut rt = tokio::runtime::Runtime::new().unwrap(); diff --git a/samples/server/petstore/rust-server/output/rust-server-test/src/client/mod.rs b/samples/server/petstore/rust-server/output/rust-server-test/src/client/mod.rs index 9c04dae37601..e178ec17e05b 100644 --- a/samples/server/petstore/rust-server/output/rust-server-test/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/rust-server-test/src/client/mod.rs @@ -161,10 +161,17 @@ impl Client< } } +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] +type HyperHttpsConnector = hyper_tls::HttpsConnector; + +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] +type HyperHttpsConnector = hyper_openssl::client::legacy::HttpsConnector; + #[derive(Debug, Clone)] pub enum HyperClient { Http(hyper_util::client::legacy::Client>), - Https(hyper_util::client::legacy::Client>), + #[cfg(feature = "client-tls")] + Https(hyper_util::client::legacy::Client>), } impl Service>> for HyperClient { @@ -175,7 +182,8 @@ impl Service>> for HyperClient { fn call(&self, req: Request>) -> Self::Future { match self { HyperClient::Http(client) => client.request(req), - HyperClient::Https(client) => client.request(req) + #[cfg(feature = "client-tls")] + HyperClient::Https(client) => client.request(req), } } } @@ -201,11 +209,17 @@ impl Client, C> where "http" => { HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build())) }, + #[cfg(feature = "client-tls")] + "https" => { + let https_connector = connector + .https() + .build() + .map_err(ClientInitError::SslError)?; + HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector)) + }, + #[cfg(not(feature = "client-tls"))] "https" => { - let connector = connector.https() - .build() - .map_err(ClientInitError::SslError)?; - HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector)) + return Err(ClientInitError::TlsNotEnabled); }, _ => { return Err(ClientInitError::InvalidScheme); @@ -249,12 +263,13 @@ impl Client< } } -#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] type HttpsConnector = hyper_tls::HttpsConnector; -#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector; +#[cfg(feature = "client-tls")] impl Client< DropContextService< hyper_util::service::TowerToHyperService< @@ -269,10 +284,25 @@ impl Client< > where C: Clone + Send + Sync + 'static { - /// Create a client with a TLS connection to the server + /// Create a client with a TLS connection to the server using native-tls. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + pub fn try_new_https(base_path: &str) -> Result + { + let https_connector = Connector::builder() + .https() + .build() + .map_err(ClientInitError::SslError)?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } + + /// Create a client with a TLS connection to the server using OpenSSL. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "" + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https(base_path: &str) -> Result { let https_connector = Connector::builder() @@ -285,7 +315,7 @@ impl Client< /// Create a client with a TLS connection to the server using a pinned certificate /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] pub fn try_new_https_pinned( @@ -306,7 +336,7 @@ impl Client< /// Create a client with a mutually authenticated TLS connection to the server. /// /// # Arguments - /// * `base_path` - base path of the client API, i.e. "" + /// * `base_path` - base path of the client API, i.e. "" /// * `ca_certificate` - Path to CA certificate used to authenticate the server /// * `client_key` - Path to the client private key /// * `client_certificate` - Path to the client's public certificate associated with the private key @@ -368,12 +398,15 @@ pub enum ClientInitError { /// Missing Hostname MissingHost, + /// HTTPS requested but TLS features not enabled + TlsNotEnabled, + /// SSL Connection Error - #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + #[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))] SslError(native_tls::Error), /// SSL Connection Error - #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + #[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))] SslError(openssl::error::ErrorStack), }